diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index c9a25670a90..00000000000 --- a/.eslintignore +++ /dev/null @@ -1,29 +0,0 @@ -**/build -**/dist -**/coverage -.angular -storybook-static - -**/node_modules - -**/webpack.*.js -**/jest.config.js - -apps/browser/config/config.js -apps/browser/src/auth/scripts/duo.js -apps/browser/webpack/manifest.js - -apps/desktop/desktop_native -apps/desktop/src/auth/scripts/duo.js - -apps/web/config.js -apps/web/scripts/*.js -apps/web/tailwind.config.js - -apps/cli/config/config.js - -tailwind.config.js -libs/components/tailwind.config.base.js -libs/components/tailwind.config.js - -scripts/*.js diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index c606b8f933b..00000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,374 +0,0 @@ -{ - "root": true, - "env": { - "browser": true, - "webextensions": true - }, - "overrides": [ - { - "files": ["*.ts", "*.js"], - "plugins": ["@typescript-eslint", "rxjs", "rxjs-angular", "import"], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "project": ["./tsconfig.eslint.json"], - "sourceType": "module", - "ecmaVersion": 2020 - }, - "extends": [ - "eslint:recommended", - "plugin:@angular-eslint/recommended", - "plugin:@typescript-eslint/recommended", - "plugin:import/recommended", - "plugin:import/typescript", - "plugin:rxjs/recommended", - "prettier", - "plugin:storybook/recommended" - ], - "settings": { - "import/parsers": { - "@typescript-eslint/parser": [".ts"] - }, - "import/resolver": { - "typescript": { - "alwaysTryTypes": true - } - } - }, - "rules": { - "@angular-eslint/component-class-suffix": 0, - "@angular-eslint/contextual-lifecycle": 0, - "@angular-eslint/directive-class-suffix": 0, - "@angular-eslint/no-empty-lifecycle-method": 0, - "@angular-eslint/no-host-metadata-property": 0, - "@angular-eslint/no-input-rename": 0, - "@angular-eslint/no-inputs-metadata-property": 0, - "@angular-eslint/no-output-native": 0, - "@angular-eslint/no-output-on-prefix": 0, - "@angular-eslint/no-output-rename": 0, - "@angular-eslint/no-outputs-metadata-property": 0, - "@angular-eslint/use-lifecycle-interface": "error", - "@angular-eslint/use-pipe-transform-interface": 0, - "@typescript-eslint/explicit-member-accessibility": [ - "error", - { "accessibility": "no-public" } - ], - "@typescript-eslint/no-explicit-any": "off", // TODO: This should be re-enabled - "@typescript-eslint/no-floating-promises": "error", - "@typescript-eslint/no-misused-promises": ["error", { "checksVoidReturn": false }], - "@typescript-eslint/no-this-alias": ["error", { "allowedNames": ["self"] }], - "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], - "no-console": "error", - "import/no-unresolved": "off", // TODO: Look into turning off once each package is an actual package. - "import/order": [ - "error", - { - "alphabetize": { - "order": "asc" - }, - "newlines-between": "always", - "pathGroups": [ - { - "pattern": "@bitwarden/**", - "group": "external", - "position": "after" - }, - { - "pattern": "src/**/*", - "group": "parent", - "position": "before" - } - ], - "pathGroupsExcludedImportTypes": ["builtin"] - } - ], - "rxjs-angular/prefer-takeuntil": ["error", { "alias": ["takeUntilDestroyed"] }], - "rxjs/no-exposed-subjects": ["error", { "allowProtected": true }], - "no-restricted-syntax": [ - "error", - { - "message": "Calling `svgIcon` directly is not allowed", - "selector": "CallExpression[callee.name='svgIcon']" - }, - { - "message": "Accessing FormGroup using `get` is not allowed, use `.value` instead", - "selector": "ChainExpression[expression.object.callee.property.name='get'][expression.property.name='value']" - } - ], - "curly": ["error", "all"], - "import/namespace": ["off"], // This doesn't resolve namespace imports correctly, but TS will throw for this anyway - "import/no-restricted-paths": [ - "error", - { - "zones": [ - { - "target": ["libs/**/*"], - "from": ["apps/**/*"], - "message": "Libs should not import app-specific code." - }, - { - // avoid specific frameworks or large dependencies in common - "target": "./libs/common/**/*", - "from": [ - // Angular - "./libs/angular/**/*", - "./node_modules/@angular*/**/*", - - // Node - "./libs/node/**/*", - - //Generator - "./libs/tools/generator/components/**/*", - "./libs/tools/generator/core/**/*", - "./libs/tools/generator/extensions/**/*", - - // Import/export - "./libs/importer/**/*", - "./libs/tools/export/vault-export/vault-export-core/**/*" - ] - }, - { - // avoid import of unexported state objects - "target": [ - "!(libs)/**/*", - "libs/!(common)/**/*", - "libs/common/!(src)/**/*", - "libs/common/src/!(platform)/**/*", - "libs/common/src/platform/!(state)/**/*" - ], - "from": ["./libs/common/src/platform/state/**/*"], - // allow module index import - "except": ["**/state/index.ts"] - } - ] - } - ], - "no-restricted-imports": ["error", { "patterns": ["src/**/*"] }] - } - }, - { - "files": ["*.html"], - "parser": "@angular-eslint/template-parser", - "plugins": ["@angular-eslint/template", "tailwindcss"], - "rules": { - "@angular-eslint/template/button-has-type": "error", - "tailwindcss/no-custom-classname": [ - "error", - { - // uses negative lookahead to whitelist any class that doesn't start with "tw-" - // in other words: classnames that start with tw- must be valid TailwindCSS classes - "whitelist": ["(?!(tw)\\-).*"] - } - ], - "tailwindcss/enforces-negative-arbitrary-values": "error", - "tailwindcss/enforces-shorthand": "error", - "tailwindcss/no-contradicting-classname": "error" - } - }, - { - "files": ["libs/admin-console/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/admin-console/*", "src/**/*"] } - ] - } - }, - { - "files": ["libs/angular/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/angular/*", "src/**/*"] }] - } - }, - { - "files": ["libs/auth/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/auth/*", "src/**/*"] }] - } - }, - { - "files": ["libs/billing/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/billing/*", "src/**/*"] }] - } - }, - { - "files": ["libs/common/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/common/*", "src/**/*"] }] - } - }, - { - "files": ["libs/components/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/components/*", "src/**/*", "@bitwarden/angular/*"] } - ] - } - }, - { - "files": ["libs/tools/generator/components/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/generator-components/*", "src/**/*"] } - ] - } - }, - { - "files": ["libs/tools/generator/core/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/generator-core/*", "src/**/*"] } - ] - } - }, - { - "files": ["libs/tools/generator/extensions/history/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/generator-history/*", "src/**/*"] } - ] - } - }, - { - "files": ["libs/tools/generator/extensions/legacy/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/generator-legacy/*", "src/**/*"] } - ] - } - }, - { - "files": ["libs/tools/generator/extensions/navigation/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/generator-navigation/*", "src/**/*"] } - ] - } - }, - { - "files": ["libs/tools/export/vault-export/vault-export-core/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/vault-export-core/*", "src/**/*"] } - ] - } - }, - { - "files": ["libs/tools/export/vault-export/vault-export-ui/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/vault-export-ui/*", "src/**/*"] } - ] - } - }, - { - "files": ["libs/importer/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/importer/*", "src/**/*"] }] - } - }, - { - "files": ["libs/node/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/node/*", "src/**/*"] }] - } - }, - { - "files": ["libs/platform/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/platform/*", "src/**/*"] }] - } - }, - { - "files": ["libs/tools/send/send-ui/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/send-ui/*", "src/**/*"] }] - } - }, - { - "files": ["libs/tools/card/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/tools-card/*", "src/**/*"] }] - } - }, - { - "files": ["libs/vault/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/vault/*", "src/**/*"] }] - } - }, - { - "files": ["apps/browser/src/**/*.ts", "libs/**/*.ts"], - "excludedFiles": [ - "apps/browser/src/autofill/{content,notification}/**/*.ts", - "apps/browser/src/**/background/**/*.ts", // It's okay to have long lived listeners in the background - "apps/browser/src/platform/background.ts" - ], - "rules": { - "no-restricted-syntax": [ - "error", - { - "message": "Using addListener in the browser popup produces a memory leak in Safari, use `BrowserApi.addListener` instead", - // This selector covers events like chrome.storage.onChange & chrome.runtime.onMessage - "selector": "CallExpression > [object.object.object.name='chrome'][property.name='addListener']" - }, - { - "message": "Using addListener in the browser popup produces a memory leak in Safari, use `BrowserApi.addListener` instead", - // This selector covers events like chrome.storage.local.onChange - "selector": "CallExpression > [object.object.object.object.name='chrome'][property.name='addListener']" - } - ] - } - }, - { - "files": ["**/*.ts"], - "excludedFiles": ["**/platform/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { - "patterns": [ - "**/platform/**/internal", // General internal pattern - // All features that have been converted to barrel files - "**/platform/messaging/**" - ] - } - ] - } - }, - { - "files": ["bitwarden_license/bit-common/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/bit-common/*", "src/**/*"] }] - } - }, - { - "files": ["apps/**/*.ts"], - "rules": { - // Catches static imports - "no-restricted-imports": [ - "error", - { - "patterns": ["biwarden_license/**", "@bitwarden/bit-common/*", "@bitwarden/bit-web/*"] - } - ], - // Catches dynamic imports, e.g. in routing modules where modules are lazy-loaded - "no-restricted-syntax": [ - "error", - { - "message": "Don't import Bitwarden licensed code into OSS code.", - "selector": "ImportExpression > Literal.source[value=/.*(bitwarden_license|bit-common|bit-web).*/]" - } - ] - } - } - ] -} diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index cb36d87b9e1..2550f0fddbe 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,6 +4,13 @@ # # https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners +## Desktop native module ## +apps/desktop/desktop_native @bitwarden/team-platform-dev +apps/desktop/desktop_native/objc/src/native/autofill @bitwarden/team-autofill-dev +apps/desktop/desktop_native/core/src/autofill @bitwarden/team-autofill-dev +## No ownership for Cargo.toml to allow dependency updates +apps/desktop/desktop_native/Cargo.toml + ## Auth team files ## apps/browser/src/auth @bitwarden/team-auth-dev apps/cli/src/auth @bitwarden/team-auth-dev @@ -29,12 +36,12 @@ libs/tools @bitwarden/team-tools-dev bitwarden_license/bit-web/src/app/tools @bitwarden/team-tools-dev bitwarden_license/bit-common/src/tools @bitwarden/team-tools-dev -## Localization/Crowdin (Tools team) -apps/browser/src/_locales @bitwarden/team-tools-dev -apps/browser/store/locales @bitwarden/team-tools-dev -apps/cli/src/locales @bitwarden/team-tools-dev -apps/desktop/src/locales @bitwarden/team-tools-dev -apps/web/src/locales @bitwarden/team-tools-dev +## Localization/Crowdin (Platform and Tools team) +apps/browser/src/_locales @bitwarden/team-tools-dev @bitwarden/team-platform-dev +apps/browser/store/locales @bitwarden/team-tools-dev @bitwarden/team-platform-dev +apps/cli/src/locales @bitwarden/team-tools-dev @bitwarden/team-platform-dev +apps/desktop/src/locales @bitwarden/team-tools-dev @bitwarden/team-platform-dev +apps/web/src/locales @bitwarden/team-tools-dev @bitwarden/team-platform-dev ## Vault team files ## apps/browser/src/vault @bitwarden/team-vault-dev @@ -58,6 +65,7 @@ libs/admin-console @bitwarden/team-admin-console-dev ## Billing team files ## apps/browser/src/billing @bitwarden/team-billing-dev +apps/desktop/src/billing @bitwarden/team-billing-dev apps/web/src/app/billing @bitwarden/team-billing-dev libs/angular/src/billing @bitwarden/team-billing-dev libs/common/src/billing @bitwarden/team-billing-dev @@ -84,41 +92,51 @@ apps/web/src/app/core @bitwarden/team-platform-dev apps/web/src/app/shared @bitwarden/team-platform-dev apps/web/src/translation-constants.ts @bitwarden/team-platform-dev # Workflows -.github/workflows/brew-bump-desktop.yml @bitwarden/team-platform-dev +# Any changes here should also be reflected in Renovate configuration +.github/workflows/automatic-issue-responses.yml @bitwarden/team-platform-dev +.github/workflows/automatic-pull-request-responses.yml @bitwarden/team-platform-dev +.github/workflows/build-browser-target.yml @bitwarden/team-platform-dev .github/workflows/build-browser.yml @bitwarden/team-platform-dev +.github/workflows/build-cli-target.yml @bitwarden/team-platform-dev .github/workflows/build-cli.yml @bitwarden/team-platform-dev +.github/workflows/build-desktop-target.yml @bitwarden/team-platform-dev .github/workflows/build-desktop.yml @bitwarden/team-platform-dev +.github/workflows/build-web-target.yml @bitwarden/team-platform-dev .github/workflows/build-web.yml @bitwarden/team-platform-dev .github/workflows/chromatic.yml @bitwarden/team-platform-dev +.github/workflows/crowdin-pull.yml @bitwarden/team-platform-dev +.github/workflows/enforce-labels.yml @bitwarden/team-platform-dev .github/workflows/lint.yml @bitwarden/team-platform-dev .github/workflows/locales-lint.yml @bitwarden/team-platform-dev .github/workflows/repository-management.yml @bitwarden/team-platform-dev .github/workflows/scan.yml @bitwarden/team-platform-dev +.github/workflows/stale-bot.yml @bitwarden/team-platform-dev .github/workflows/test.yml @bitwarden/team-platform-dev .github/workflows/version-auto-bump.yml @bitwarden/team-platform-dev +# ESLint custom rules +libs/eslint @bitwarden/team-platform-dev ## Autofill team files ## apps/browser/src/autofill @bitwarden/team-autofill-dev apps/desktop/src/autofill @bitwarden/team-autofill-dev libs/common/src/autofill @bitwarden/team-autofill-dev apps/desktop/macos/autofill-extension @bitwarden/team-autofill-dev +apps/desktop/src/app/components/fido2placeholder.component.ts @bitwarden/team-autofill-dev +apps/desktop/desktop_native/windows-plugin-authenticator @bitwarden/team-autofill-dev # DuckDuckGo integration apps/desktop/native-messaging-test-runner @bitwarden/team-autofill-dev apps/desktop/src/services/duckduckgo-message-handler.service.ts @bitwarden/team-autofill-dev # SSH Agent apps/desktop/desktop_native/core/src/ssh_agent @bitwarden/team-autofill-dev @bitwarden/wg-ssh-keys -## Component Library ## -.storybook @bitwarden/team-design-system -libs/components @bitwarden/team-design-system -apps/browser/src/platform/popup/layout @bitwarden/team-design-system -apps/browser/src/popup/app-routing.animations.ts @bitwarden/team-design-system -apps/web/src/app/layouts @bitwarden/team-design-system +## UI Foundation ## +.storybook @bitwarden/team-ui-foundation +libs/components @bitwarden/team-ui-foundation +libs/ui @bitwarden/team-ui-foundation +apps/browser/src/platform/popup/layout @bitwarden/team-ui-foundation +apps/browser/src/popup/app-routing.animations.ts @bitwarden/team-ui-foundation +apps/web/src/app/layouts @bitwarden/team-ui-foundation -## Desktop native module ## -apps/desktop/desktop_native @bitwarden/team-platform-dev -apps/desktop/desktop_native/objc/src/native/autofill @bitwarden/team-autofill-dev -apps/desktop/desktop_native/core/src/autofill @bitwarden/team-autofill-dev ## Key management team files ## apps/desktop/src/key-management @bitwarden/team-key-management-dev @@ -126,9 +144,10 @@ apps/web/src/app/key-management @bitwarden/team-key-management-dev apps/browser/src/key-management @bitwarden/team-key-management-dev apps/cli/src/key-management @bitwarden/team-key-management-dev libs/key-management @bitwarden/team-key-management-dev +libs/key-management-ui @bitwarden/team-key-management-dev libs/common/src/key-management @bitwarden/team-key-management-dev -apps/desktop/destkop_native/core/src/biometric/ @bitwarden/team-key-management-dev +apps/desktop/desktop_native/core/src/biometric/ @bitwarden/team-key-management-dev apps/desktop/src/services/native-messaging.service.ts @bitwarden/team-key-management-dev apps/browser/src/background/nativeMessaging.background.ts @bitwarden/team-key-management-dev apps/desktop/src/services/biometric-message-handler.service.ts @bitwarden/team-key-management-dev @@ -141,6 +160,7 @@ apps/desktop/src/locales/en/messages.json apps/web/src/locales/en/messages.json ## BRE team owns these workflows ## +# Any changes here should also be reflected in Renovate configuration ## .github/workflows/brew-bump-desktop.yml @bitwarden/dept-bre .github/workflows/deploy-web.yml @bitwarden/dept-bre .github/workflows/publish-cli.yml @bitwarden/dept-bre @@ -148,16 +168,18 @@ apps/web/src/locales/en/messages.json .github/workflows/publish-web.yml @bitwarden/dept-bre .github/workflows/retrieve-current-desktop-rollout.yml @bitwarden/dept-bre .github/workflows/staged-rollout-desktop.yml @bitwarden/dept-bre - -## Shared ownership workflows ## -.github/workflows/release-browser.yml -.github/workflows/release-cli.yml -.github/workflows/release-desktop-beta.yml -.github/workflows/release-desktop.yml -.github/workflows/release-web.yml +.github/workflows/release-browser.yml @bitwarden/dept-bre +.github/workflows/release-cli.yml @bitwarden/dept-bre +.github/workflows/release-desktop-beta.yml @bitwarden/dept-bre +.github/workflows/release-desktop.yml @bitwarden/dept-bre +.github/workflows/release-web.yml @bitwarden/dept-bre ## Docker files have shared ownership ## **/Dockerfile **/*.Dockerfile **/.dockerignore **/entrypoint.sh + +## Overrides +# tsconfig files are potentially dangerous and will be reviewed by platform to prevent misconfigurations +**/tsconfig.json @bitwarden/team-platform-dev diff --git a/.github/ISSUE_TEMPLATE/browser.yml b/.github/ISSUE_TEMPLATE/browser.yml index ec78f3ee555..23a0e4276bf 100644 --- a/.github/ISSUE_TEMPLATE/browser.yml +++ b/.github/ISSUE_TEMPLATE/browser.yml @@ -84,11 +84,11 @@ body: attributes: label: Browser Version description: What version of the browser(s) are you seeing the problem on? - - type: input + - type: textarea id: version attributes: - label: Build Version - description: What version of our software are you running? (go to "Settings" → "About" in the extension) + label: Environment Versions + description: Copy from "Settings" → "About" → "About Bitwarden" in the extension. Should include the extension version and server environment. validations: required: true - type: checkboxes diff --git a/.github/codecov.yml b/.github/codecov.yml index b4774407206..d9d59f9de28 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -1,2 +1,68 @@ ignore: - "**/*.spec.ts" # Tests + +component_management: + default_rules: + statuses: + - type: project + target: auto + individual_components: + - component_id: key-management-biometrics + name: Key Management - Biometrics + paths: + - apps/browser/src/key-management/biometrics/** + - apps/cli/src/key-management/cli-biometrics-service.ts + - apps/desktop/desktop_native/core/src/biometric/** + - apps/desktop/src/key-management/biometrics/** + - apps/desktop/src/services/biometric-message-handler.service.ts + - apps/web/src/app/key-management/web-biometric.service.ts + - libs/key-management/src/biometrics/** + - component_id: key-management-lock + name: Key Management - Lock + paths: + - apps/browser/src/key-management/lock/** + - apps/desktop/src/key-management/lock/** + - apps/web/src/app/key-management/lock/** + - libs/key-management-ui/src/lock/** + - libs/common/src/key-management/device-trust/** + - component_id: key-management-ipc + name: Key Management - IPC + paths: + - apps/browser/src/background/nativeMessaging.background.ts + - apps/desktop/src/services/native-messaging.service.ts + - component_id: key-management-key-rotation + name: Key Management - Key Rotation + paths: + - apps/web/src/app/key-management/key-rotation/** + - apps/web/src/app/key-management/migrate-encryption/** + - libs/key-management/src/user-asymmetric-key-regeneration/** + - component_id: key-management-process-reload + name: Key Management - Process Reload + paths: + - apps/web/src/app/key-management/services/web-process-reload.service.ts + - libs/common/src/key-management/services/default-process-reload.service.ts + - component_id: key-management-keys + name: Key Management - Keys + paths: + - libs/key-management/src/kdf-config.service.ts + - libs/key-management/src/key.service.ts + - libs/common/src/key-management/master-password/** + - libs/common/src/key-management/key-connector/** + - component_id: key-management-crypto + name: Key Management - Crypto + paths: + - libs/common/src/key-management/crypto/** + - component_id: key-management + name: Key Management + paths: + - apps/browser/src/key-management/** + - apps/browser/src/background/nativeMessaging.background.ts + - apps/cli/src/key-management/** + - apps/desktop/desktop_native/core/src/biometric/** + - apps/desktop/src/key-management/** + - apps/desktop/src/services/biometric-message-handler.service.ts + - apps/desktop/src/services/native-messaging.service.ts + - apps/web/src/app/key-management/** + - libs/common/src/key-management/** + - libs/key-management/** + - libs/key-management-ui/** diff --git a/.github/renovate.json b/.github/renovate.json deleted file mode 100644 index 776c66af68e..00000000000 --- a/.github/renovate.json +++ /dev/null @@ -1,283 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": ["github>bitwarden/renovate-config"], - "enabledManagers": ["cargo", "github-actions", "npm"], - "packageRules": [ - { - "groupName": "gh minor", - "matchManagers": ["github-actions"], - "matchUpdateTypes": ["minor", "patch"] - }, - { - "matchManagers": ["github-actions"], - "commitMessagePrefix": "[deps] BRE:" - }, - { - "matchManagers": ["cargo"], - "commitMessagePrefix": "[deps] Platform:" - }, - { - "groupName": "napi", - "matchPackageNames": ["napi", "napi-build", "napi-derive"] - }, - { - "matchPackageNames": ["typescript", "zone.js"], - "matchUpdateTypes": ["major", "minor"], - "description": "Determined by Angular", - "enabled": false - }, - { - "matchPackageNames": ["typescript", "zone.js"], - "matchUpdateTypes": "patch" - }, - { - "groupName": "jest", - "matchPackageNames": ["@types/jest", "jest", "ts-jest", "jest-preset-angular"], - "matchUpdateTypes": "major" - }, - { - "groupName": "macOS/iOS bindings", - "matchPackageNames": ["core-foundation", "security-framework", "security-framework-sys"] - }, - { - "groupName": "zbus", - "matchPackageNames": ["zbus", "zbus_polkit"] - }, - { - "matchPackageNames": [ - "base64-loader", - "buffer", - "bufferutil", - "core-js", - "css-loader", - "html-loader", - "mini-css-extract-plugin", - "ngx-infinite-scroll", - "postcss", - "postcss-loader", - "process", - "sass", - "sass-loader", - "style-loader", - "ts-loader", - "url", - "util" - ], - "description": "Admin Console owned dependencies", - "commitMessagePrefix": "[deps] AC:", - "reviewers": ["team:team-admin-console-dev"] - }, - { - "matchPackageNames": ["qrious"], - "description": "Auth owned dependencies", - "commitMessagePrefix": "[deps] Auth:", - "reviewers": ["team:team-auth-dev"] - }, - { - "matchPackageNames": [ - "@emotion/css", - "@webcomponents/custom-elements", - "concurrently", - "cross-env", - "del", - "lit", - "nord", - "patch-package", - "prettier", - "prettier-plugin-tailwindcss", - "rimraf", - "tabbable", - "tldts", - "wait-on" - ], - "description": "Autofill owned dependencies", - "commitMessagePrefix": "[deps] Autofill:", - "reviewers": ["team:team-autofill-dev"] - }, - { - "matchPackageNames": ["braintree-web-drop-in"], - "description": "Billing owned dependencies", - "commitMessagePrefix": "[deps] Billing:", - "reviewers": ["team:team-billing-dev"] - }, - { - "matchPackageNames": [ - "@babel/core", - "@babel/preset-env", - "@bitwarden/sdk-internal", - "@electron/fuses", - "@electron/notarize", - "@electron/rebuild", - "@ngtools/webpack", - "@types/chrome", - "@types/firefox-webext-browser", - "@types/glob", - "@types/jquery", - "@types/lowdb", - "@types/node", - "@types/node-forge", - "@types/node-ipc", - "@yao-pkg/pkg", - "babel-loader", - "browserslist", - "copy-webpack-plugin", - "electron", - "electron-builder", - "electron-log", - "electron-reload", - "electron-store", - "electron-updater", - "html-webpack-injector", - "html-webpack-plugin", - "lowdb", - "node-forge", - "node-ipc", - "pkg", - "rxjs", - "tsconfig-paths-webpack-plugin", - "type-fest", - "typescript", - "typescript-strict-plugin", - "webpack", - "webpack-cli", - "webpack-dev-server", - "webpack-node-externals" - ], - "description": "Platform owned dependencies", - "commitMessagePrefix": "[deps] Platform:", - "reviewers": ["team:team-platform-dev"] - }, - { - "matchPackageNames": [ - "@angular-devkit/build-angular", - "@angular/animations", - "@angular/cdk", - "@angular/cli", - "@angular/common", - "@angular/compiler-cli", - "@angular/compiler", - "@angular/core", - "@angular/forms", - "@angular/platform-browser-dynamic", - "@angular/platform-browser", - "@angular/platform", - "@angular/router", - "@compodoc/compodoc", - "@ng-select/ng-select", - "@storybook/addon-a11y", - "@storybook/addon-actions", - "@storybook/addon-designs", - "@storybook/addon-essentials", - "@storybook/addon-interactions", - "@storybook/addon-links", - "@storybook/angular", - "@storybook/manager-api", - "@storybook/theming", - "@types/react", - "autoprefixer", - "bootstrap", - "chromatic", - "jquery", - "ngx-toastr", - "popper.js", - "react", - "react-dom", - "remark-gfm", - "storybook", - "tailwindcss", - "zone.js" - ], - "description": "Component library owned dependencies", - "commitMessagePrefix": "[deps] Design System:", - "reviewers": ["team:team-design-system"] - }, - { - "matchPackageNames": [ - "@angular-eslint/eslint-plugin", - "@angular-eslint/eslint-plugin-template", - "@angular-eslint/schematics", - "@angular-eslint/template-parser", - "@angular/elements", - "@types/jest", - "@typescript-eslint/eslint-plugin", - "@typescript-eslint/parser", - "eslint", - "eslint-config-prettier", - "eslint-import-resolver-typescript", - "eslint-plugin-import", - "eslint-plugin-rxjs", - "eslint-plugin-rxjs-angular", - "eslint-plugin-storybook", - "eslint-plugin-tailwindcss", - "husky", - "jest-extended", - "jest-junit", - "jest-mock-extended", - "jest-preset-angular", - "lint-staged", - "ts-jest" - ], - "description": "Secrets Manager owned dependencies", - "commitMessagePrefix": "[deps] SM:", - "reviewers": ["team:team-secrets-manager-dev"] - }, - { - "matchPackageNames": [ - "@microsoft/signalr-protocol-msgpack", - "@microsoft/signalr", - "@types/jsdom", - "@types/papaparse", - "@types/zxcvbn", - "jsdom", - "jszip", - "oidc-client-ts", - "papaparse", - "utf-8-validate", - "zxcvbn" - ], - "description": "Tools owned dependencies", - "commitMessagePrefix": "[deps] Tools:", - "reviewers": ["team:team-tools-dev"] - }, - { - "matchPackageNames": [ - "@koa/multer", - "@koa/router", - "@types/inquirer", - "@types/koa", - "@types/koa__multer", - "@types/koa__router", - "@types/koa-bodyparser", - "@types/koa-json", - "@types/lunr", - "@types/node-fetch", - "@types/proper-lockfile", - "@types/retry", - "chalk", - "commander", - "form-data", - "https-proxy-agent", - "inquirer", - "koa", - "koa-bodyparser", - "koa-json", - "lunr", - "multer", - "node-fetch", - "open", - "proper-lockfile", - "qrcode-parser" - ], - "description": "Vault owned dependencies", - "commitMessagePrefix": "[deps] Vault:", - "reviewers": ["team:team-vault-dev"] - }, - { - "matchPackageNames": ["@types/argon2-browser", "argon2", "argon2-browser", "big-integer"], - "description": "Key Management owned dependencies", - "commitMessagePrefix": "[deps] KM:", - "reviewers": ["team:team-key-management-dev"] - } - ], - "ignoreDeps": ["@types/koa-bodyparser", "bootstrap", "node-ipc", "node", "npm"] -} diff --git a/.github/renovate.json5 b/.github/renovate.json5 new file mode 100644 index 00000000000..01361a97404 --- /dev/null +++ b/.github/renovate.json5 @@ -0,0 +1,414 @@ +{ + $schema: "https://docs.renovatebot.com/renovate-schema.json", + extends: ["github>bitwarden/renovate-config"], // Extends our default configuration for pinned dependencies + enabledManagers: ["cargo", "github-actions", "npm"], + packageRules: [ + { + // Group all build/test/lint workflows for GitHub Actions together for Platform + // Since they are code owners we don't need to assign a review team in Renovate + // Any changes here should also be reflected in CODEOWNERS + groupName: "github-action minor", + matchManagers: ["github-actions"], + matchFileNames: [ + "./github/workflows/automatic-issue-responses.yml", + "./github/workflows/automatic-pull-request-responses.yml", + "./github/workflows/build-browser.yml", + "./github/workflows/build-cli.yml", + "./github/workflows/build-desktop.yml", + "./github/workflows/build-web.yml", + "./github/workflows/chromatic.yml", + "./github/workflows/crowdin-pull.yml", + "./github/workflows/enforce-labels.yml", + "./github/workflows/lint.yml", + "./github/workflows/locales-lint.yml", + "./github/workflows/repository-management.yml", + "./github/workflows/scan.yml", + "./github/workflows/stale-bot.yml", + "./github/workflows/test.yml", + "./github/workflows/version-auto-bump.yml", + ], + commitMessagePrefix: "[deps] Platform:", + }, + { + // Group all release-related workflows for GitHub Actions together for BRE + // Since they are code owners we don't need to assign a review team in Renovate + // Any changes here should also be reflected in CODEOWNERS + groupName: "github-action minor", + matchManagers: ["github-actions"], + matchFileNames: [ + "./github/workflows/brew-bump-desktop.yml", + "./github/workflows/deploy-web.yml", + "./github/workflows/publish-cli.yml", + "./github/workflows/publish-desktop.yml", + "./github/workflows/publish-web.yml", + "./github/workflows/retrieve-current-desktop-rollout.yml", + "./github/workflows/staged-rollout-desktop.yml", + "./github/workflows/release-cli.yml", + "./github/workflows/release-desktop-beta.yml", + "./github/workflows/release-desktop.yml", + "./github/workflows/release-web.yml", + ], + commitMessagePrefix: "[deps] BRE:", + }, + { + // Disable major and minor updates for TypeScript and Zone.js because they are managed by Angular + matchPackageNames: ["typescript", "zone.js"], + matchUpdateTypes: ["major", "minor"], + description: "Determined by Angular", + enabled: false, + }, + { + // Disable major updates for core Angular dependencies because they are managed through ng update + // when we decide to upgrade. + matchSourceUrls: [ + "https://github.com/angular-eslint/angular-eslint", + "https://github.com/angular/angular-cli", + "https://github.com/angular/angular", + "https://github.com/angular/components", + "https://github.com/ng-select/ng-select", + ], + matchUpdateTypes: ["major"], + description: "Manually updated using ng update", + enabled: false, + }, + { + // Renovate should manage patch updates for TypeScript and Zone.js, despite ignoring major and minor + matchPackageNames: ["typescript", "zone.js"], + matchUpdateTypes: "patch", + }, + { + // We want to update all the Jest-related packages together, to reduce PR noise + groupName: "jest", + matchPackageNames: ["@types/jest", "jest", "ts-jest", "jest-preset-angular"], + }, + { + // We need to group all napi-related packages together to avoid build errors caused by version incompatibilities + groupName: "napi", + matchPackageNames: ["napi", "napi-build", "napi-derive"], + }, + { + // We need to group all macOS/iOS binding-related packages together to avoid build errors caused by version incompatibilities + groupName: "macOS/iOS bindings", + matchPackageNames: ["core-foundation", "security-framework", "security-framework-sys"], + }, + { + // We need to group all zbus-related packages together to avoid build errors caused by version incompatibilities + groupName: "zbus", + matchPackageNames: ["zbus", "zbus_polkit"], + }, + { + matchPackageNames: [ + "base64-loader", + "buffer", + "bufferutil", + "core-js", + "css-loader", + "html-loader", + "mini-css-extract-plugin", + "postcss", + "postcss-loader", + "process", + "sass", + "sass-loader", + "style-loader", + "ts-loader", + "url", + "util", + ], + description: "Admin Console owned dependencies", + commitMessagePrefix: "[deps] AC:", + reviewers: ["team:team-admin-console-dev"], + }, + { + matchPackageNames: ["qrious"], + description: "Auth owned dependencies", + commitMessagePrefix: "[deps] Auth:", + reviewers: ["team:team-auth-dev"], + }, + { + matchPackageNames: [ + "@angular-eslint/schematics", + "angular-eslint", + "eslint-config-prettier", + "eslint-import-resolver-typescript", + "eslint-plugin-import", + "eslint-plugin-rxjs-angular", + "eslint-plugin-rxjs", + "eslint-plugin-storybook", + "eslint-plugin-tailwindcss", + "eslint", + "husky", + "lint-staged", + "typescript-eslint", + ], + description: "Architecture owned dependencies", + commitMessagePrefix: "[deps] Architecture:", + reviewers: ["team:dept-architecture"], + }, + { + matchPackageNames: [ + "@angular-eslint/schematics", + "angular-eslint", + "eslint-config-prettier", + "eslint-import-resolver-typescript", + "eslint-plugin-import", + "eslint-plugin-rxjs-angular", + "eslint-plugin-rxjs", + "eslint-plugin-storybook", + "eslint-plugin-tailwindcss", + "eslint", + "husky", + "lint-staged", + "typescript-eslint", + ], + groupName: "Linting minor-patch", + matchUpdateTypes: ["minor", "patch"], + }, + { + matchPackageNames: [ + "@emotion/css", + "@webcomponents/custom-elements", + "bitwarden-russh", + "bytes", + "concurrently", + "cross-env", + "del", + "ed25519", + "lit", + "patch-package", + "pkcs8", + "prettier", + "prettier-plugin-tailwindcss", + "rimraf", + "ssh-encoding", + "ssh-key", + "@storybook/web-components-webpack5", + "tabbable", + "tldts", + "wait-on", + ], + description: "Autofill owned dependencies", + commitMessagePrefix: "[deps] Autofill:", + reviewers: ["team:team-autofill-dev"], + }, + { + matchPackageNames: ["braintree-web-drop-in"], + description: "Billing owned dependencies", + commitMessagePrefix: "[deps] Billing:", + reviewers: ["team:team-billing-dev"], + }, + { + matchPackageNames: [ + "@babel/core", + "@babel/preset-env", + "@bitwarden/sdk-internal", + "@electron/fuses", + "@electron/notarize", + "@electron/rebuild", + "@ngtools/webpack", + "@types/chrome", + "@types/firefox-webext-browser", + "@types/glob", + "@types/jquery", + "@types/lowdb", + "@types/node", + "@types/node-forge", + "@types/node-ipc", + "@yao-pkg/pkg", + "anyhow", + "arboard", + "babel-loader", + "base64", + "bindgen", + "browserslist", + "byteorder", + "bytes", + "core-foundation", + "copy-webpack-plugin", + "dirs", + "electron", + "electron-builder", + "electron-log", + "electron-reload", + "electron-store", + "electron-updater", + "embed_plist", + "futures", + "hex", + "homedir", + "html-webpack-injector", + "html-webpack-plugin", + "interprocess", + "json5", + "keytar", + "libc", + "log", + "lowdb", + "napi", + "napi-build", + "napi-derive", + "node-forge", + "node-ipc", + "oo7", + "oslog", + "pin-project", + "pkg", + "rand", + "rxjs", + "scopeguard", + "security-framework", + "security-framework-sys", + "serde", + "serde_json", + "simplelog", + "sysinfo", + "tsconfig-paths-webpack-plugin", + "type-fest", + "typenum", + "typescript", + "typescript-strict-plugin", + "uniffi", + "webpack", + "webpack-cli", + "webpack-dev-server", + "webpack-node-externals", + "widestring", + "windows", + "windows-registry", + "zbus", + "zbus_polkit", + ], + description: "Platform owned dependencies", + commitMessagePrefix: "[deps] Platform:", + reviewers: ["team:team-platform-dev"], + }, + { + matchPackageNames: [ + "@angular-devkit/build-angular", + "@angular/animations", + "@angular/cdk", + "@angular/cli", + "@angular/common", + "@angular/compiler-cli", + "@angular/compiler", + "@angular/core", + "@angular/forms", + "@angular/platform-browser-dynamic", + "@angular/platform-browser", + "@angular/platform", + "@angular/router", + "@compodoc/compodoc", + "@ng-select/ng-select", + "@storybook/addon-a11y", + "@storybook/addon-actions", + "@storybook/addon-designs", + "@storybook/addon-essentials", + "@storybook/addon-interactions", + "@storybook/addon-links", + "@storybook/addon-themes", + "@storybook/angular", + "@storybook/manager-api", + "@storybook/theming", + "@typescript-eslint/utils", + "@typescript-eslint/rule-tester", + "@types/react", + "autoprefixer", + "bootstrap", + "chromatic", + "jquery", + "ngx-toastr", + "popper.js", + "react", + "react-dom", + "remark-gfm", + "storybook", + "tailwindcss", + "zone.js", + ], + description: "UI Foundation owned dependencies", + commitMessagePrefix: "[deps] UI Foundation:", + reviewers: ["team:team-ui-foundation"], + }, + { + matchPackageNames: [ + "@types/jest", + "jest-junit", + "jest-mock-extended", + "jest-preset-angular", + "jest-diff", + "ts-jest", + ], + description: "Secrets Manager owned dependencies", + commitMessagePrefix: "[deps] SM:", + reviewers: ["team:team-secrets-manager-dev"], + }, + { + matchPackageNames: [ + "@microsoft/signalr-protocol-msgpack", + "@microsoft/signalr", + "@types/jsdom", + "@types/papaparse", + "@types/zxcvbn", + "jsdom", + "jszip", + "oidc-client-ts", + "papaparse", + "utf-8-validate", + "zxcvbn", + ], + description: "Tools owned dependencies", + commitMessagePrefix: "[deps] Tools:", + reviewers: ["team:team-tools-dev"], + }, + { + matchPackageNames: [ + "@koa/multer", + "@koa/router", + "@types/inquirer", + "@types/koa", + "@types/koa__multer", + "@types/koa__router", + "@types/koa-bodyparser", + "@types/koa-json", + "@types/lunr", + "@types/node-fetch", + "@types/proper-lockfile", + "@types/retry", + "chalk", + "commander", + "form-data", + "https-proxy-agent", + "inquirer", + "koa", + "koa-bodyparser", + "koa-json", + "lunr", + "multer", + "node-fetch", + "open", + "proper-lockfile", + "qrcode-parser", + ], + description: "Vault owned dependencies", + commitMessagePrefix: "[deps] Vault:", + reviewers: ["team:team-vault-dev"], + }, + { + matchPackageNames: [ + "@types/argon2-browser", + "aes", + "argon2", + "argon2-browser", + "big-integer", + "cbc", + "rsa", + "russh-cryptovec", + "sha2", + ], + description: "Key Management owned dependencies", + commitMessagePrefix: "[deps] KM:", + reviewers: ["team:team-key-management-dev"], + }, + ], + ignoreDeps: ["@types/koa-bodyparser", "bootstrap", "node-ipc", "node", "npm"], +} diff --git a/.github/whitelist-capital-letters.txt b/.github/whitelist-capital-letters.txt index 73d323851e5..653f6591c7f 100644 --- a/.github/whitelist-capital-letters.txt +++ b/.github/whitelist-capital-letters.txt @@ -3,26 +3,12 @@ ./apps/browser/src/safari/desktop/Assets.xcassets/AppIcon.appiconset ./apps/browser/src/safari/desktop/Base.lproj ./apps/browser/store/windows/Assets -./bitwarden_license/README.md ./libs/angular/src/directives/cipherListVirtualScroll.directive.ts -./libs/admin-console/README.md -./libs/auth/README.md -./libs/billing/README.md -./libs/common/src/tools/integration/README.md -./libs/platform/README.md -./libs/key-management/README.md -./libs/tools/README.md -./libs/tools/export/vault-export/README.md -./libs/tools/send/README.md -./libs/tools/card/README.md -./libs/vault/README.md -./README.md ./LICENSE_BITWARDEN.txt ./CONTRIBUTING.md ./LICENSE_GPL.txt ./LICENSE.txt ./apps/web/Dockerfile -./apps/web/README.md ./apps/desktop/resources/installerSidebar.bmp ./apps/desktop/resources/appx/SplashScreen.png ./apps/desktop/resources/appx/BadgeLogo.png @@ -30,10 +16,7 @@ ./apps/desktop/resources/appx/StoreLogo.png ./apps/desktop/resources/appx/Wide310x150Logo.png ./apps/desktop/resources/appx/Square44x44Logo.png -./apps/desktop/README.md ./apps/cli/stores/chocolatey/tools/VERIFICATION.txt -./apps/cli/README.md -./apps/browser/README.md ./apps/browser/store/windows/AppxManifest.xml ./apps/browser/src/background/nativeMessaging.background.ts ./apps/browser/src/models/browserComponentState.ts diff --git a/.github/workflows/build-browser-target.yml b/.github/workflows/build-browser-target.yml new file mode 100644 index 00000000000..3334326920c --- /dev/null +++ b/.github/workflows/build-browser-target.yml @@ -0,0 +1,33 @@ +name: Build Browser on PR Target + +on: + pull_request: + types: [opened, synchronize] + branches-ignore: + - 'l10n_master' + - 'cf-pages' + paths: + - 'apps/browser/**' + - 'libs/**' + - '*' + - '!*.md' + - '!*.txt' + workflow_call: + inputs: {} + +defaults: + run: + shell: bash + +jobs: + check-run: + name: Check PR run + uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main + + run-workflow: + name: Run Build Browser on PR Target + needs: check-run + if: ${{ github.event.pull_request.head.repo.full_name != github.repository }} + uses: ./.github/workflows/build-browser.yml + secrets: inherit + diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index aa62d602ad8..4748a6a9f15 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -1,7 +1,7 @@ name: Build Browser on: - pull_request_target: + pull_request: types: [opened, synchronize] branches-ignore: - 'l10n_master' @@ -38,19 +38,14 @@ defaults: shell: bash jobs: - check-run: - name: Check PR run - uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main - setup: name: Setup runs-on: ubuntu-22.04 - needs: - - check-run outputs: repo_url: ${{ steps.gen_vars.outputs.repo_url }} adj_build_number: ${{ steps.gen_vars.outputs.adj_build_number }} node_version: ${{ steps.retrieve-node-version.outputs.node_version }} + has_secrets: ${{ steps.check-secrets.outputs.has_secrets }} steps: - name: Check out repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -74,6 +69,14 @@ jobs: NODE_VERSION=${NODE_NVMRC/v/''} echo "node_version=$NODE_VERSION" >> $GITHUB_OUTPUT + - name: Check secrets + id: check-secrets + env: + AZURE_KV_CI_SERVICE_PRINCIPAL: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + run: | + has_secrets=${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL != '' }} + echo "has_secrets=$has_secrets" >> $GITHUB_OUTPUT + locales-test: name: Locales Test @@ -169,7 +172,7 @@ jobs: zip -r browser-source.zip browser-source - name: Upload browser source - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: browser-source-${{ env._BUILD_NUMBER }}.zip path: browser-source.zip @@ -196,11 +199,7 @@ jobs: - name: "edge" npm_command: "dist:edge" archive_name: "dist-edge.zip" - artifact_name: "dist-edge" - - name: "edge-mv3" - npm_command: "dist:edge:mv3" - archive_name: "dist-edge.zip" - artifact_name: "DO-NOT-USE-FOR-PROD-dist-edge-MV3" + artifact_name: "dist-edge-MV3" - name: "firefox" npm_command: "dist:firefox" archive_name: "dist-firefox.zip" @@ -209,14 +208,10 @@ jobs: npm_command: "dist:firefox:mv3" archive_name: "dist-firefox.zip" artifact_name: "DO-NOT-USE-FOR-PROD-dist-firefox-MV3" - - name: "opera" - npm_command: "dist:opera" - archive_name: "dist-opera.zip" - artifact_name: "dist-opera" - name: "opera-mv3" npm_command: "dist:opera:mv3" archive_name: "dist-opera.zip" - artifact_name: "DO-NOT-USE-FOR-PROD-dist-opera-MV3" + artifact_name: "dist-opera-MV3" steps: - name: Check out repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -272,7 +267,7 @@ jobs: working-directory: browser-source/apps/browser - name: Upload extension artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: ${{ matrix.artifact_name }}-${{ env._BUILD_NUMBER }}.zip path: browser-source/apps/browser/dist/${{ matrix.archive_name }} @@ -285,6 +280,7 @@ jobs: needs: - setup - locales-test + if: ${{ needs.setup.outputs.has_secrets == 'true' }} env: _BUILD_NUMBER: ${{ needs.setup.outputs.adj_build_number }} _NODE_VERSION: ${{ needs.setup.outputs.node_version }} @@ -409,7 +405,7 @@ jobs: ls -la - name: Upload Safari artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: dist-safari-${{ env._BUILD_NUMBER }}.zip path: apps/browser/dist/dist-safari.zip diff --git a/.github/workflows/build-cli-target.yml b/.github/workflows/build-cli-target.yml new file mode 100644 index 00000000000..81ec4178681 --- /dev/null +++ b/.github/workflows/build-cli-target.yml @@ -0,0 +1,33 @@ +name: Build CLI on PR Target + +on: + pull_request: + types: [opened, synchronize] + branches-ignore: + - 'l10n_master' + - 'cf-pages' + paths: + - 'apps/cli/**' + - 'libs/**' + - '*' + - '!*.md' + - '!*.txt' + - '.github/workflows/build-cli.yml' + - 'bitwarden_license/bit-cli/**' + +defaults: + run: + shell: bash + +jobs: + check-run: + name: Check PR run + uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main + + run-workflow: + name: Run Build CLI on PR Target + needs: check-run + if: ${{ github.event.pull_request.head.repo.full_name != github.repository }} + uses: ./.github/workflows/build-cli.yml + secrets: inherit + diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index 02432e0a5f4..8599a699d9d 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -1,7 +1,7 @@ name: Build CLI on: - pull_request_target: + pull_request: types: [opened, synchronize] branches-ignore: - 'l10n_master' @@ -27,6 +27,8 @@ on: - '!*.txt' - '.github/workflows/build-cli.yml' - 'bitwarden_license/bit-cli/**' + workflow_call: + inputs: {} workflow_dispatch: inputs: sdk_branch: @@ -39,18 +41,13 @@ defaults: working-directory: apps/cli jobs: - check-run: - name: Check PR run - uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main - setup: name: Setup runs-on: ubuntu-22.04 - needs: - - check-run outputs: package_version: ${{ steps.retrieve-package-version.outputs.package_version }} node_version: ${{ steps.retrieve-node-version.outputs.node_version }} + has_secrets: ${{ steps.check-secrets.outputs.has_secrets }} steps: - name: Check out repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -71,15 +68,24 @@ jobs: NODE_VERSION=${NODE_NVMRC/v/''} echo "node_version=$NODE_VERSION" >> $GITHUB_OUTPUT + - name: Check secrets + id: check-secrets + env: + AZURE_KV_CI_SERVICE_PRINCIPAL: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + run: | + has_secrets=${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL != '' }} + echo "has_secrets=$has_secrets" >> $GITHUB_OUTPUT + cli: - name: CLI ${{ matrix.os.base }} - ${{ matrix.license_type.readable }} + name: CLI ${{ matrix.os.base }}${{ matrix.os.target_suffix }} - ${{ matrix.license_type.readable }} strategy: matrix: os: - [ - { base: "linux", distro: "ubuntu-22.04" }, - { base: "mac", distro: "macos-13" } - ] + [ + { base: "linux", distro: "ubuntu-22.04", target_suffix: "" }, + { base: "mac", distro: "macos-13", target_suffix: "" }, + { base: "mac", distro: "macos-14", target_suffix: "-arm64" } + ] license_type: [ { build_prefix: "oss", artifact_prefix: "-oss", readable: "open source license" }, @@ -117,7 +123,7 @@ jobs: working-directory: ./ - name: Download SDK Artifacts - if: ${{ inputs.sdk_branch != '' }} + if: ${{ inputs.sdk_branch != '' && needs.setup.outputs.has_secrets == 'true' }} uses: bitwarden/gh-actions/download-artifacts@main with: github_token: ${{secrets.GITHUB_TOKEN}} @@ -130,23 +136,23 @@ jobs: if_no_artifact_found: fail - name: Override SDK - if: ${{ inputs.sdk_branch != '' }} + if: ${{ inputs.sdk_branch != '' && needs.setup.outputs.has_secrets == 'true' }} working-directory: ./ run: | ls -l ../ npm link ../sdk-internal - name: Build & Package Unix - run: npm run dist:${{ matrix.license_type.build_prefix }}:${{ env.SHORT_RUNNER_OS }} --quiet + run: npm run dist:${{ matrix.license_type.build_prefix }}:${{ env.SHORT_RUNNER_OS }}${{ matrix.os.target_suffix }} --quiet - name: Zip Unix run: | - cd ./dist/${{ matrix.license_type.build_prefix }}/${{ env.LOWER_RUNNER_OS }} - zip ../../bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-${{ env._PACKAGE_VERSION }}.zip ./bw + cd ./dist/${{ matrix.license_type.build_prefix }}/${{ env.LOWER_RUNNER_OS }}${{ matrix.os.target_suffix }} + zip ../../bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}${{ matrix.os.target_suffix }}-${{ env._PACKAGE_VERSION }}.zip ./bw - name: Version Test run: | - unzip "./dist/bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-${{ env._PACKAGE_VERSION }}.zip" -d "./test" + unzip "./dist/bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}${{ matrix.os.target_suffix }}-${{ env._PACKAGE_VERSION }}.zip" -d "./test" testVersion=$(./test/bw -v) echo "version: $_PACKAGE_VERSION" echo "testVersion: $testVersion" @@ -158,22 +164,22 @@ jobs: - name: Create checksums Unix run: | cd ./dist - shasum -a 256 bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-${{ env._PACKAGE_VERSION }}.zip \ + shasum -a 256 bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}${{ matrix.os.target_suffix }}-${{ env._PACKAGE_VERSION }}.zip \ | awk '{split($0, a); print a[1]}' > bw${{ - matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-sha256-${{ env._PACKAGE_VERSION }}.txt + matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}${{ matrix.os.target_suffix }}-sha256-${{ env._PACKAGE_VERSION }}.txt - name: Upload unix zip asset - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: - name: bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-${{ env._PACKAGE_VERSION }}.zip - path: apps/cli/dist/bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-${{ env._PACKAGE_VERSION }}.zip + name: bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}${{ matrix.os.target_suffix }}-${{ env._PACKAGE_VERSION }}.zip + path: apps/cli/dist/bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}${{ matrix.os.target_suffix }}-${{ env._PACKAGE_VERSION }}.zip if-no-files-found: error - name: Upload unix checksum asset - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: - name: bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-sha256-${{ env._PACKAGE_VERSION }}.txt - path: apps/cli/dist/bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-sha256-${{ env._PACKAGE_VERSION }}.txt + name: bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}${{ matrix.os.target_suffix }}-sha256-${{ env._PACKAGE_VERSION }}.txt + path: apps/cli/dist/bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}${{ matrix.os.target_suffix }}-sha256-${{ env._PACKAGE_VERSION }}.txt if-no-files-found: error cli-windows: @@ -199,6 +205,9 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha }} + - name: Install AST + run: dotnet tool install --global AzureSignTool --version 4.0.1 + - name: Setup Windows builder run: | choco install checksum --no-progress @@ -267,12 +276,30 @@ jobs: ResourceHacker -open version-info.rc -save version-info.res -action compile ResourceHacker -open %WIN_PKG_BUILT% -save %WIN_PKG_BUILT% -action addoverwrite -resource version-info.res + - name: Login to Azure + if: ${{ needs.setup.outputs.has_secrets == 'true' }} + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 + with: + creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + + - name: Retrieve secrets + id: retrieve-secrets + if: ${{ needs.setup.outputs.has_secrets == 'true' }} + uses: bitwarden/gh-actions/get-keyvault-secrets@main + with: + keyvault: "bitwarden-ci" + secrets: "code-signing-vault-url, + code-signing-client-id, + code-signing-tenant-id, + code-signing-client-secret, + code-signing-cert-name" + - name: Install run: npm ci working-directory: ./ - name: Download SDK Artifacts - if: ${{ inputs.sdk_branch != '' }} + if: ${{ inputs.sdk_branch != '' && needs.setup.outputs.has_secrets == 'true' }} uses: bitwarden/gh-actions/download-artifacts@main with: github_token: ${{secrets.GITHUB_TOKEN}} @@ -285,7 +312,7 @@ jobs: if_no_artifact_found: fail - name: Override SDK - if: ${{ inputs.sdk_branch != '' }} + if: ${{ inputs.sdk_branch != '' && needs.setup.outputs.has_secrets == 'true' }} working-directory: ./ run: | ls -l ../ @@ -294,6 +321,18 @@ jobs: - name: Build & Package Windows run: npm run dist:${{ matrix.license_type.build_prefix }}:win --quiet + - name: Sign executable + if: ${{ needs.setup.outputs.has_secrets == 'true' }} + shell: pwsh + env: + SIGNING_VAULT_URL: ${{ steps.retrieve-secrets.outputs.code-signing-vault-url }} + SIGNING_CLIENT_ID: ${{ steps.retrieve-secrets.outputs.code-signing-client-id }} + SIGNING_TENANT_ID: ${{ steps.retrieve-secrets.outputs.code-signing-tenant-id }} + SIGNING_CLIENT_SECRET: ${{ steps.retrieve-secrets.outputs.code-signing-client-secret }} + SIGNING_CERT_NAME: ${{ steps.retrieve-secrets.outputs.code-signing-cert-name }} + EXE_PATH: dist/${{ matrix.license_type.build_prefix }}/windows/bw.exe + run: . .\scripts\sign-cli.ps1 + - name: Package Chocolatey shell: pwsh if: ${{ matrix.license_type.build_prefix == 'bit' }} @@ -324,14 +363,14 @@ jobs: -t sha256 | Out-File -Encoding ASCII ./dist/bw${{ matrix.license_type.artifact_prefix }}-windows-sha256-${env:_PACKAGE_VERSION}.txt - name: Upload windows zip asset - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bw${{ matrix.license_type.artifact_prefix }}-windows-${{ env._PACKAGE_VERSION }}.zip path: apps/cli/dist/bw${{ matrix.license_type.artifact_prefix }}-windows-${{ env._PACKAGE_VERSION }}.zip if-no-files-found: error - name: Upload windows checksum asset - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bw${{ matrix.license_type.artifact_prefix }}-windows-sha256-${{ env._PACKAGE_VERSION }}.txt path: apps/cli/dist/bw${{ matrix.license_type.artifact_prefix }}-windows-sha256-${{ env._PACKAGE_VERSION }}.txt @@ -339,7 +378,7 @@ jobs: - name: Upload Chocolatey asset if: matrix.license_type.build_prefix == 'bit' - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bitwarden-cli.${{ env._PACKAGE_VERSION }}.nupkg path: apps/cli/dist/chocolatey/bitwarden-cli.${{ env._PACKAGE_VERSION }}.nupkg @@ -350,7 +389,7 @@ jobs: - name: Upload NPM Build Directory asset if: matrix.license_type.build_prefix == 'bit' - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bitwarden-cli-${{ env._PACKAGE_VERSION }}-npm-build.zip path: apps/cli/bitwarden-cli-${{ env._PACKAGE_VERSION }}-npm-build.zip @@ -421,14 +460,14 @@ jobs: run: sudo snap remove bw - name: Upload snap asset - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bw_${{ env._PACKAGE_VERSION }}_amd64.snap path: apps/cli/dist/snap/bw_${{ env._PACKAGE_VERSION }}_amd64.snap if-no-files-found: error - name: Upload snap checksum asset - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bw-snap-sha256-${{ env._PACKAGE_VERSION }}.txt path: apps/cli/dist/snap/bw-snap-sha256-${{ env._PACKAGE_VERSION }}.txt diff --git a/.github/workflows/build-desktop-target.yml b/.github/workflows/build-desktop-target.yml new file mode 100644 index 00000000000..8c26f991174 --- /dev/null +++ b/.github/workflows/build-desktop-target.yml @@ -0,0 +1,32 @@ +name: Build Desktop on PR Target + +on: + pull_request: + types: [opened, synchronize] + branches-ignore: + - 'l10n_master' + - 'cf-pages' + paths: + - 'apps/desktop/**' + - 'libs/**' + - '*' + - '!*.md' + - '!*.txt' + - '.github/workflows/build-desktop.yml' + +defaults: + run: + shell: bash + +jobs: + check-run: + name: Check PR run + uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main + + run-workflow: + name: Run Build Desktop on PR Target + needs: check-run + if: ${{ github.event.pull_request.head.repo.full_name != github.repository }} + uses: ./.github/workflows/build-desktop.yml + secrets: inherit + diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index b27d1486bd2..48ecca540e8 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -1,7 +1,7 @@ name: Build Desktop on: - pull_request_target: + pull_request: types: [opened, synchronize] branches-ignore: - 'l10n_master' @@ -25,6 +25,8 @@ on: - '!*.md' - '!*.txt' - '.github/workflows/build-desktop.yml' + workflow_call: + inputs: {} workflow_dispatch: inputs: sdk_branch: @@ -37,15 +39,9 @@ defaults: shell: bash jobs: - check-run: - name: Check PR run - uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main - electron-verify: name: Verify Electron Version runs-on: ubuntu-22.04 - needs: - - check-run steps: - name: Check out repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -67,8 +63,6 @@ jobs: setup: name: Setup runs-on: ubuntu-22.04 - needs: - - check-run outputs: package_version: ${{ steps.retrieve-version.outputs.package_version }} release_channel: ${{ steps.release-channel.outputs.channel }} @@ -76,6 +70,7 @@ jobs: rc_branch_exists: ${{ steps.branch-check.outputs.rc_branch_exists }} hotfix_branch_exists: ${{ steps.branch-check.outputs.hotfix_branch_exists }} node_version: ${{ steps.retrieve-node-version.outputs.node_version }} + has_secrets: ${{ steps.check-secrets.outputs.has_secrets }} defaults: run: working-directory: apps/desktop @@ -138,14 +133,22 @@ jobs: NODE_VERSION=${NODE_NVMRC/v/''} echo "node_version=$NODE_VERSION" >> $GITHUB_OUTPUT + - name: Check secrets + id: check-secrets + env: + AZURE_KV_CI_SERVICE_PRINCIPAL: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + run: | + has_secrets=${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL != '' }} + echo "has_secrets=$has_secrets" >> $GITHUB_OUTPUT + linux: name: Linux Build # Note, before updating the ubuntu version of the workflow, ensure the snap base image # is equal or greater than the new version. Otherwise there might be GLIBC version issues. # The snap base for desktop is defined in `apps/desktop/electron-builder.json` - # We are currently running on 20.04 until the Ubuntu 24.04 release is available, as moving - # to 22.04 now breaks users who are on 20.04 due to mismatched GLIBC versions. - runs-on: ubuntu-20.04 + # We intentionally keep this runner on the oldest supported OS in GitHub Actions + # for maximum compatibility across GLIBC versions + runs-on: ubuntu-22.04 needs: setup env: _PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }} @@ -232,42 +235,42 @@ jobs: run: npm run dist:lin - name: Upload .deb artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-amd64.deb path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-amd64.deb if-no-files-found: error - name: Upload .rpm artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.rpm path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.rpm if-no-files-found: error - name: Upload .freebsd artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64.freebsd path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64.freebsd if-no-files-found: error - name: Upload .snap artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bitwarden_${{ env._PACKAGE_VERSION }}_amd64.snap path: apps/desktop/dist/bitwarden_${{ env._PACKAGE_VERSION }}_amd64.snap if-no-files-found: error - name: Upload .AppImage artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.AppImage path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.AppImage if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: ${{ needs.setup.outputs.release_channel }}-linux.yml path: apps/desktop/dist/${{ needs.setup.outputs.release_channel }}-linux.yml @@ -280,7 +283,7 @@ jobs: sudo npm run pack:lin:flatpak - name: Upload flatpak artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: com.bitwarden.desktop.flatpak path: apps/desktop/dist/com.bitwarden.desktop.flatpak @@ -333,12 +336,14 @@ jobs: rustup show - name: Login to Azure + if: ${{ needs.setup.outputs.has_secrets == 'true' }} uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} - name: Retrieve secrets id: retrieve-secrets + if: ${{ needs.setup.outputs.has_secrets == 'true' }} uses: bitwarden/gh-actions/get-keyvault-secrets@main with: keyvault: "bitwarden-ci" @@ -353,7 +358,7 @@ jobs: working-directory: ./ - name: Download SDK Artifacts - if: ${{ inputs.sdk_branch != '' }} + if: ${{ inputs.sdk_branch != '' && needs.setup.outputs.has_secrets == 'true' }} uses: bitwarden/gh-actions/download-artifacts@main with: github_token: ${{secrets.GITHUB_TOKEN}} @@ -366,7 +371,7 @@ jobs: if_no_artifact_found: fail - name: Override SDK - if: ${{ inputs.sdk_branch != '' }} + if: ${{ inputs.sdk_branch != '' && needs.setup.outputs.has_secrets == 'true' }} working-directory: ./ run: | ls -l ../ @@ -386,7 +391,16 @@ jobs: working-directory: apps/desktop/desktop_native run: node build.js cross-platform - - name: Build & Sign (dev) + - name: Build + run: npm run build + + - name: Pack + if: ${{ needs.setup.outputs.has_secrets == 'false' }} + run: | + npm run pack:win + + - name: Pack & Sign (dev) + if: ${{ needs.setup.outputs.has_secrets == 'true' }} env: ELECTRON_BUILDER_SIGN: 1 SIGNING_VAULT_URL: ${{ steps.retrieve-secrets.outputs.code-signing-vault-url }} @@ -395,10 +409,10 @@ jobs: SIGNING_CLIENT_SECRET: ${{ steps.retrieve-secrets.outputs.code-signing-client-secret }} SIGNING_CERT_NAME: ${{ steps.retrieve-secrets.outputs.code-signing-cert-name }} run: | - npm run build npm run pack:win - name: Rename appx files for store + if: ${{ needs.setup.outputs.has_secrets == 'true' }} run: | Copy-Item "./dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.appx" ` -Destination "./dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32-store.appx" @@ -408,6 +422,7 @@ jobs: -Destination "./dist/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64-store.appx" - name: Package for Chocolatey + if: ${{ needs.setup.outputs.has_secrets == 'true' }} run: | Copy-Item -Path ./stores/chocolatey -Destination ./dist/chocolatey -Recurse Copy-Item -Path ./dist/nsis-web/Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe ` @@ -419,6 +434,7 @@ jobs: choco pack ./dist/chocolatey/bitwarden.nuspec --version "$env:_PACKAGE_VERSION" --out ./dist/chocolatey - name: Fix NSIS artifact names for auto-updater + if: ${{ needs.setup.outputs.has_secrets == 'true' }} run: | Rename-Item -Path .\dist\nsis-web\Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z ` -NewName bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z @@ -428,91 +444,103 @@ jobs: -NewName bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z - name: Upload portable exe artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-Portable-${{ env._PACKAGE_VERSION }}.exe path: apps/desktop/dist/Bitwarden-Portable-${{ env._PACKAGE_VERSION }}.exe if-no-files-found: error - name: Upload installer exe artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + if: ${{ needs.setup.outputs.has_secrets == 'true' }} + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe path: apps/desktop/dist/nsis-web/Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe if-no-files-found: error - name: Upload appx ia32 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + if: ${{ needs.setup.outputs.has_secrets == 'true' }} + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.appx if-no-files-found: error - name: Upload store appx ia32 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + if: ${{ needs.setup.outputs.has_secrets == 'true' }} + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-ia32-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32-store.appx if-no-files-found: error - name: Upload NSIS ia32 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + if: ${{ needs.setup.outputs.has_secrets == 'true' }} + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z if-no-files-found: error - name: Upload appx x64 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + if: ${{ needs.setup.outputs.has_secrets == 'true' }} + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64.appx if-no-files-found: error - name: Upload store appx x64 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + if: ${{ needs.setup.outputs.has_secrets == 'true' }} + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64-store.appx if-no-files-found: error - name: Upload NSIS x64 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + if: ${{ needs.setup.outputs.has_secrets == 'true' }} + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-x64.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-x64.nsis.7z if-no-files-found: error - name: Upload appx ARM64 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + if: ${{ needs.setup.outputs.has_secrets == 'true' }} + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.appx if-no-files-found: error - name: Upload store appx ARM64 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + if: ${{ needs.setup.outputs.has_secrets == 'true' }} + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-arm64-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64-store.appx if-no-files-found: error - name: Upload NSIS ARM64 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + if: ${{ needs.setup.outputs.has_secrets == 'true' }} + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z if-no-files-found: error - name: Upload nupkg artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + if: ${{ needs.setup.outputs.has_secrets == 'true' }} + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bitwarden.${{ env._PACKAGE_VERSION }}.nupkg path: apps/desktop/dist/chocolatey/bitwarden.${{ env._PACKAGE_VERSION }}.nupkg if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + if: ${{ needs.setup.outputs.has_secrets == 'true' }} + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: ${{ needs.setup.outputs.release_channel }}.yml path: apps/desktop/dist/nsis-web/${{ needs.setup.outputs.release_channel }}.yml @@ -574,11 +602,13 @@ jobs: key: ${{ runner.os }}-${{ github.run_id }}-safari-extension - name: Login to Azure + if: ${{ needs.setup.outputs.has_secrets == 'true' }} uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} - name: Download Provisioning Profiles secrets + if: ${{ needs.setup.outputs.has_secrets == 'true' }} env: ACCOUNT_NAME: bitwardenci CONTAINER_NAME: profiles @@ -591,6 +621,7 @@ jobs: --output none - name: Get certificates + if: ${{ needs.setup.outputs.has_secrets == 'true' }} run: | mkdir -p $HOME/certificates @@ -613,6 +644,7 @@ jobs: jq -r .value | base64 -d > $HOME/certificates/macdev-cert.p12 - name: Set up keychain + if: ${{ needs.setup.outputs.has_secrets == 'true' }} env: KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} run: | @@ -642,6 +674,7 @@ jobs: security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain - name: Set up provisioning profiles + if: ${{ needs.setup.outputs.has_secrets == 'true' }} run: | cp $HOME/secrets/bitwarden_desktop_appstore.provisionprofile \ $GITHUB_WORKSPACE/apps/desktop/bitwarden_desktop_appstore.provisionprofile @@ -661,7 +694,7 @@ jobs: working-directory: ./ - name: Download SDK Artifacts - if: ${{ inputs.sdk_branch != '' }} + if: ${{ inputs.sdk_branch != '' && needs.setup.outputs.has_secrets == 'true' }} uses: bitwarden/gh-actions/download-artifacts@main with: github_token: ${{secrets.GITHUB_TOKEN}} @@ -674,7 +707,7 @@ jobs: if_no_artifact_found: fail - name: Override SDK - if: ${{ inputs.sdk_branch != '' }} + if: ${{ inputs.sdk_branch != '' && needs.setup.outputs.has_secrets == 'true' }} working-directory: ./ run: | ls -l ../ @@ -701,6 +734,7 @@ jobs: browser-build: name: Browser Build needs: setup + if: ${{ needs.setup.outputs.has_secrets == 'true' }} uses: ./.github/workflows/build-browser.yml secrets: inherit @@ -708,6 +742,7 @@ jobs: macos-package-github: name: MacOS Package GitHub Release Assets runs-on: macos-13 + if: ${{ needs.setup.outputs.has_secrets == 'true' }} needs: - browser-build - macos-build @@ -918,28 +953,28 @@ jobs: run: npm run pack:mac - name: Upload .zip artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal-mac.zip path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal-mac.zip if-no-files-found: error - name: Upload .dmg artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg if-no-files-found: error - name: Upload .dmg blockmap artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg.blockmap path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg.blockmap if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: ${{ needs.setup.outputs.release_channel }}-mac.yml path: apps/desktop/dist/${{ needs.setup.outputs.release_channel }}-mac.yml @@ -949,6 +984,7 @@ jobs: macos-package-mas: name: MacOS Package Prod Release Asset runs-on: macos-13 + if: ${{ needs.setup.outputs.has_secrets == 'true' }} needs: - browser-build - macos-build @@ -1166,7 +1202,7 @@ jobs: run: npm run pack:mac:mas - name: Upload .pkg artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.pkg path: apps/desktop/dist/mas-universal/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.pkg @@ -1217,6 +1253,7 @@ jobs: macos-package-dev: name: MacOS Package Dev Release Asset runs-on: macos-13 + if: ${{ needs.setup.outputs.has_secrets == 'true' }} needs: - browser-build - macos-build @@ -1425,7 +1462,7 @@ jobs: zip -r Bitwarden-${{ env._PACKAGE_VERSION }}-masdev-universal.zip Bitwarden.app - name: Upload masdev artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-masdev-universal.zip path: apps/desktop/dist/mas-dev-universal/Bitwarden-${{ env._PACKAGE_VERSION }}-masdev-universal.zip diff --git a/.github/workflows/build-web-target.yml b/.github/workflows/build-web-target.yml new file mode 100644 index 00000000000..fb7074292b5 --- /dev/null +++ b/.github/workflows/build-web-target.yml @@ -0,0 +1,32 @@ +name: Build Web on PR Target + +on: + pull_request: + types: [opened, synchronize] + branches-ignore: + - 'l10n_master' + - 'cf-pages' + paths: + - 'apps/web/**' + - 'libs/**' + - '*' + - '!*.md' + - '!*.txt' + - '.github/workflows/build-web.yml' + +defaults: + run: + shell: bash + +jobs: + check-run: + name: Check PR run + uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main + + run-workflow: + name: Run Build Web on PR Target + needs: check-run + if: ${{ github.event.pull_request.head.repo.full_name != github.repository }} + uses: ./.github/workflows/build-web.yml + secrets: inherit + diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml index 73ae0e14962..e91fba2e87a 100644 --- a/.github/workflows/build-web.yml +++ b/.github/workflows/build-web.yml @@ -1,7 +1,7 @@ name: Build Web on: - pull_request_target: + pull_request: types: [opened, synchronize] branches-ignore: - 'l10n_master' @@ -27,6 +27,8 @@ on: - '.github/workflows/build-web.yml' release: types: [published] + workflow_call: + inputs: {} workflow_dispatch: inputs: custom_tag_extension: @@ -41,18 +43,13 @@ env: _AZ_REGISTRY: bitwardenprod.azurecr.io jobs: - check-run: - name: Check PR run - uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main - setup: name: Setup runs-on: ubuntu-22.04 - needs: - - check-run outputs: version: ${{ steps.version.outputs.value }} node_version: ${{ steps.retrieve-node-version.outputs.node_version }} + has_secrets: ${{ steps.check-secrets.outputs.has_secrets }} steps: - name: Check out repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -70,6 +67,14 @@ jobs: NODE_VERSION=${NODE_NVMRC/v/''} echo "node_version=$NODE_VERSION" >> $GITHUB_OUTPUT + - name: Check secrets + id: check-secrets + env: + AZURE_KV_CI_SERVICE_PRINCIPAL: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + run: | + has_secrets=${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL != '' }} + echo "has_secrets=$has_secrets" >> $GITHUB_OUTPUT + build-artifacts: name: Build artifacts runs-on: ubuntu-22.04 @@ -128,7 +133,7 @@ jobs: run: npm ci - name: Download SDK Artifacts - if: ${{ inputs.sdk_branch != '' }} + if: ${{ inputs.sdk_branch != '' && needs.setup.outputs.has_secrets == 'true' }} uses: bitwarden/gh-actions/download-artifacts@main with: github_token: ${{secrets.GITHUB_TOKEN}} @@ -141,7 +146,7 @@ jobs: if_no_artifact_found: fail - name: Override SDK - if: ${{ inputs.sdk_branch != '' }} + if: ${{ inputs.sdk_branch != '' && needs.setup.outputs.has_secrets == 'true' }} working-directory: ./ run: | ls -l ../ @@ -164,7 +169,7 @@ jobs: run: zip -r web-${{ env._VERSION }}-${{ matrix.name }}.zip build - name: Upload ${{ matrix.name }} artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: web-${{ env._VERSION }}-${{ matrix.name }}.zip path: apps/web/web-${{ env._VERSION }}-${{ matrix.name }}.zip @@ -213,19 +218,23 @@ jobs: ########## ACRs ########## - name: Login to Prod Azure + if: ${{ needs.setup.outputs.has_secrets == 'true' }} uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 with: creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} - name: Log into Prod container registry + if: ${{ needs.setup.outputs.has_secrets == 'true' }} run: az acr login -n bitwardenprod - name: Login to Azure - CI Subscription + if: ${{ needs.setup.outputs.has_secrets == 'true' }} uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} - name: Retrieve github PAT secrets + if: ${{ needs.setup.outputs.has_secrets == 'true' }} id: retrieve-secret-pat uses: bitwarden/gh-actions/get-keyvault-secrets@main with: @@ -242,7 +251,7 @@ jobs: - name: Generate Docker image tag id: tag run: | - if [[ "${GITHUB_EVENT_NAME}" == "pull_request_target" ]]; then + if [[ "${GITHUB_EVENT_NAME}" == "pull_request" ]]; then IMAGE_TAG=$(echo "${GITHUB_HEAD_REF}" | sed "s#/#-#g") else IMAGE_TAG=$(echo "${GITHUB_REF_NAME}" | sed "s#/#-#g") @@ -273,8 +282,9 @@ jobs: run: echo "name=$_AZ_REGISTRY/${PROJECT_NAME}:${IMAGE_TAG}" >> $GITHUB_OUTPUT - name: Build Docker image + if: ${{ needs.setup.outputs.has_secrets == 'true' }} id: build-docker - uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0 + uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6.12.0 with: context: apps/web file: apps/web/Dockerfile @@ -283,7 +293,7 @@ jobs: tags: ${{ steps.image-name.outputs.name }} secrets: | "GH_PAT=${{ steps.retrieve-secret-pat.outputs.github-pat-bitwarden-devops-bot-repo-scope }}" - + - name: Install Cosign if: github.event_name != 'pull_request_target' && github.ref == 'refs/heads/main' uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0 @@ -302,6 +312,7 @@ jobs: cosign sign --yes ${images} - name: Scan Docker image + if: ${{ needs.setup.outputs.has_secrets == 'true' }} id: container-scan uses: anchore/scan-action@869c549e657a088dc0441b08ce4fc0ecdac2bb65 # v5.3.0 with: @@ -310,9 +321,12 @@ jobs: output-format: sarif - name: Upload Grype results to GitHub - uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + if: ${{ needs.setup.outputs.has_secrets == 'true' }} + uses: github/codeql-action/upload-sarif@d68b2d4edb4189fd2a5366ac14e72027bd4b37dd # v3.28.2 with: sarif_file: ${{ steps.container-scan.outputs.sarif }} + sha: ${{ contains(github.event_name, 'pull_request') && github.event.pull_request.head.sha || github.sha }} + ref: ${{ contains(github.event_name, 'pull_request') && format('refs/pull/{0}/head', github.event.pull_request.number) || github.ref }} - name: Log out of Docker run: docker logout diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml index a5ebd363f63..75a07431942 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -56,7 +56,7 @@ jobs: run: npm run build-storybook:ci - name: Publish to Chromatic - uses: chromaui/action@64a9c0ca3bfb724389b0d536e544f56b7b5ff5b3 # v11.20.2 + uses: chromaui/action@8a12962215a66cd05b1ac5b0f1c08768d1aab155 # v11.25.0 with: token: ${{ secrets.GITHUB_TOKEN }} projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 867de3844e7..4fbef027c7c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,12 +1,20 @@ name: Lint on: - push: + pull_request: + types: [opened, synchronize] branches-ignore: - 'l10n_master' - 'cf-pages' paths-ignore: - '.github/workflows/**' + push: + branches: + - 'main' + - 'rc' + - 'hotfix-rc-*' + paths-ignore: + - '.github/workflows/**' workflow_dispatch: inputs: {} @@ -34,6 +42,7 @@ jobs: ! -path "*/.DS_Store" \ ! -path "*/*locales/*" \ ! -path "./.github/*" \ + ! -path "*/README.md" \ ! -path "*/Cargo.toml" \ ! -path "*/Cargo.lock" \ ! -path "./apps/desktop/macos/*" \ diff --git a/.github/workflows/publish-cli.yml b/.github/workflows/publish-cli.yml index ff85a30d3f6..d758e6f11c9 100644 --- a/.github/workflows/publish-cli.yml +++ b/.github/workflows/publish-cli.yml @@ -222,7 +222,7 @@ jobs: with: token: '${{ secrets.GITHUB_TOKEN }}' state: 'success' - deployment_id: ${{ needs.setup.outputs.deployment_id }} + deployment-id: ${{ needs.setup.outputs.deployment_id }} - name: Update deployment status to Failure if: ${{ inputs.publish_type != 'Dry Run' && failure() }} @@ -230,4 +230,4 @@ jobs: with: token: '${{ secrets.GITHUB_TOKEN }}' state: 'failure' - deployment_id: ${{ needs.setup.outputs.deployment_id }} + deployment-id: ${{ needs.setup.outputs.deployment_id }} diff --git a/.github/workflows/publish-desktop.yml b/.github/workflows/publish-desktop.yml index 69ccd841065..ae631165db9 100644 --- a/.github/workflows/publish-desktop.yml +++ b/.github/workflows/publish-desktop.yml @@ -7,19 +7,18 @@ on: publish_type: description: 'Publish Options' required: true - default: 'Initial Publish' + default: 'Publish' type: choice options: - - Initial Publish - - Republish + - Publish - Dry Run version: description: 'Version to publish (default: latest desktop release)' required: true type: string default: latest - rollout_percentage: - description: 'Staged Rollout Percentage' + electron_rollout_percentage: + description: 'Staged Rollout Percentage for Electron' required: true default: '10' type: string @@ -137,7 +136,7 @@ jobs: - name: Set staged rollout percentage env: RELEASE_CHANNEL: ${{ needs.setup.outputs.release_channel }} - ROLLOUT_PCT: ${{ inputs.rollout_percentage }} + ROLLOUT_PCT: ${{ inputs.electron_rollout_percentage }} run: | echo "stagingPercentage: ${ROLLOUT_PCT}" >> apps/desktop/artifacts/${RELEASE_CHANNEL}.yml echo "stagingPercentage: ${ROLLOUT_PCT}" >> apps/desktop/artifacts/${RELEASE_CHANNEL}-linux.yml @@ -163,7 +162,7 @@ jobs: with: token: '${{ secrets.GITHUB_TOKEN }}' state: 'success' - deployment_id: ${{ needs.setup.outputs.deployment_id }} + deployment-id: ${{ needs.setup.outputs.deployment_id }} - name: Update deployment status to Failure if: ${{ inputs.publish_type != 'Dry Run' && failure() }} @@ -171,7 +170,7 @@ jobs: with: token: '${{ secrets.GITHUB_TOKEN }}' state: 'failure' - deployment_id: ${{ needs.setup.outputs.deployment_id }} + deployment-id: ${{ needs.setup.outputs.deployment_id }} snap: name: Deploy Snap @@ -284,7 +283,7 @@ jobs: with: token: '${{ secrets.GITHUB_TOKEN }}' state: 'success' - deployment_id: ${{ needs.setup.outputs.deployment_id }} + deployment-id: ${{ needs.setup.outputs.deployment_id }} - name: Update deployment status to Failure if: ${{ inputs.publish_type != 'Dry Run' && failure() }} @@ -292,4 +291,4 @@ jobs: with: token: '${{ secrets.GITHUB_TOKEN }}' state: 'failure' - deployment_id: ${{ needs.setup.outputs.deployment_id }} + deployment-id: ${{ needs.setup.outputs.deployment_id }} diff --git a/.github/workflows/release-browser.yml b/.github/workflows/release-browser.yml index 7e8722dc79f..498f8748959 100644 --- a/.github/workflows/release-browser.yml +++ b/.github/workflows/release-browser.yml @@ -40,7 +40,7 @@ jobs: - name: Check Release Version id: version - uses: bitwarden/gh-actions/release_version-check@main + uses: bitwarden/gh-actions/release-version-check@main with: release-type: ${{ github.event.inputs.release_type }} project-type: ts @@ -128,7 +128,7 @@ jobs: - name: Create release if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 + uses: ncipollo/release-action@cdcc88a9acf3ca41c16c37bb7d21b9ad48560d87 # v1.15.0 with: artifacts: 'browser-source-${{ needs.setup.outputs.release_version }}.zip, dist-chrome-${{ needs.setup.outputs.release_version }}.zip, diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index d16cd744d7d..519fee1989b 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -40,7 +40,7 @@ jobs: - name: Check Release Version id: version - uses: bitwarden/gh-actions/release_version-check@main + uses: bitwarden/gh-actions/release-version-check@main with: release-type: ${{ inputs.release_type }} project-type: ts @@ -73,7 +73,7 @@ jobs: - name: Create release if: ${{ inputs.release_type != 'Dry Run' }} - uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 + uses: ncipollo/release-action@cdcc88a9acf3ca41c16c37bb7d21b9ad48560d87 # v1.15.0 env: PKG_VERSION: ${{ needs.setup.outputs.release_version }} with: @@ -83,8 +83,12 @@ jobs: apps/cli/bw-windows-sha256-${{ env.PKG_VERSION }}.txt, apps/cli/bw-oss-macos-${{ env.PKG_VERSION }}.zip, apps/cli/bw-oss-macos-sha256-${{ env.PKG_VERSION }}.txt, + apps/cli/bw-oss-macos-arm64-${{ env.PKG_VERSION }}.zip, + apps/cli/bw-oss-macos-arm64-sha256-${{ env.PKG_VERSION }}.txt, apps/cli/bw-macos-${{ env.PKG_VERSION }}.zip, apps/cli/bw-macos-sha256-${{ env.PKG_VERSION }}.txt, + apps/cli/bw-macos-arm64-${{ env.PKG_VERSION }}.zip, + apps/cli/bw-macos-arm64-sha256-${{ env.PKG_VERSION }}.txt, apps/cli/bw-oss-linux-${{ env.PKG_VERSION }}.zip, apps/cli/bw-oss-linux-sha256-${{ env.PKG_VERSION }}.txt, apps/cli/bw-linux-${{ env.PKG_VERSION }}.zip, diff --git a/.github/workflows/release-desktop-beta.yml b/.github/workflows/release-desktop-beta.yml index 08174dc552e..a5e374395d8 100644 --- a/.github/workflows/release-desktop-beta.yml +++ b/.github/workflows/release-desktop-beta.yml @@ -47,7 +47,7 @@ jobs: - name: Check Release Version id: version - uses: bitwarden/gh-actions/release_version-check@main + uses: bitwarden/gh-actions/release-version-check@main with: release-type: 'Initial Release' project-type: ts @@ -158,42 +158,42 @@ jobs: run: npm run dist:lin - name: Upload .deb artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-amd64.deb path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-amd64.deb if-no-files-found: error - name: Upload .rpm artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.rpm path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.rpm if-no-files-found: error - name: Upload .freebsd artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64.freebsd path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64.freebsd if-no-files-found: error - name: Upload .snap artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bitwarden_${{ env._PACKAGE_VERSION }}_amd64.snap path: apps/desktop/dist/bitwarden_${{ env._PACKAGE_VERSION }}_amd64.snap if-no-files-found: error - name: Upload .AppImage artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.AppImage path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.AppImage if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: ${{ needs.setup.outputs.release_channel }}-linux.yml path: apps/desktop/dist/${{ needs.setup.outputs.release_channel }}-linux.yml @@ -299,91 +299,91 @@ jobs: -NewName bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z - name: Upload portable exe artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-Portable-${{ env._PACKAGE_VERSION }}.exe path: apps/desktop/dist/Bitwarden-Portable-${{ env._PACKAGE_VERSION }}.exe if-no-files-found: error - name: Upload installer exe artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe path: apps/desktop/dist/nsis-web/Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe if-no-files-found: error - name: Upload appx ia32 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.appx if-no-files-found: error - name: Upload store appx ia32 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-ia32-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32-store.appx if-no-files-found: error - name: Upload NSIS ia32 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z if-no-files-found: error - name: Upload appx x64 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64.appx if-no-files-found: error - name: Upload store appx x64 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64-store.appx if-no-files-found: error - name: Upload NSIS x64 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-x64.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-x64.nsis.7z if-no-files-found: error - name: Upload appx ARM64 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.appx if-no-files-found: error - name: Upload store appx ARM64 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-arm64-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64-store.appx if-no-files-found: error - name: Upload NSIS ARM64 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z if-no-files-found: error - name: Upload nupkg artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bitwarden.${{ env._PACKAGE_VERSION }}.nupkg path: apps/desktop/dist/chocolatey/bitwarden.${{ env._PACKAGE_VERSION }}.nupkg if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: ${{ needs.setup.outputs.release_channel }}.yml path: apps/desktop/dist/nsis-web/${{ needs.setup.outputs.release_channel }}.yml @@ -707,28 +707,28 @@ jobs: run: npm run pack:mac - name: Upload .zip artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal-mac.zip path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal-mac.zip if-no-files-found: error - name: Upload .dmg artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg if-no-files-found: error - name: Upload .dmg blockmap artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg.blockmap path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg.blockmap if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: ${{ needs.setup.outputs.release_channel }}-mac.yml path: apps/desktop/dist/${{ needs.setup.outputs.release_channel }}-mac.yml @@ -915,7 +915,7 @@ jobs: APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} - name: Upload .pkg artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.pkg path: apps/desktop/dist/mas-universal/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.pkg diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index ba934235b44..57143747a86 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -40,7 +40,7 @@ jobs: - name: Check Release Version id: version - uses: bitwarden/gh-actions/release_version-check@main + uses: bitwarden/gh-actions/release-version-check@main with: release-type: ${{ inputs.release_type }} project-type: ts @@ -96,7 +96,7 @@ jobs: file_path: "apps/desktop/artifacts/sha256-checksums.txt" - name: Create Release - uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 + uses: ncipollo/release-action@cdcc88a9acf3ca41c16c37bb7d21b9ad48560d87 # v1.15.0 if: ${{ steps.release_channel.outputs.channel == 'latest' && github.event.inputs.release_type != 'Dry Run' }} env: PKG_VERSION: ${{ steps.version.outputs.version }} diff --git a/.github/workflows/release-web.yml b/.github/workflows/release-web.yml index faa398f6d67..0301b814796 100644 --- a/.github/workflows/release-web.yml +++ b/.github/workflows/release-web.yml @@ -81,7 +81,7 @@ jobs: - name: Create release if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 + uses: ncipollo/release-action@cdcc88a9acf3ca41c16c37bb7d21b9ad48560d87 # v1.15.0 with: name: "Web v${{ needs.setup.outputs.release_version }}" commit: ${{ github.sha }} diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml index b0874b38cbf..77b66ba8bf1 100644 --- a/.github/workflows/scan.yml +++ b/.github/workflows/scan.yml @@ -46,9 +46,11 @@ jobs: --output-path . ${{ env.INCREMENTAL }} - name: Upload Checkmarx results to GitHub - uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + uses: github/codeql-action/upload-sarif@d68b2d4edb4189fd2a5366ac14e72027bd4b37dd # v3.28.2 with: sarif_file: cx_result.sarif + sha: ${{ contains(github.event_name, 'pull_request') && github.event.pull_request.head.sha || github.sha }} + ref: ${{ contains(github.event_name, 'pull_request') && format('refs/pull/{0}/head', github.event.pull_request.number) || github.ref }} quality: name: Quality scan @@ -66,10 +68,9 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: Scan with SonarCloud - uses: sonarsource/sonarcloud-github-action@02ef91109b2d589e757aefcfb2854c2783fd7b19 # v4.0.0 + uses: sonarsource/sonarqube-scan-action@bfd4e558cda28cda6b5defafb9232d191be8c203 # v4.2.1 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: args: > -Dsonar.organization=${{ github.repository_owner }} @@ -78,3 +79,4 @@ jobs: -Dsonar.sources=. -Dsonar.test.inclusions=**/*.spec.ts -Dsonar.exclusions=**/*.spec.ts + -Dsonar.pullrequest.key=${{ github.event.pull_request.number }} diff --git a/.github/workflows/staged-rollout-desktop.yml b/.github/workflows/staged-rollout-desktop.yml index 91250a443f2..4ec3af3be97 100644 --- a/.github/workflows/staged-rollout-desktop.yml +++ b/.github/workflows/staged-rollout-desktop.yml @@ -1,4 +1,5 @@ name: Staged Rollout Desktop +run-name: Staged Rollout Desktop - ${{ inputs.rollout_percentage }}% on: workflow_dispatch: diff --git a/.github/workflows/stale-bot.yml b/.github/workflows/stale-bot.yml index 6caa7b99331..abb292f53f3 100644 --- a/.github/workflows/stale-bot.yml +++ b/.github/workflows/stale-bot.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: 'Run stale action' - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0 + uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0 with: stale-issue-label: 'needs-reply' stale-pr-label: 'needs-changes' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 72bc3594beb..0b039315b30 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,28 +11,10 @@ on: types: [opened, synchronize] jobs: - check-test-secrets: - name: Check for test secrets - runs-on: ubuntu-22.04 - outputs: - available: ${{ steps.check-test-secrets.outputs.available }} - permissions: - contents: read - - steps: - - name: Check - id: check-test-secrets - run: | - if [ "${{ secrets.CODECOV_TOKEN }}" != '' ]; then - echo "available=true" >> $GITHUB_OUTPUT; - else - echo "available=false" >> $GITHUB_OUTPUT; - fi testing: name: Run tests runs-on: ubuntu-22.04 - needs: check-test-secrets permissions: checks: write contents: read @@ -77,7 +59,7 @@ jobs: - name: Report test results uses: dorny/test-reporter@31a54ee7ebcacc03a09ea97a7e5465a47b84aea5 # v1.9.1 - if: ${{ needs.check-test-secrets.outputs.available == 'true' && !cancelled() }} + if: ${{ github.event.pull_request.head.repo.full_name == github.repository && !cancelled() }} with: name: Test Results path: "junit.xml" @@ -85,14 +67,10 @@ jobs: fail-on-error: true - name: Upload coverage to codecov.io - uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0 - if: ${{ needs.check-test-secrets.outputs.available == 'true' }} - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5.1.2 - name: Upload results to codecov.io - uses: codecov/test-results-action@9739113ad922ea0a9abb4b2c0f8bf6a4aa8ef820 # v1.0.1 - if: ${{ needs.check-test-secrets.outputs.available == 'true' }} + uses: codecov/test-results-action@4e79e65778be1cecd5df25e14af1eafb6df80ea9 # v1.0.2 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} @@ -106,15 +84,15 @@ jobs: matrix: os: - ubuntu-22.04 - - macos-latest - - windows-latest + - macos-14 + - windows-2022 steps: - name: Check Rust version run: rustup --version - name: Install gnome-keyring - if: ${{ matrix.os=='ubuntu-latest' }} + if: ${{ matrix.os=='ubuntu-22.04' }} run: | sudo apt-get update sudo apt-get install -y gnome-keyring dbus-x11 @@ -127,7 +105,7 @@ jobs: run: cargo build - name: Test Ubuntu - if: ${{ matrix.os=='ubuntu-latest' }} + if: ${{ matrix.os=='ubuntu-22.04' }} working-directory: ./apps/desktop/desktop_native run: | eval "$(dbus-launch --sh-syntax)" @@ -138,11 +116,41 @@ jobs: cargo test -- --test-threads=1 - name: Test macOS - if: ${{ matrix.os=='macos-latest' }} + if: ${{ matrix.os=='macos-14' }} working-directory: ./apps/desktop/desktop_native run: cargo test -- --test-threads=1 - name: Test Windows - if: ${{ matrix.os=='windows-latest'}} + if: ${{ matrix.os=='windows-2022'}} working-directory: ./apps/desktop/desktop_native/core run: cargo test -- --test-threads=1 + + rust-coverage: + name: Rust Coverage + runs-on: macos-14 + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Install rust + uses: dtolnay/rust-toolchain@c5a29ddb4d9d194e7c84ec8c3fba61b1c31fee8c # stable + with: + toolchain: stable + components: llvm-tools + + - name: Cache cargo registry + uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2.7.5 + with: + workspaces: "apps/desktop/desktop_native -> target" + + - name: Install cargo-llvm-cov + run: cargo install cargo-llvm-cov --version 0.6.16 + + - name: Generate coverage + working-directory: ./apps/desktop/desktop_native + run: cargo llvm-cov --all-features --lcov --output-path lcov.info --workspace --no-cfg-coverage + + - name: Upload to codecov.io + uses: codecov/codecov-action@13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3 # v5.3.1 + with: + files: ./apps/desktop/desktop_native/lcov.info diff --git a/.storybook/main.ts b/.storybook/main.ts index b48a86ba2b2..9583d1fc6f2 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -1,7 +1,8 @@ import { dirname, join } from "path"; + import { StorybookConfig } from "@storybook/angular"; -import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin"; import remarkGfm from "remark-gfm"; +import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin"; const config: StorybookConfig = { stories: [ @@ -28,7 +29,10 @@ const config: StorybookConfig = { getAbsolutePath("@storybook/addon-a11y"), getAbsolutePath("@storybook/addon-designs"), getAbsolutePath("@storybook/addon-interactions"), + getAbsolutePath("@storybook/addon-themes"), { + // @storybook/addon-docs is part of @storybook/addon-essentials + // eslint-disable-next-line storybook/no-uninstalled-addons name: "@storybook/addon-docs", options: { mdxPluginOptions: { diff --git a/.storybook/manager.js b/.storybook/manager.js index 409f93ec505..e0ec04fd375 100644 --- a/.storybook/manager.js +++ b/.storybook/manager.js @@ -50,10 +50,14 @@ const darkTheme = create({ }); export const getPreferredColorScheme = () => { - if (!globalThis || !globalThis.matchMedia) return "light"; + if (!globalThis || !globalThis.matchMedia) { + return "light"; + } const isDarkThemePreferred = globalThis.matchMedia("(prefers-color-scheme: dark)").matches; - if (isDarkThemePreferred) return "dark"; + if (isDarkThemePreferred) { + return "dark"; + } return "light"; }; diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index d1ba27e108d..6bd28cfe809 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -1,96 +1,30 @@ import { setCompodocJson } from "@storybook/addon-docs/angular"; +import { withThemeByClassName } from "@storybook/addon-themes"; import { componentWrapperDecorator } from "@storybook/angular"; import type { Preview } from "@storybook/angular"; import docJson from "../documentation.json"; setCompodocJson(docJson); -const decorator = componentWrapperDecorator( - (story) => { - return ` - -
- ${story} -
-
- -
- ${story} -
-
- -
- ${story} -
-
- -
- ${story} -
-
- - - - - +const wrapperDecorator = componentWrapperDecorator((story) => { + return /*html*/ ` +
+ ${story} +
`; - }, - ({ globals }) => { - return { theme: `${globals["theme"]}` }; - }, -); +}); const preview: Preview = { - decorators: [decorator], - globalTypes: { - theme: { - description: "Global theme for components", - defaultValue: "both", - toolbar: { - title: "Theme", - icon: "circlehollow", - items: [ - { - title: "Light & Dark", - value: "both", - icon: "sidebyside", - }, - { - title: "Light", - value: "light", - icon: "sun", - }, - { - title: "Dark", - value: "dark", - icon: "moon", - }, - { - title: "Nord", - value: "nord", - left: "⛰", - }, - { - title: "Solarized", - value: "solarized", - left: "☯", - }, - ], - dynamicTitle: true, + decorators: [ + withThemeByClassName({ + themes: { + light: "theme_light", + dark: "theme_dark", }, - }, - }, + defaultTheme: "light", + }), + wrapperDecorator, + ], parameters: { controls: { matchers: { @@ -105,6 +39,9 @@ const preview: Preview = { }, }, docs: { source: { type: "dynamic", excludeDecorators: true } }, + backgrounds: { + disable: true, + }, }, tags: ["autodocs"], }; diff --git a/.vscode/settings.json b/.vscode/settings.json index 6b31121e17b..295c290a37a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,5 +7,6 @@ "**/_locales/*[^n]/messages.json": true }, "rust-analyzer.linkedProjects": ["apps/desktop/desktop_native/Cargo.toml"], - "typescript.tsdk": "node_modules/typescript/lib" + "typescript.tsdk": "node_modules/typescript/lib", + "eslint.useFlatConfig": true } diff --git a/angular.json b/angular.json index 7053050262e..665d810cf4e 100644 --- a/angular.json +++ b/angular.json @@ -147,6 +147,10 @@ "./tsconfig.json", "-e", "json", + "--disableInternal", + "--disableLifeCycleHooks", + "--disablePrivate", + "--disableProtected", "-d", ".", "--disableRoutesGraph" @@ -165,6 +169,10 @@ "./tsconfig.json", "-e", "json", + "--disableInternal", + "--disableLifeCycleHooks", + "--disablePrivate", + "--disableProtected", "-d", ".", "--disableRoutesGraph" diff --git a/apps/browser/.eslintrc.json b/apps/browser/.eslintrc.json deleted file mode 100644 index ba960511839..00000000000 --- a/apps/browser/.eslintrc.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "env": { - "browser": true, - "webextensions": true - }, - "overrides": [ - { - "files": ["src/**/*.ts"], - "excludedFiles": [ - "src/**/{content,popup,spec}/**/*.ts", - "src/**/autofill/{notification,overlay}/**/*.ts", - "src/**/autofill/**/{autofill-overlay-content,collect-autofill-content,dom-element-visibility,insert-autofill-content}.service.ts", - "src/**/*.spec.ts" - ], - "rules": { - "no-restricted-globals": [ - "error", - { - "name": "window", - "message": "The `window` object is not available in service workers and may not be available within the background script. Consider using `self`, `globalThis`, or another global property instead." - } - ] - } - } - ] -} diff --git a/apps/browser/package.json b/apps/browser/package.json index 3adeb292b6d..5a8ddd03b41 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -1,35 +1,35 @@ { "name": "@bitwarden/browser", - "version": "2025.1.0", + "version": "2025.3.1", "scripts": { "build": "npm run build:chrome", - "build:chrome": "cross-env BROWSER=chrome MANIFEST_VERSION=3 webpack", - "build:edge": "cross-env BROWSER=edge webpack", - "build:firefox": "cross-env BROWSER=firefox webpack", - "build:opera": "cross-env BROWSER=opera webpack", - "build:safari": "cross-env BROWSER=safari webpack", + "build:chrome": "cross-env BROWSER=chrome MANIFEST_VERSION=3 NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", + "build:edge": "cross-env BROWSER=edge MANIFEST_VERSION=3 NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", + "build:firefox": "cross-env BROWSER=firefox NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", + "build:opera": "cross-env BROWSER=opera MANIFEST_VERSION=3 NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", + "build:safari": "cross-env BROWSER=safari NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", "build:watch": "npm run build:watch:chrome", "build:watch:chrome": "npm run build:chrome -- --watch", "build:watch:edge": "npm run build:edge -- --watch", "build:watch:firefox": "npm run build:firefox -- --watch", "build:watch:opera": "npm run build:opera -- --watch", "build:watch:safari": "npm run build:safari -- --watch", - "build:prod:chrome": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=8192\" npm run build:chrome", - "build:prod:edge": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=8192\" npm run build:edge", - "build:prod:firefox": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=8192\" npm run build:firefox", - "build:prod:opera": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=8192\" npm run build:opera", - "build:prod:safari": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=8192\" npm run build:safari", + "build:prod:chrome": "cross-env NODE_ENV=production npm run build:chrome", + "build:prod:edge": "cross-env NODE_ENV=production npm run build:edge", + "build:prod:firefox": "cross-env NODE_ENV=production npm run build:firefox", + "build:prod:opera": "cross-env NODE_ENV=production npm run build:opera", + "build:prod:safari": "cross-env NODE_ENV=production npm run build:safari", "dist:chrome": "npm run build:prod:chrome && mkdir -p dist && ./scripts/compress.ps1 dist-chrome.zip", "dist:edge": "npm run build:prod:edge && mkdir -p dist && ./scripts/compress.ps1 dist-edge.zip", "dist:firefox": "npm run build:prod:firefox && mkdir -p dist && ./scripts/compress.ps1 dist-firefox.zip", "dist:opera": "npm run build:prod:opera && mkdir -p dist && ./scripts/compress.ps1 dist-opera.zip", "dist:safari": "npm run build:prod:safari && ./scripts/package-safari.ps1", - "dist:edge:mv3": "cross-env MANIFEST_VERSION=3 npm run dist:edge", "dist:firefox:mv3": "cross-env MANIFEST_VERSION=3 npm run dist:firefox", "dist:opera:mv3": "cross-env MANIFEST_VERSION=3 npm run dist:opera", "dist:safari:mv3": "cross-env MANIFEST_VERSION=3 npm run dist:safari", "test": "jest", "test:watch": "jest --watch", - "test:watch:all": "jest --watchAll" + "test:watch:all": "jest --watchAll", + "test:clearCache": "jest --clear-cache" } } diff --git a/apps/browser/postcss.config.js b/apps/browser/postcss.config.js index c4513687e89..5657df3afcf 100644 --- a/apps/browser/postcss.config.js +++ b/apps/browser/postcss.config.js @@ -1,4 +1,9 @@ -/* eslint-disable no-undef */ +/* eslint-disable @typescript-eslint/no-require-imports */ module.exports = { - plugins: [require("tailwindcss"), require("autoprefixer"), require("postcss-nested")], + plugins: [ + require("postcss-import"), + require("postcss-nested"), + require("tailwindcss"), + require("autoprefixer"), + ], }; diff --git a/apps/browser/scripts/package-safari.ps1 b/apps/browser/scripts/package-safari.ps1 index 075ed606070..ce208478098 100755 --- a/apps/browser/scripts/package-safari.ps1 +++ b/apps/browser/scripts/package-safari.ps1 @@ -52,7 +52,7 @@ foreach ($subBuildPath in $subBuildPaths) { "--verbose", "--force", "--sign", - "E7C9978F6FBCE0553429185C405E61F5380BE8EB", + "4B9662CAB74E8E4F4ECBDD9EDEF2543659D95E3C", "--entitlements", $entitlementsPath ) diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index 46f119bdafe..4c051d455ac 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "تلميح كلمة المرور الرئيسية (إختياري)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "انضم إلى المنظمة" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "نسخ الملاحظات" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { "message": "ملء", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "أدخل عنوان البريد الإلكتروني لحسابك وسيُرسل تلميح كلمة المرور الخاصة بك إليك" }, - "passwordHint": { - "message": "تلميح كلمة المرور" - }, - "enterEmailToGetHint": { - "message": "أدخل عنوان البريد الإلكتروني لحسابك للحصول على تلميح كلمة المرور الرئيسية." - }, "getMasterPasswordHint": { "message": "احصل على تلميح كلمة المرور الرئيسية" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "تحرير المجلّد" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "مجلد جديد" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "توليد عبارة المرور" }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "regeneratePassword": { "message": "إعادة توليد كلمة المرور" }, @@ -454,22 +482,6 @@ "length": { "message": "الطول" }, - "uppercase": { - "message": "أحرف كبيرة (من A إلى Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "أحرف كبيرة (من a إلى z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "الأرقام (من 0 الى 9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "الأحرف الخاصة (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "تضمين", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "تضمين أحرف خاصة", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "عدد الكلمات" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "متصفح الويب الخاص بك لا يدعم خاصية النسخ السهل. يرجى استخدام النسخ اليدوي." }, - "verifyIdentity": { - "message": "قم بتأكيد هويتك" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "خزانتك مقفلة. قم بتأكيد هويتك للمتابعة." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "تسجيل الدخول إلى Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "إعادة التسجيل" }, @@ -875,6 +904,9 @@ "no": { "message": "لا" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "حدث خطأ غير متوقع." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "اطلب إضافة عنصر إذا لم يتم العثور على عنصر في المخزن الخاص بك. ينطبق على جميع حسابات تسجيل الدخول." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "أظهر البطاقات في صفحة التبويبات" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "قائمة عناصر البطاقة في صفحة التبويب لسهولة التعبئة التلقائية." }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "إظهار الهويات على صفحة التبويب" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "مسح الحافظة", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "حفظ" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "اسأل عن تحديث تسجيل الدخول الحالي" }, @@ -1084,10 +1169,6 @@ "message": "فاتح", "description": "Light color" }, - "solarizedDark": { - "message": "داكن مُشمس", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "التصدير من" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "شراء العضوية المميزة" }, - "premiumPurchaseAlert": { - "message": "يمكنك شراء العضوية المتميزة على bitwarden.com على خزانة الويب. هل تريد زيارة الموقع الآن؟" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "تذكرني" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "إرسال رمز التحقق إلى البريد الإلكتروني مرة أخرى" }, "useAnotherTwoStepMethod": { "message": "استخدام طريقة أخرى لتسجيل الدخول بخطوتين" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "أدخل YubiKey الخاص بك في منفذ USB في كمبيوترك، ثم المس الزر." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "فتح علامة تبويب جديدة" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "مصادقة WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "تسجيل الدخول غير متاح" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "خيارات تسجيل الدخول بخطوتين" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "هل تفقد الوصول إلى جميع مزودي التحقق بعاملين؟ استخدم رمز الاسترداد الخاص بك لتعطيل جميع مزودي التحقق بعاملين من حسابك." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "البيئة المستضافة ذاتيا" }, - "selfHostedEnvironmentFooter": { - "message": "حدد عنوان URL الأساسي لتثبيت Bitwarden المستضاف محليًا." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "بيئة مخصصة" }, - "customEnvironmentFooter": { - "message": "للمستخدمين المتقدمين. يمكنك تحديد عنوان URL الأساسي لكل خدمة بشكل مستقل." - }, "baseUrl": { "message": "رابط الخادم" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "اسحب للفرز" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "نص" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "مولد اسم المستخدم" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "استخدم كلمة المرور هذه" }, @@ -2063,12 +2163,24 @@ "message": "لإنشاء كلمة مرور فريدة قوية", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "إجراء مهلة المخزن" }, "vaultTimeoutAction1": { "message": "Timeout action" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "قفل", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "النطاقات", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "النطاقات المستبعدة" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden لن يطلب حفظ تفاصيل تسجيل الدخول لهذه النطافات لجميع الحسابات مسجلة الدخول. يجب عليك تحديث الصفحة لكي تصبح التغييرات نافذة المفعول." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "الموقع $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "تم حفظ تغييرات استبعاد النطاقات" }, @@ -2789,6 +3022,20 @@ "error": { "message": "خطأ" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "إنشاء اسم المستخدم" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "تم تعديل الإعدادات" - }, - "environmentEditedClick": { - "message": "انقر هنا" - }, - "environmentEditedReset": { - "message": "لإعادة تعيين الإعدادات المُعدة مسبقاً" - }, "serverVersion": { "message": "إصدار الخادم" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "تسجيل الدخول باستخدام كلمة المرور الرئيسية" }, - "loggingInAs": { - "message": "تسجيل الدخول كـ" - }, - "notYou": { - "message": "ليس حسابك؟" - }, "newAroundHere": { "message": "New around here?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "تسجيل الدخول بالجهاز" }, - "loginWithDeviceEnabledInfo": { - "message": "يجب إعداد تسجيل الدخول بالجهاز في إعدادات تطبيق Bitwarden. هل تحتاج إلى خِيار آخر؟" - }, "fingerprintPhraseHeader": { "message": "عبارة بصمة الإصبع" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "View all log in options" }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" - }, "notificationSentDevice": { "message": "تم إرسال إشعار إلى جهازك." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "You will be notified once the request is approved" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "بَدْء تسجيل الدخول" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "كلمة المرور الرئيسية مكشوفة" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "طلب موافقة المدير" }, - "approveWithMasterPassword": { - "message": "الموافقة بواسطة كلمة المرور الرئيسية" - }, "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3396,38 +3649,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "استيراد بياناتك إلى Bitwarden؟", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "حماية بياناتك الأخيرة واستيرادك إلى Bitwarden؟", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "حفظ كملف غير مشفر", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "استيراد إلى Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "جارِ الاستيراد...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "لقد تم استيراد البيانات بنجاح!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "خطأ في الاستيراد. تحقق من وحدة التحكم للحصول على التفاصيل.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "تم مواجهة خطأ في الشبكة أثناء الاستيراد.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "الاسم البديل للنطاق" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Active account" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Available accounts" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "No values to copy" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organization is deactivated" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "You have not selected anything." }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra wide" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 51bfe95a48a..6f3ace453ae 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Ana parol ipucu (ixtiyari)" }, + "passwordStrengthScore": { + "message": "Parolun güc xalı: $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Təşkilata qoşul" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Notları kopyala" }, + "copy": { + "message": "Kopyala", + "description": "Copy to clipboard" + }, "fill": { "message": "Doldur", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Hesabınızın e-poçt ünvanını daxil edin və parolunuz üçün ipucu sizə göndəriləcək" }, - "passwordHint": { - "message": "Parol ipucu" - }, - "enterEmailToGetHint": { - "message": "Ana parol ipucunuzu alacağınız hesabınızın e-poçt ünvanını daxil edin." - }, "getMasterPasswordHint": { "message": "Ana parol üçün ipucu alın" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Qovluğa düzəliş et" }, + "editFolderWithName": { + "message": "Qovluğa düzəliş et: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "Yeni qovluq" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Keçid ifadələri yarat" }, + "passwordGenerated": { + "message": "Parol yaradıldı." + }, + "passphraseGenerated": { + "message": "Keçid ifadəsi yaradıldı" + }, + "usernameGenerated": { + "message": "İstifadəçi adı yaradıldı" + }, + "emailGenerated": { + "message": "E-poçt yaradıldı" + }, "regeneratePassword": { "message": "Parolu yenidən yarat" }, @@ -454,22 +482,6 @@ "length": { "message": "Uzunluq" }, - "uppercase": { - "message": "Böyük hərf (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Kiçik hərf (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Rəqəmlər (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Xüsusi simvollar (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Daxil et", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Xüsusi xarakterləri daxil et", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Söz sayı" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Veb brauzeriniz lövhəyə kopyalamağı dəstəkləmir. Əvəzində əllə kopyalayın." }, - "verifyIdentity": { - "message": "Kimliyi doğrula" + "verifyYourIdentity": { + "message": "Kimliyinizi doğrulayın" + }, + "weDontRecognizeThisDevice": { + "message": "Bu cihazı tanımırıq. Kimliyinizi doğrulamaq üçün e-poçtunuza göndərilən kodu daxil edin." + }, + "continueLoggingIn": { + "message": "Giriş etməyə davam" }, "yourVaultIsLocked": { "message": "Seyfiniz kilidlənib. Davam etmək üçün kimliyinizi doğrulayın." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Bitwarden-ə giriş edin" }, + "enterTheCodeSentToYourEmail": { + "message": "E-poçtunuza göndərilən kodu daxil edin" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Kimlik doğrulayıcı tətbiqinizdəki kodu daxil edin" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Kimliyi doğrulamaq üçün YubiKey-inizə basın" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Hesabınız üçün Duo iki addımlı giriş tələb olunur. Giriş prosesini tamamlamaq üçün aşağıdakı addımları izləyin." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Giriş prosesini tamamlamaq üçün aşağıdakı addımları izləyin." + }, "restartRegistration": { "message": "Qeydiyyatı yenidən başlat" }, @@ -875,6 +904,9 @@ "no": { "message": "Xeyr" }, + "location": { + "message": "Yerləşmə" + }, "unexpectedError": { "message": "Gözlənilməz bir xəta baş verdi." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Seyfinizdə tapılmayan elementin əlavə edilməsi soruşulsun. Giriş etmiş bütün hesablara aiddir." }, - "showCardsInVaultView": { - "message": "Kartları, Seyf görünüşündə Avto-doldurma təklifləri olaraq göstər" + "showCardsInVaultViewV2": { + "message": "Kartları, Seyf görünüşündə Avto-doldurma təklifləri olaraq həmişə göstər" }, "showCardsCurrentTab": { "message": "Kartları Vərəq səhifəsində göstər" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Asan avto-doldurma üçün Vərəq səhifəsində kart elementlərini sadalayın." }, - "showIdentitiesInVaultView": { - "message": "Kimlikləri, Seyf görünüşündə Avto-doldurma təklifləri olaraq göstər" + "showIdentitiesInVaultViewV2": { + "message": "Kimlikləri, Seyf görünüşündə Avto-doldurma təklifləri olaraq həmişə göstər" }, "showIdentitiesCurrentTab": { "message": "Vərəq səhifəsində kimlikləri göstər" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Seyf görünüşündə avto-doldurmaq üçün elementlərə klikləyin" }, + "clickToAutofill": { + "message": "Avto-doldurma təkliflərində elementləri doldurmaq üçün klikləyin" + }, "clearClipboard": { "message": "Lövhəni təmizlə", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Saxla" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ Bitwarden-də saxlanıldı.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ Bitwarden-də güncəlləndi.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Yeni giriş kimi saxla", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Giriş məlumatlarını güncəllə", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Giriş məlumatları saxlanılsın?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Mövcud giriş məlumatları güncəllənsin?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Giriş məlumatları saxlanıldı", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Giriş məlumatları güncəlləndi", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Saxlama xətası", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Bunu saxlaya bilmədik. Məlumatları manual daxil etməyə çalışın.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Mövcud girişin güncəllənməsini soruş" }, @@ -1084,10 +1169,6 @@ "message": "Açıq", "description": "Light color" }, - "solarizedDark": { - "message": "Günəşli tünd", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Buradan xaricə köçür" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Premium satın al" }, - "premiumPurchaseAlert": { - "message": "Premium üzvlüyü bitwarden.com veb seyfində satın ala bilərsiniz. İndi saytı ziyarət etmək istəyirsiniz?" - }, "premiumPurchaseAlertV2": { "message": "Bitwarden veb tətbiqindəki hesab ayarlarınızda Premium satın ala bilərsiniz." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Məni xatırla" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Bu cihazda 30 gün ərzində soruşulmasın" + }, "sendVerificationCodeEmailAgain": { "message": "Doğrulama kodu olan e-poçtu yenidən göndər" }, "useAnotherTwoStepMethod": { "message": "Başqa bir iki addımlı giriş üsulu istifadə edin" }, + "selectAnotherMethod": { + "message": "Başqa üsul seçin", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Geri qaytarma kodunuzu istifadə edin" + }, "insertYubiKey": { "message": "\"YubiKey\"i kompüterinizin USB portuna taxın, daha sonra düyməsinə toxunun." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Yeni vərəq aç" }, + "openInNewTab": { + "message": "Yeni vərəqdə aç" + }, "webAuthnAuthenticate": { "message": "WebAuthn kimlik doğrulama" }, + "readSecurityKey": { + "message": "Güvənlik açarını oxu" + }, + "awaitingSecurityKeyInteraction": { + "message": "Güvənlik açarı ilə əlaqə gözlənilir..." + }, "loginUnavailable": { "message": "Giriş edilə bilmir" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "İki addımlı giriş seçimləri" }, + "selectTwoStepLoginMethod": { + "message": "İki addımlı giriş üsulunu seçin" + }, "recoveryCodeDesc": { "message": "İki faktorlu provayderlərinə müraciəti itirmisiniz? Geri qaytarma kodunuzu istifadə edərək hesabınızdakı bütün iki faktorlu provayderləri sıradan çıxarda bilərsiniz." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Self-hosted mühit" }, - "selfHostedEnvironmentFooter": { - "message": "Öz-özünə sahiblik edən Bitwarden quraşdırmasının təməl URL-sini müəyyənləşdirin." - }, "selfHostedBaseUrlHint": { "message": "Öz-özünə sahiblik edən Bitwarden quraşdırmasının təməl URL-sini müəyyənləşdirin. Nümunə: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Özəl mühit" }, - "customEnvironmentFooter": { - "message": "Qabaqcıl istifadəçilər üçündür. Hər xidmətin baza URL-sini müstəqil olaraq müəyyənləşdirə bilərsiniz." - }, "baseUrl": { "message": "Server URL-si" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Sıralamaq üçün sürüklə" }, + "dragToReorder": { + "message": "Yenidən sıralamaq üçün sürüklə" + }, "cfTypeText": { "message": "Mətn" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "İstifadəçi adı yaradıcı" }, + "useThisEmail": { + "message": "Bu e-poçtu istifadə et" + }, "useThisPassword": { "message": "Bu parolu istifadə et" }, @@ -2063,12 +2163,24 @@ "message": "Daha unikal parollar yarat", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Seyf özəlləşdirməsi" + }, "vaultTimeoutAction": { "message": "Seyf vaxtının bitmə əməliyyatı" }, "vaultTimeoutAction1": { "message": "Vaxt bitmə əməliyyatı" }, + "newCustomizationOptionsCalloutTitle": { + "message": "Yeni özəlləşdirmə seçimləri" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Cəld kopyalama fəaliyyəti, yığcam rejim və daha çoxu ilə seyf təcrübənizi özəlləşdirin!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "Bütün Görünüş ayarlarına bax" + }, "lock": { "message": "Kilidlə", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domenlər", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Əngəllənmiş domenlər" + }, + "learnMoreAboutBlockedDomains": { + "message": "Əngəllənmiş domenlər barədə daha ətraflı" + }, "excludedDomains": { "message": "İstisna edilən domenlər" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden, giriş etmiş bütün hesablar üçün bu domenlərin giriş detallarını saxlamağı soruşmayacaq. Dəyişikliklərin qüvvəyə minməsi üçün səhifəni təzələməlisiniz." }, + "blockedDomainsDesc": { + "message": "Bu veb saytlar üçün avto-doldurma və digər əlaqəli özəlliklər təklif olunmayacaq. Dəyişikliklərin qüvvəyə minməsi üçün səhifəni təzələməlisiniz." + }, + "autofillBlockedNoticeV2": { + "message": "Bu veb sayt üçün avto-doldurma əngəllənib." + }, + "autofillBlockedNoticeGuidance": { + "message": "Bunu ayarlarda dəyişdir" + }, + "change": { + "message": "Dəyişdir" + }, + "changeButtonTitle": { + "message": "Parolu dəyişdir - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "Riskli parollar" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$, sizdən bir parolu dəyişdirməyinizi tələb edir, çünki risk altındadır.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$, sizdən $COUNT$ parolu dəyişdirməyinizi tələb edir, çünki risk altındadır.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Təşkilatlarınız, sizdən $COUNT$ parolu dəyişdirməyinizi tələb edir, çünki risk altındadır.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Riskli bir parolu incələ və dəyişdir" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Riskli $COUNT$ parolu incələ və dəyişdir", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Riskli parolları daha tez dəyişdir" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Cəld şəkildə parollarınızı avto-doldura və yenilərini yarada bilməyiniz üçün ayarlarınızı güncəlləyin" + }, + "reviewAtRiskLogins": { + "message": "Risk altındakı giriş məlumatlarını incələ" + }, + "reviewAtRiskPasswords": { + "message": "Risk altındakı parolları incələ" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Təşkilatınız parolları zəif, təkrar istifadə olunduğu və/və ya ifşa olunduğu üçün risk altındadır.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Risk altındakı girişlərin olduğu siyahının təsviri" + }, + "generatePasswordSlideDesc": { + "message": "Risk altında olan saytda Bitwarden avto-doldurma menyusu ilə güclü, unikal parolları cəld yaradın.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Yaradılan parolları göstərən Bitwarden avto-doldurma menyusunun təsviri" + }, + "updateInBitwarden": { + "message": "Bitwarden-də güncəllə" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden, daha sonra parol menecerində parolu güncəlləməyinizi istəyəcək.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "İstifadəçidən giriş məlumatlarını güncəlləməsini istəyən Bitwarden bildirişinin təsviri" + }, + "turnOnAutofill": { + "message": "Avto-doldurmanı işə sal" + }, + "turnedOnAutofill": { + "message": "Avto-doldurma işə salındı" + }, + "dismiss": { + "message": "Rədd et" + }, "websiteItemLabel": { "message": "Veb sayt $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Əngəllənmiş domen dəyişiklikləri saxlanıldı" + }, "excludedDomainsSavedSuccess": { "message": "İstisna domen dəyişikliyi saxlanıldı" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Xəta" }, + "decryptionError": { + "message": "Şifrə açma xətası" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden, aşağıda sadalanan seyf element(lər)inin şifrəsini aça bilmədi." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Əlavə data itkisini önləmək üçün", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "müştəri dəstəyi ilə əlaqə saxlayın.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "İstifadəçi adı yarat" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ tələbinizə rədd cavabı verdi. Lütfən kömək üçün xidmət provayderinizlə əlaqə saxlayın.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ tələbinizə rədd cavabı verdi: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "$SERVICENAME$ maskalı e-poçt hesab kimliyi alına bilmir.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Ayarlara düzəliş edildi" - }, - "environmentEditedClick": { - "message": "Bura klikləyin" - }, - "environmentEditedReset": { - "message": "ön konfiqurasiyalı ayarları sıfırlamaq üçün" - }, "serverVersion": { "message": "Server versiyası" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Ana parolla giriş et" }, - "loggingInAs": { - "message": "Giriş et" - }, - "notYou": { - "message": "Siz deyilsiniz?" - }, "newAroundHere": { "message": "Burada yenisiniz?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Cihazla giriş et" }, - "loginWithDeviceEnabledInfo": { - "message": "Cihazla giriş, Bitwarden tətbiqinin ayarlarında qurulmalıdır. Başqa bir seçimə ehtiyacınız var?" - }, "fingerprintPhraseHeader": { "message": "Barmaq izi ifadəsi" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "Bütün giriş seçimlərinə bax" }, - "viewAllLoginOptionsV1": { - "message": "Bütün giriş seçimlərinə bax" - }, "notificationSentDevice": { "message": "Cihazınıza bir bildiriş göndərildi." }, + "notificationSentDevicePart1": { + "message": "Cihazınızda Bitwarden kilidini açın, ya da " + }, + "notificationSentDeviceAnchor": { + "message": "veb tətbiqinizdə" + }, + "notificationSentDevicePart2": { + "message": "Təsdiqləməzdən əvvəl Barmaq izi ifadəsinin aşağıdakı ifadə ilə uyuşduğuna əmin olun." + }, "aNotificationWasSentToYourDevice": { "message": "Cihazınıza bir bildiriş göndərildi" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Hesabınızın kilidinin açıq olduğuna və barmaq izi ifadəsinin digər cihazda uyuşduğuna əmin olun" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "Tələbiniz təsdiqləndikdə bildiriş alacaqsınız" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Giriş başladıldı" }, + "logInRequestSent": { + "message": "Tələb göndərildi" + }, "exposedMasterPassword": { "message": "İfşa olunmuş ana parol" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Admin təsdiqini tələb et" }, - "approveWithMasterPassword": { - "message": "Ana parolla təsdiqlə" - }, "ssoIdentifierRequired": { "message": "Təşkilat SSO identifikatoru tələb olunur." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Tələbiniz admininizə göndərildi." }, - "youWillBeNotifiedOnceApproved": { - "message": "Təsdiqləndikdən sonra məlumatlandırılacaqsınız." - }, "troubleLoggingIn": { "message": "Girişdə problem var?" }, @@ -3396,38 +3649,6 @@ "message": "Yığcamlaşdırmanı aç/bağla", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Datanız Bitwarden-ə köçürülsün?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "LastPass datanızı qoruyub Bitwarden-ə köçürürsünüz?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Şifrələnməmiş fayl olaraq saxla", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Bitwarden-ə köçür", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Daxilə köçürülür...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data uğurla daxilə köçürüldü!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Daxilə köçürmə xətası. Detallar üçün konsolu yoxlayın.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Daxilə köçürmə zamanı şəbəkə xətası baş verdi.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Domen ləqəbi" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Aktiv hesab" }, + "bitwardenAccount": { + "message": "Bitwarden hesabı" + }, "availableAccounts": { "message": "Mövcud hesablar" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Avto-doldurma təklifləri" }, + "itemSuggestions": { + "message": "Təklif olunan elementlər" + }, "autofillSuggestionsTip": { "message": "Bu saytın avto-doldurması üçün giriş elementini saxlayın" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Kopyala: $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "Kopyalanacaq dəyər yoxdur" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Element adı" }, - "cannotRemoveViewOnlyCollections": { - "message": "\"Yalnız baxma\" icazələrinə sahib kolleksiyaları silə bilməzsiniz: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Təşkilat deaktiv edildi" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Veb sayt URI-yını yenidən sırala. Ox düyməsi ilə elementi yuxarı və ya aşağı daşıyın." + }, "reorderFieldUp": { "message": "$LABEL$ yuxarı daşındı, mövqeyi $INDEX$/$LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "Heç nə seçməmisiniz." }, - "movedItemsToOrg": { - "message": "Seçilən elementlər $ORGNAME$ təşkilatına daşınıldı", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Elementlər bura daşındı: $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Mətn \"Send\"ləri" }, - "bitwardenNewLook": { - "message": "Bitwarden-in yeni bir görünüşü var!" - }, - "bitwardenNewLookDesc": { - "message": "Seyf vərəqindən avto-doldurma və axtarış etmə artıq daha asan və intuitivdir. Nəzər salın!" - }, "accountActions": { "message": "Hesab fəaliyyətləri" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "Bu elementə düzəliş etmə icazəniz yoxdur" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Əvvəlcə PIN və ya parol ilə kilid açma tələb olunduğu üçün biometrik ilə kilid açma əlçatmazdır." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrik kilid açma indi əlçatmazdır." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Yanlış konfiqurasiya edilmiş sistem fayllarına görə biometrik kilid açma əlçatmazdır." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Yanlış konfiqurasiya edilmiş sistem fayllarına görə biometrik kilid açma əlçatmazdır." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Bitwarden masaüstü tətbiqi bağlı olduğu üçün biometrik kilid açma əlçatmazdır." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Bitwarden masaüstü tətbiqində $EMAIL$ üçün fəal olmadığına görə biometrik kilid açma əlçatmazdır.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Bilinməyən bilinməyən bir səbəbə görə biometrik kilid açma əlçatmazdır." + }, "authenticating": { "message": "Kimlik doğrulama" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Ekstra enli" + }, + "sshKeyWrongPassword": { + "message": "Daxil etdiyiniz parol yanlışdır." + }, + "importSshKey": { + "message": "Daxilə köçür" + }, + "confirmSshKeyPassword": { + "message": "Parolu təsdiqlə" + }, + "enterSshKeyPasswordDesc": { + "message": "SSH açarı üçün parolu daxil edin." + }, + "enterSshKeyPassword": { + "message": "Parolu daxil edin" + }, + "invalidSshKey": { + "message": "SSH açarı yararsızdır" + }, + "sshKeyTypeUnsupported": { + "message": "SSH açar növü dəstəklənmir" + }, + "importSshKeyFromClipboard": { + "message": "Açarı lövhədən daxilə köçür" + }, + "sshKeyImported": { + "message": "SSH açarı uğurla daxilə köçürüldü" + }, + "cannotRemoveViewOnlyCollections": { + "message": "\"Yalnız baxma\" icazələrinə sahib kolleksiyaları silə bilməzsiniz: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Lütfən masaüstü tətbiqinizi güncəlləyin" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "Biometrik kilid açmanı istifadə etmək üçün lütfən masaüstü tətbiqinizi güncəlləyin, ya da masaüstü ayarlarında barmaq izi ilə kilid açmanı sıradan çıxardın." + }, + "changeAtRiskPassword": { + "message": "Riskli parolları dəyişdir" } } diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index 46c804d27d9..905f6b6ac1d 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Падказка да асноўнага пароля (неабавязкова)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Далучыцца да арганізацыі" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Скапіяваць нататкі" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { "message": "Запоўніць", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Падказка да пароля" - }, - "enterEmailToGetHint": { - "message": "Увядзіце адрас электроннай пошты ўліковага запісу для атрымання падказкі да асноўнага пароля." - }, "getMasterPasswordHint": { "message": "Атрымаць падказку да асноўнага пароля" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Рэдагаваць папку" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "Новая папка" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Згенерыраваць парольную фразу" }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "regeneratePassword": { "message": "Паўторна генерыраваць пароль" }, @@ -454,22 +482,6 @@ "length": { "message": "Даўжыня" }, - "uppercase": { - "message": "Вялікія літары (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Маленькія літары (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Лічбы (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Спецыяльныя сімвалы (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Уключыць", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Уключыце спецыяльныя сімвалы", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Колькасць слоў" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Ваш вэб-браўзер не падтрымлівае капіяванне даных у буфер абмену. Скапіюйце іх уручную." }, - "verifyIdentity": { - "message": "Праверыць асобу" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "Ваша сховішча заблакіравана. Каб працягнуць, пацвердзіце сваю асобу." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Увайсці ў Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Restart registration" }, @@ -875,6 +904,9 @@ "no": { "message": "Не" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "Адбылася нечаканая памылка." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "Паказваць карткі на старонцы з укладкамі" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Спіс элементаў картак на старонцы з укладкамі для лёгкага аўтазапаўнення." }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "Паказваць пасведчанні на старонцы з укладкамі" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "Ачыстка буфера абмену", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Захаваць" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Пытацца пра абнаўленні існуючага лагіна" }, @@ -1084,10 +1169,6 @@ "message": "Светлая", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized dark", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Экспартаванне з" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Купіць прэміум" }, - "premiumPurchaseAlert": { - "message": "Вы можаце купіць прэміяльны статус на bitwarden.com. Перайсці на вэб-сайт зараз?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Запомніць мяне" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Адправіць праверачны код яшчэ раз" }, "useAnotherTwoStepMethod": { "message": "Выкарыстоўваць іншы метад двухэтапнага ўваходу" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Устаўце свой YubiKey у порт USB камп'ютара, а потым націсніце на кнопку." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Адкрыць новую ўкладку" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "Аўтэнтыфікацыя WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "Уваход недаступны" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Параметры двухэтапнага ўваходу" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Згубілі доступ да ўсіх варыянтаў доступу пастаўшчыкоў двухэтапнай аўтэнтыфікацыі? Скарыстайцеся кодам аднаўлення, каб адключыць праверку пастаўшчыкоў двухэтапнай аўтэнтыфікацыі для вашага ўліковага запісу." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Асяроддзе ўласнага хостынгу" }, - "selfHostedEnvironmentFooter": { - "message": "Увядзіце асноўны URL-адрас вашага лакальнага размяшчэння ўсталяванага Bitwarden." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Карыстальніцкае асяроддзе" }, - "customEnvironmentFooter": { - "message": "Для дасведчаных карыстальнікаў. Можна ўвесці URL-адрасы асобна для кожнай службы." - }, "baseUrl": { "message": "URL-адрас сервера" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Перацягніце для сартавання" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Тэкст" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Генератар імені карыстальніка" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "Выкарыстоўваць гэты пароль" }, @@ -2063,12 +2163,24 @@ "message": "каб стварыць надзейны ўнікальны пароль", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "Дзеянне пасля заканчэння часу чакання сховішча" }, "vaultTimeoutAction1": { "message": "Timeout action" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "Заблакіраваць", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Дамены", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "Выключаныя дамены" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "Вэб-сайт $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Памылка" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Генерыраваць імя карыстальніка" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Налады былі адрэдагаваныя" - }, - "environmentEditedClick": { - "message": "Націсніце тут" - }, - "environmentEditedReset": { - "message": "для скіду да прадвызначаных наладаў" - }, "serverVersion": { "message": "Версія сервера" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Увайсці з асноўным паролем" }, - "loggingInAs": { - "message": "Увайсці як" - }, - "notYou": { - "message": "Не вы?" - }, "newAroundHere": { "message": "Упершыню тут?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Уваход з прыладай" }, - "loginWithDeviceEnabledInfo": { - "message": "Неабходна наладзіць уваход з прыладай у наладах мабільнай праграмы Bitwarden. Патрабуецца іншы варыянт?" - }, "fingerprintPhraseHeader": { "message": "Фраза адбітка пальца" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "View all log in options" }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" - }, "notificationSentDevice": { "message": "Апавяшчэнне было адпраўлена на вашу прыладу." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "You will be notified once the request is approved" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Ініцыяваны ўваход" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Скампраметаваны асноўны пароль" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Запытаць ухваленне адміністратара" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Ваш запыт адпраўлены адміністратару." }, - "youWillBeNotifiedOnceApproved": { - "message": "Вы атрымаеце апавяшчэння пасля яго ўхвалення." - }, "troubleLoggingIn": { "message": "Праблемы з уваходам?" }, @@ -3396,38 +3649,6 @@ "message": "Згарнуць/Разгарнуць", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Імпартаванне...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Даныя паспяхова імпартаваны!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Мянушка дамена" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Active account" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Available accounts" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "No values to copy" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Назва элемента" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organization is deactivated" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "Вы пакуль нічога не выбралі." }, - "movedItemsToOrg": { - "message": "Выбраныя элементы перамешчаны ў $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra wide" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index 7cbbdc07be4..226e63e32cb 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Подсказване за главната парола (по избор)" }, + "passwordStrengthScore": { + "message": "Оценка на сложността на паролата: $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Присъединяване към организацията" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Копиране на бележките" }, + "copy": { + "message": "Копиране", + "description": "Copy to clipboard" + }, "fill": { "message": "Попълване", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Въведете е-пощата на регистрацията си и подсказката за паролата ще Ви бъде изпратена" }, - "passwordHint": { - "message": "Подсказка за паролата" - }, - "enterEmailToGetHint": { - "message": "Въведете адреса на имейла си, за да получите подсказка за главната си парола." - }, "getMasterPasswordHint": { "message": "Подсказка за главната парола" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Редактиране на папка" }, + "editFolderWithName": { + "message": "Редактиране на папка: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "Нова папка" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Генериране на парола-фраза" }, + "passwordGenerated": { + "message": "Паролата е генерирана" + }, + "passphraseGenerated": { + "message": "Паролата-фраза е генерирана" + }, + "usernameGenerated": { + "message": "Потребителското име е генерирано" + }, + "emailGenerated": { + "message": "Е-пощата е генерирана" + }, "regeneratePassword": { "message": "Регенериране на паролата" }, @@ -454,22 +482,6 @@ "length": { "message": "Дължина" }, - "uppercase": { - "message": "Главни букви (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Малки букви (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Числа (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Специални знаци (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Включване", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Включване на специални знаци", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Брой думи" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Браузърът не поддържа копиране в буфера, затова копирайте на ръка." }, - "verifyIdentity": { - "message": "Потвърждаване на самоличността" + "verifyYourIdentity": { + "message": "Потвърдете самоличността си" + }, + "weDontRecognizeThisDevice": { + "message": "Това устройство е непознато. Въведете кода изпратен на е-пощата Ви, за да потвърдите самоличността си." + }, + "continueLoggingIn": { + "message": "Продължаване с вписването" }, "yourVaultIsLocked": { "message": "Трезорът е заключен — въведете главната си парола, за да продължите." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Впишете се в Битуорден" }, + "enterTheCodeSentToYourEmail": { + "message": "Въведете кода изпратен на е-пощата Ви" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Въведете кода от Вашето приложение за удостоверяване" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Натиснете бутона на своя YubiKey за удостоверяване" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Вашият акаунт изисква вписване чрез двустепенно удостоверяване с Duo. Следвайте стъпките по-долу, за да завършите вписването." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Следвайте стъпките по-долу, за да завършите вписването." + }, "restartRegistration": { "message": "Рестартиране на регистрацията" }, @@ -875,6 +904,9 @@ "no": { "message": "Не" }, + "location": { + "message": "Местоположение" + }, "unexpectedError": { "message": "Възникна неочаквана грешка." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Питане за добавяне на елемент, ако такъв не бъде намерен в трезора. Прилага се за всички регистрации, в които сте вписан(а)." }, - "showCardsInVaultView": { - "message": "Показване на картите като предложения за авт. попълване в изгледа на трезора" + "showCardsInVaultViewV2": { + "message": "Картите да се показват винаги като предложения за авт. попълване в изгледа на трезора" }, "showCardsCurrentTab": { "message": "Показване на карти в страницата с разделите" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Показване на картите в страницата с разделите, за лесно автоматично попълване." }, - "showIdentitiesInVaultView": { - "message": "Показване на самоличности като предложения за автоматично попълване в изгледа на трезора" + "showIdentitiesInVaultViewV2": { + "message": "Самоличностите да се показват винаги като предложения за автоматично попълване в изгледа на трезора" }, "showIdentitiesCurrentTab": { "message": "Показване на самоличности в страницата с разделите" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Щракнете върху елементите в трезора за автоматично попълване" }, + "clickToAutofill": { + "message": "Щракнете върху елементите в предложениета за автоматично попълване" + }, "clearClipboard": { "message": "Изчистване на буфера", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Да, нека се запише сега" }, + "loginSaveSuccessDetails": { + "message": "Запазено в Битуорден: $USERNAME$.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "Обновено в Битуорден: $USERNAME$.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Запазване като нов елемент за вписване", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Обновяване на данните за вписване", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Запазване на данните за вписване?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Да се обновят ли текущите данни за вписване?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Данните за вписване са запазени", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Данните за вписване са обновени", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Грешка при запазването", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "О, не! Запазването не беше успешно. Опитайте да въведете данните ръчно.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Питане за обновяване на съществуващ запис" }, @@ -1084,10 +1169,6 @@ "message": "Светъл", "description": "Light color" }, - "solarizedDark": { - "message": "Преекспонирано тъмен", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Изнасяне от" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Покупка на платен абонамент" }, - "premiumPurchaseAlert": { - "message": "Може да платите абонамента си през сайта bitwarden.com. Искате ли да го посетите сега?" - }, "premiumPurchaseAlertV2": { "message": "Можете да закупите платената версия от настройките на регистрацията си, в приложението по уеб на Битуорден." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Запомняне" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Не ме питайте отново на това устройство за 30 дни" + }, "sendVerificationCodeEmailAgain": { "message": "Повторно изпращане на писмото за потвърждение" }, "useAnotherTwoStepMethod": { "message": "Използвайте друг начин на двустепенно удостоверяване" }, + "selectAnotherMethod": { + "message": "Изберете друг метод", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Използване на код за възстановяване" + }, "insertYubiKey": { "message": "Поставете устройството на YubiKey в USB порт на компютъра и натиснете бутона на устройството." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Отваряне на нов раздел" }, + "openInNewTab": { + "message": "Отваряне в нов раздел" + }, "webAuthnAuthenticate": { "message": "Идентификация WebAuthn" }, + "readSecurityKey": { + "message": "Прочитане на ключа за сигурност" + }, + "awaitingSecurityKeyInteraction": { + "message": "Изчакване на действие с ключ за сигурност…" + }, "loginUnavailable": { "message": "Записът липсва" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Настройки на двустепенното удостоверяване" }, + "selectTwoStepLoginMethod": { + "message": "Изберете начин за двустепенно удостоверяване" + }, "recoveryCodeDesc": { "message": "Ако сте загубили достъп до двустепенното удостоверяване, може да използвате код за възстановяване, за да изключите двустепенното удостоверяване в абонамента си." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Собствена среда" }, - "selfHostedEnvironmentFooter": { - "message": "Укажете базовия адрес за собствената ви инсталирана среда на Bitwarden." - }, "selfHostedBaseUrlHint": { "message": "Посочете базовия адрес на Вашата собствена инсталация на Битуорден. Пример: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Специална среда" }, - "customEnvironmentFooter": { - "message": "За специални случаи. Може да укажете основните адреси на всяка ползвана услуга поотделно." - }, "baseUrl": { "message": "Адрес на сървъра" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Подредба чрез влачене" }, + "dragToReorder": { + "message": "Плъзнете за пренареждане" + }, "cfTypeText": { "message": "Текст" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Генератор на потребителски имена" }, + "useThisEmail": { + "message": "Използване на тази е-поща" + }, "useThisPassword": { "message": "Използване на тази парола" }, @@ -2063,12 +2163,24 @@ "message": "за да създадете сигурна и уникална парола", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Персонализиране на трезора" + }, "vaultTimeoutAction": { "message": "Действие при изтичане на времето" }, "vaultTimeoutAction1": { "message": "Действие при изтичането на времето за достъп" }, + "newCustomizationOptionsCalloutTitle": { + "message": "Нови възможности за персонализиране" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Персонализирайте трезора си с бързи действия за копиране, компактен режим и още!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "Преглед на всички настройки за външния вид" + }, "lock": { "message": "Заключване", "description": "Verb form: to make secure or inaccessible by" @@ -2285,7 +2397,7 @@ "message": "Потребителят е заключен или отписан" }, "biometricsNotUnlockedDesc": { - "message": "Отключете потребителя в настолното приложение и опитайте отново." + "message": "Отключете потребителя в самостоятелното приложение и опитайте отново." }, "biometricsNotAvailableTitle": { "message": "Отключването чрез биометрични данни не е налично" @@ -2324,6 +2436,12 @@ "message": "Домейни", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Блокирани домейни" + }, + "learnMoreAboutBlockedDomains": { + "message": "Научете повече за блокираните домейни" + }, "excludedDomains": { "message": "Изключени домейни" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Битуорден няма да пита дали да запазва данните за вход в тези сайтове за всички регистрации, в които сте вписан(а). За да влезе правилото в сила, презаредете страницата." }, + "blockedDomainsDesc": { + "message": "Автоматичното попълване и други свързани функции няма да бъдат предлагани за тези уеб сайтове. Трябва да презаредите страницата, за да влязат в сила промените." + }, + "autofillBlockedNoticeV2": { + "message": "Автоматичното попълване е блокирано за този уеб сайт." + }, + "autofillBlockedNoticeGuidance": { + "message": "Променете това в настройките" + }, + "change": { + "message": "Промяна" + }, + "changeButtonTitle": { + "message": "Промяна на паролата – $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "Пароли в риск" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ изисква да промените една парола, тъй като е в риск.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ изисква да промените $COUNT$ пароли, тъй като са в риск.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Вашата организация изисква да промените $COUNT$ пароли, тъй като са в риск.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Преглед и промяна на една парола в риск" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Преглед и промяна на $COUNT$ пароли в риск", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "По-бърза промяна на паролите в риск" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Променете настройките си, така че да можете бързо да попълвате автоматично паролите си, както и да генерирате нови" + }, + "reviewAtRiskLogins": { + "message": "Преглед на елементите за вписване в риск" + }, + "reviewAtRiskPasswords": { + "message": "Преглед на паролите в риск" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Паролите в организацията Ви са в риск, защото са слаби, преизползвани и/или разкрити.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Илюстрация на списък с елементи за вписване, които са в риск" + }, + "generatePasswordSlideDesc": { + "message": "Генерирайте бързо сложна и уникална парола от менюто за автоматично попълване на Битуорден, на уеб сайта, който е в риск.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Илюстрация на автоматичното попълване на Битуорден, показващо генерирана парола" + }, + "updateInBitwarden": { + "message": "Обновяване в Битуорден" + }, + "updateInBitwardenSlideDesc": { + "message": "След това Битуорден ще попита дали искате да обновите паролата в управителя на пароли.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Илюстрация на известието на Битуорден, чрез което пита потребителя дали да се обновят данните за вписване" + }, + "turnOnAutofill": { + "message": "Включване на автоматичното попълване" + }, + "turnedOnAutofill": { + "message": "Автоматичното попълване беше включено" + }, + "dismiss": { + "message": "Отхвърляне" + }, "websiteItemLabel": { "message": "Уеб сайт $number$ (адрес)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Промените на блокираните домейни са запазени" + }, "excludedDomainsSavedSuccess": { "message": "Промените на изключените домейни са запазени" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Грешка" }, + "decryptionError": { + "message": "Грешка при дешифриране" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Битоурден не може да дешифрира елементите от трезора посочени по-долу." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Свържете се с поддръжката", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "за да избегнете загубата на данни.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Генериране на потр. име" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ отказа заявката Ви. Свържете се с доставчика на услугата за съдействие.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ отказа заявката Ви: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Не може да бъде получен идентификатор на маскиран чрез е-поща акаунт от $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Настройките баха променени" - }, - "environmentEditedClick": { - "message": "Щракнете тук" - }, - "environmentEditedReset": { - "message": "за да върнете предварително зададените настройки" - }, "serverVersion": { "message": "Версия на сървъра" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Вписване с главната парола" }, - "loggingInAs": { - "message": "Вписване като" - }, - "notYou": { - "message": "Това не сте Вие?" - }, "newAroundHere": { "message": "За пръв път ли сте тук?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Вписване с устройство" }, - "loginWithDeviceEnabledInfo": { - "message": "Вписването с устройство трябва да е включено в настройките на приложението на Битуорден. Друга настройка ли търсите?" - }, "fingerprintPhraseHeader": { "message": "Уникална фраза" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "Вижте всички възможности за вписване" }, - "viewAllLoginOptionsV1": { - "message": "Вижте всички възможности за вписване" - }, "notificationSentDevice": { "message": "Към устройството Ви е изпратено известие." }, + "notificationSentDevicePart1": { + "message": "Отключете Битоурден на устройството си или в" + }, + "notificationSentDeviceAnchor": { + "message": "приложението по уеб" + }, + "notificationSentDevicePart2": { + "message": "Уверете се, че уникалната фраза съвпада с тази по-долу, преди да одобрите." + }, "aNotificationWasSentToYourDevice": { "message": "Към устройството Ви е изпратено известие" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Уверете се, че регистрацията Ви е отключена и че уникалната фраза съвпада с другото устройство" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "Ще получите уведомление когато заявката бъде одобрена" }, @@ -3091,8 +3347,11 @@ "loginInitiated": { "message": "Вписването е стартирано" }, + "logInRequestSent": { + "message": "Заявката е изпратена" + }, "exposedMasterPassword": { - "message": "Разобличена главна парола" + "message": "Разкрита главна парола" }, "exposedMasterPasswordDesc": { "message": "Паролата е намерена в пробив на данни. Използвайте уникална парола, за да защитите вашия акаунт. Наистина ли искате да използвате слаба парола?" @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Подаване на заявка за одобрение от администратор" }, - "approveWithMasterPassword": { - "message": "Одобряване с главната парола" - }, "ssoIdentifierRequired": { "message": "Идентификаторът за еднократна идентификация на организация е задължителен." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Вашата заявка беше изпратена до администратора Ви." }, - "youWillBeNotifiedOnceApproved": { - "message": "Ще получите известие, когато тя бъде одобрена." - }, "troubleLoggingIn": { "message": "Имате проблем с вписването?" }, @@ -3396,38 +3649,6 @@ "message": "Превключване на свиването", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Внасяне на данните Ви в Битуорден?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Защитаване на данните Ви от LastPass и внасяне в Битуорден?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Запазване като нешифрован файл", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Внасяне в Битуорден", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Внасяне…", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Данните бяха внесени успешно!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Грешка при внасянето. Вижте конзолата за подробности.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "По време на внасянето възникна мрежова грешка.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Псевдонимен домейн" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Активиране на регистрацията" }, + "bitwardenAccount": { + "message": "Акаунт в Битуорден" + }, "availableAccounts": { "message": "Налични регистрации" }, @@ -3979,7 +4203,10 @@ "message": "Секретният ключ е премахнат" }, "autofillSuggestions": { - "message": "Автоматично попълване на предложения" + "message": "Предложения за авт. попълване" + }, + "itemSuggestions": { + "message": "Препоръчани елементи" }, "autofillSuggestionsTip": { "message": "Запазване на елемент за вписване за този уеб сайт, за авт. попълване" @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Копиране на $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "Няма стойности за копиране" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Име на елемента" }, - "cannotRemoveViewOnlyCollections": { - "message": "Не можете да премахвате колекции с права „Само за преглед“: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Организацията е деактивирана" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Преместване на адреса на уеб сайта. Използвайте стрелките, за да преместите елемента нагоре или надолу." + }, "reorderFieldUp": { "message": "Преместено нагоре: $LABEL$. Позиция $INDEX$ от $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "Не сте избрали нищо." }, - "movedItemsToOrg": { - "message": "Избраните записи бяха преместени в $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Елементите са преместени в $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Текстови изпращания" }, - "bitwardenNewLook": { - "message": "Биуорден има нов облик!" - }, - "bitwardenNewLookDesc": { - "message": "Сега е по-лесно и интуитивно от всякога да използвате автоматичното попълване и да търсите в раздела на трезора. Разгледайте!" - }, "accountActions": { "message": "Действия по регистрацията" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "Нямате право за редактиране на този елемент" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Отключването с биометрични данни не е налично, тъй като първо се изисква отключване чрез ПИН или парола." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Отключването с биометрични данни не е налично в момента." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Отключването с биометрични данни не е налично поради неправилно настроени системни файлове." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Отключването с биометрични данни не е налично поради неправилно настроени системни файлове." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Отключването с биометрични данни не е налично, тъй като приложението на Биуорден за компютър не работи." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Отключването с биометрични данни не е налично, тъй като не е включено за $EMAIL$ в приложението на Битуорден за компютър.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Отключването с биометрични данни не е налично по неизвестна причина." + }, "authenticating": { "message": "Удостоверяване" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Много широко" + }, + "sshKeyWrongPassword": { + "message": "Въведената парола е неправилна." + }, + "importSshKey": { + "message": "Внасяне" + }, + "confirmSshKeyPassword": { + "message": "Потвърждаване на паролата" + }, + "enterSshKeyPasswordDesc": { + "message": "Въведете паролата за SSH-ключа." + }, + "enterSshKeyPassword": { + "message": "Въведете паролата" + }, + "invalidSshKey": { + "message": "SSH ключът е неправилен" + }, + "sshKeyTypeUnsupported": { + "message": "Типът на SSH ключа не се поддържа" + }, + "importSshKeyFromClipboard": { + "message": "Внасяне на ключ от буфера за обмен" + }, + "sshKeyImported": { + "message": "SSH ключът е внесен успешно" + }, + "cannotRemoveViewOnlyCollections": { + "message": "Не можете да премахвате колекции с права „Само за преглед“: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Моля, обновете самостоятелното приложение" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "За да използвате отключването чрез биометрични данни, обновете самостоятелното приложение или изключете отключването чрез пръстов отпечатък в настройките му." + }, + "changeAtRiskPassword": { + "message": "Промяна на парола в риск" } } diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index 2da7b40554c..983b9fadde4 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "মূল পাসওয়ার্ড ইঙ্গিত (ঐচ্ছিক)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Copy notes" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { "message": "Fill", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "পাসওয়ার্ড ইঙ্গিত" - }, - "enterEmailToGetHint": { - "message": "আপনার মূল পাসওয়ার্ডের ইঙ্গিতটি পেতে আপনার অ্যাকাউন্টের ইমেল ঠিকানা প্রবেশ করুন।" - }, "getMasterPasswordHint": { "message": "মূল পাসওয়ার্ডের ইঙ্গিত পান" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "ফোল্ডার সম্পাদনা" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "New folder" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Generate passphrase" }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "regeneratePassword": { "message": "পাসওয়ার্ড পুনঃতৈরি করুন" }, @@ -454,22 +482,6 @@ "length": { "message": "দৈর্ঘ্য" }, - "uppercase": { - "message": "Uppercase (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Lowercase (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Numbers (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Special characters (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Include", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "শব্দের সংখ্যা" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "আপনার ওয়েব ব্রাউজার সহজে ক্লিপবোর্ড অনুলিপি সমর্থন করে না। পরিবর্তে এটি নিজেই অনুলিপি করুন।" }, - "verifyIdentity": { - "message": "Verify identity" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "আপনার ভল্ট লক করা আছে। চালিয়ে যেতে আপনার মূল পাসওয়ার্ডটি যাচাই করান।" @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Restart registration" }, @@ -875,6 +904,9 @@ "no": { "message": "না" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "একটি অপ্রত্যাশিত ত্রুটি ঘটেছে।" }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "Show cards on Tab page" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "List card items on the Tab page for easy autofill." }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "Show identities on Tab page" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "ক্লিপবোর্ড পরিষ্কার", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "হ্যাঁ, এখনই সংরক্ষণ করুন" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Ask to update existing login" }, @@ -1084,10 +1169,6 @@ "message": "উজ্জ্বল", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized dark", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Export from" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "প্রিমিয়াম কিনুন" }, - "premiumPurchaseAlert": { - "message": "আপনি bitwarden.com ওয়েব ভল্টে প্রিমিয়াম সদস্যতা কিনতে পারেন। আপনি কি এখনই ওয়েবসাইটটি দেখতে চান?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "আমাকে মনে রাখবেন" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "আবার যাচাইকরণ কোড ইমেইলে প্রেরণ করুন" }, "useAnotherTwoStepMethod": { "message": "অন্য দ্বি-পদক্ষেপ প্রবেশ পদ্ধতি ব্যবহার করুন" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "আপনার কম্পিউটারের ইউএসবি পোর্টে আপনার YubiKey ঢোকান, তারপরে তার বোতামটি স্পর্শ করুন।" }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Open new tab" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "লগইন অনুপলব্ধ" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "দ্বি-পদক্ষেপ লগইন বিকল্প" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "আপনার সমস্ত দ্বি-গুণক সরবরাহকারীদের অ্যাক্সেস হারিয়েছেন? আপনার অ্যাকাউন্ট থেকে সমস্ত দ্বি-গুণক সরবরাহকারীদের অক্ষম করতে আপনার পুনরুদ্ধার কোডটি ব্যবহার করুন।" }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "স্ব-হোস্টকৃত পরিবেশ" }, - "selfHostedEnvironmentFooter": { - "message": "আপনার অন-প্রাঙ্গনে হোস্টকৃত Bitwarden ইনস্টলেশনটির বেস URL উল্লেখ করুন।" - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "পছন্দসই পরিবেশ" }, - "customEnvironmentFooter": { - "message": "উন্নত ব্যবহারকারীদের জন্য। আপনি স্বতন্ত্রভাবে প্রতিটি পরিষেবার মূল URL নির্দিষ্ট করতে পারেন।" - }, "baseUrl": { "message": "সার্ভার URL" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "বাছাই করতে টানুন" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "পাঠ্য" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Username generator" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "Use this password" }, @@ -2063,12 +2163,24 @@ "message": "to create a strong unique password", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "ভল্টের সময়সীমা কর্ম" }, "vaultTimeoutAction1": { "message": "Timeout action" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "লক", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Settings have been edited" - }, - "environmentEditedClick": { - "message": "Click here" - }, - "environmentEditedReset": { - "message": "to reset to pre-configured settings" - }, "serverVersion": { "message": "Server version" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Log in with device" }, - "loginWithDeviceEnabledInfo": { - "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" - }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "View all log in options" }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" - }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "You will be notified once the request is approved" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3396,38 +3649,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Active account" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Available accounts" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "No values to copy" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organization is deactivated" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "You have not selected anything." }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra wide" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index 9f5a610c351..08fedb6f10a 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Master password hint (optional)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Copy notes" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { "message": "Fill", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Nagovještaj lozinke" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Edit folder" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "New folder" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Generate passphrase" }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "regeneratePassword": { "message": "Regenerate password" }, @@ -454,22 +482,6 @@ "length": { "message": "Length" }, - "uppercase": { - "message": "Uppercase (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Lowercase (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Numbers (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Special characters (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Include", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Number of words" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Your web browser does not support easy clipboard copying. Copy it manually instead." }, - "verifyIdentity": { - "message": "Verify identity" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "Your vault is locked. Verify your identity to continue." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Restart registration" }, @@ -875,6 +904,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "An unexpected error has occurred." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "Show cards on Tab page" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "List card items on the Tab page for easy autofill." }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "Show identities on Tab page" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "Clear clipboard", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Save" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Ask to update existing login" }, @@ -1084,10 +1169,6 @@ "message": "Light", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized dark", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Export from" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Purchase Premium" }, - "premiumPurchaseAlert": { - "message": "You can purchase Premium membership on the bitwarden.com web vault. Do you want to visit the website now?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Open new tab" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "Login unavailable" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Lost access to all of your two-factor providers? Use your recovery code to turn off all two-factor providers from your account." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Self-hosted environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Custom environment" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "Server URL" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Drag to sort" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Text" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Username generator" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "Use this password" }, @@ -2063,12 +2163,24 @@ "message": "to create a strong unique password", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "Vault timeout action" }, "vaultTimeoutAction1": { "message": "Timeout action" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "Zaključaj", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Settings have been edited" - }, - "environmentEditedClick": { - "message": "Click here" - }, - "environmentEditedReset": { - "message": "to reset to pre-configured settings" - }, "serverVersion": { "message": "Server version" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Log in with device" }, - "loginWithDeviceEnabledInfo": { - "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" - }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "View all log in options" }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" - }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "You will be notified once the request is approved" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3396,38 +3649,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Active account" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Available accounts" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "No values to copy" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organization is deactivated" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "You have not selected anything." }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra wide" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index b38783abc82..14e4a577440 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -20,16 +20,16 @@ "message": "Crea un compte" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Nou a Bitwarden?" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Inicieu sessió amb la clau de pas" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "Inici de sessió únic" }, "welcomeBack": { - "message": "Welcome back" + "message": "Benvingut/da de nou" }, "setAStrongPassword": { "message": "Estableix una contrasenya segura" @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Pista de la contrasenya mestra (opcional)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Uneix-te a l'organització" }, @@ -120,7 +129,7 @@ "message": "Copia contrasenya" }, "copyPassphrase": { - "message": "Copy passphrase" + "message": "Copia frase de pas" }, "copyNote": { "message": "Copia nota" @@ -153,13 +162,13 @@ "message": "Copia el número de llicència" }, "copyPrivateKey": { - "message": "Copy private key" + "message": "Copia la clau privada" }, "copyPublicKey": { - "message": "Copy public key" + "message": "Copieu la clau pública" }, "copyFingerprint": { - "message": "Copy fingerprint" + "message": "Copia l'empremta digital" }, "copyCustomField": { "message": "Copia $FIELD$", @@ -176,8 +185,12 @@ "copyNotes": { "message": "Copia notes" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { - "message": "Fill", + "message": "Emplena", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." }, "autoFill": { @@ -193,10 +206,10 @@ "message": "Emplena automàticament l'identitat" }, "fillVerificationCode": { - "message": "Fill verification code" + "message": "Emplena el codi de verificació" }, "fillVerificationCodeAria": { - "message": "Fill Verification Code", + "message": "Emplena el codi de verificació", "description": "Aria label for the heading displayed the inline menu for totp code autofill" }, "generatePasswordCopied": { @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Introduïu l'adreça de correu electrònic del compte i se us enviarà la pista de contrasenya" }, - "passwordHint": { - "message": "Pista de la contrasenya" - }, - "enterEmailToGetHint": { - "message": "Introduïu l'adreça electrònica del vostre compte per rebre la contrasenya mestra." - }, "getMasterPasswordHint": { "message": "Obteniu la pista de la contrasenya mestra" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Edita la carpeta" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "Carpeta nova" }, @@ -443,7 +459,19 @@ "message": "Genera contrasenya" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "Genera frase de pas" + }, + "passwordGenerated": { + "message": "Contrasenya generada" + }, + "passphraseGenerated": { + "message": "Frase de pas generada" + }, + "usernameGenerated": { + "message": "Nom d'usuari generat" + }, + "emailGenerated": { + "message": "Correu electrònic generat" }, "regeneratePassword": { "message": "Regenera contrasenya" @@ -454,22 +482,6 @@ "length": { "message": "Longitud" }, - "uppercase": { - "message": "Majúscula (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Minúscula (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Números (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Caràcters especials (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Inclou", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Inclou caràcters especials", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Nombre de paraules" }, @@ -530,7 +538,7 @@ "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { - "message": "Enterprise policy requirements have been applied to your generator options.", + "message": "Els requisits de la política empresarial s'han aplicat a les opcions del generador.", "description": "Indicates that a policy limits the credential generator screen." }, "searchVault": { @@ -576,7 +584,7 @@ "message": "Notes" }, "privateNote": { - "message": "Private note" + "message": "Nota privada" }, "note": { "message": "Nota" @@ -600,7 +608,7 @@ "message": "Obri la web" }, "launchWebsiteName": { - "message": "Launch website $ITEMNAME$", + "message": "Inicia el lloc web $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "El vostre navegador web no admet la còpia fàcil del porta-retalls. Copieu-ho manualment." }, - "verifyIdentity": { - "message": "Verifica identitat" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "No reconeixem aquest dispositiu. Introduïu el codi que us hem enviat al correu electrònic per verificar la identitat." + }, + "continueLoggingIn": { + "message": "Continua l'inici de sessió" }, "yourVaultIsLocked": { "message": "La caixa forta està bloquejada. Comproveu la contrasenya mestra per continuar." @@ -682,7 +696,7 @@ "message": "Temps d'espera de la caixa forta" }, "vaultTimeout1": { - "message": "Timeout" + "message": "Temps d'espera" }, "lockNow": { "message": "Bloqueja ara" @@ -779,10 +793,10 @@ "message": "El vostre compte s'ha creat correctament. Ara ja podeu iniciar sessió." }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "S'ha creat el vostre compte nou!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "Heu iniciat sessió!" }, "youSuccessfullyLoggedIn": { "message": "Heu iniciat sessió correctament" @@ -831,10 +845,10 @@ "message": "Bitwarden pot emmagatzemar i omplir codis de verificació en dos passos. Copieu i enganxeu la clau en aquest camp." }, "totpHelperWithCapture": { - "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + "message": "Bitwarden pot emmagatzemar i omplir codis de verificació en dos passos. Seleccioneu la icona de la càmera per fer una captura de pantalla del codi QR de l'autenticador d'aquest lloc web o copieu i enganxeu la clau en aquest camp." }, "learnMoreAboutAuthenticators": { - "message": "Learn more about authenticators" + "message": "Més informació sobre els autenticadors" }, "copyTOTP": { "message": "Copia la clau de l'autenticador (TOTP)" @@ -843,7 +857,7 @@ "message": "Sessió tancada" }, "loggedOutDesc": { - "message": "You have been logged out of your account." + "message": "Heu tancat la sessió del compte." }, "loginExpired": { "message": "La vostra sessió ha caducat." @@ -852,19 +866,34 @@ "message": "Inicia sessió" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Inicia sessió a Bitwarden" + }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." }, "restartRegistration": { - "message": "Restart registration" + "message": "Reinicia el registre" }, "expiredLink": { - "message": "Expired link" + "message": "Enllaç caducat" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "Reinicieu el registre o proveu d'iniciar sessió." }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "És possible que ja tingueu un compte" }, "logOutConfirmation": { "message": "Segur que voleu tancar la sessió?" @@ -875,6 +904,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "S'ha produït un error inesperat." }, @@ -888,10 +920,10 @@ "message": "L'inici de sessió en dues passes fa que el vostre compte siga més segur, ja que obliga a verificar el vostre inici de sessió amb un altre dispositiu, com ara una clau de seguretat, una aplicació autenticadora, un SMS, una trucada telefònica o un correu electrònic. Es pot habilitar l'inici de sessió en dues passes a la caixa forta web de bitwarden.com. Voleu visitar el lloc web ara?" }, "twoStepLoginConfirmationContent": { - "message": "Make your account more secure by setting up two-step login in the Bitwarden web app." + "message": "Fes que el vostre compte siga més segur configurant l'inici de sessió en dos passos a l'aplicació web de Bitwarden." }, "twoStepLoginConfirmationTitle": { - "message": "Continue to web app?" + "message": "Continua cap a l'aplicació web?" }, "editedFolder": { "message": "Carpeta guardada" @@ -978,7 +1010,7 @@ "message": "Demana d'afegir els inicis de sessió" }, "vaultSaveOptionsTitle": { - "message": "Save to vault options" + "message": "Opcions de guardar a la caixa forta" }, "addLoginNotificationDesc": { "message": "La \"Notificació per afegir inicis de sessió\" demana automàticament que guardeu els nous inicis de sessió a la vostra caixa forta quan inicieu la sessió per primera vegada." @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Demana afegir un element si no se'n troba cap a la caixa forta. S'aplica a tots els comptes connectats." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Mostra sempre les targetes com a suggeriments d'emplenament automàtic a la vista de la caixa forta" }, "showCardsCurrentTab": { "message": "Mostra les targetes a la pàgina de pestanya" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Llista els elements de la targeta a la pàgina de pestanya per facilitar l'autoemplenat." }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Mostra sempre les identitats com a suggeriments d'emplenament automàtic a la vista de la caixa forta" }, "showIdentitiesCurrentTab": { "message": "Mostra les identitats a la pàgina de pestanya" @@ -1005,7 +1037,10 @@ "message": "Llista els elements d'identitat de la pestanya de la pàgina per facilitar l'autoemplenat." }, "clickToAutofillOnVault": { - "message": "Click items to autofill on Vault view" + "message": "Feu clic als elements per emplenar automàticament a la vista de la caixa forta" + }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" }, "clearClipboard": { "message": "Buida el porta-retalls", @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Guarda" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Demana d'actualitzar els inicis de sessió existents" }, @@ -1084,10 +1169,6 @@ "message": "Clar", "description": "Light color" }, - "solarizedDark": { - "message": "Solaritzat fosc", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Exporta des de" }, @@ -1098,7 +1179,7 @@ "message": "Format de fitxer" }, "fileEncryptedExportWarningDesc": { - "message": "This file export will be password protected and require the file password to decrypt." + "message": "Aquesta exportació de fitxers estarà protegida amb contrasenya i requerirà la contrasenya del fitxer per desxifrar-la." }, "filePassword": { "message": "Contrasenya del fitxer" @@ -1122,11 +1203,11 @@ "message": "\"Contrasenya del fitxer\" i \"Confirma contrasenya del fitxer\" no coincideixen." }, "warning": { - "message": "ADVERTIMENT", + "message": "AVÍS", "description": "WARNING (should stay in capitalized letters if the language permits)" }, "warningCapitalized": { - "message": "Warning", + "message": "Advertència", "description": "Warning (should maintain locale-relevant capitalization)" }, "confirmVaultExport": { @@ -1148,7 +1229,7 @@ "message": "Compartit" }, "bitwardenForBusinessPageDesc": { - "message": "Bitwarden for Business allows you to share your vault items with others by using an organization. Learn more on the bitwarden.com website." + "message": "Bitwarden per empreses us permet compartir els elements de la vostra caixa forta amb altres usuaris mitjançant una organització. Més informació al lloc web bitwarden.com." }, "moveToOrganization": { "message": "Desplaça a l'organització" @@ -1206,7 +1287,7 @@ "message": "Fitxer" }, "fileToShare": { - "message": "File to share" + "message": "Fitxer per compartir" }, "selectFile": { "message": "Seleccioneu un fitxer" @@ -1242,7 +1323,7 @@ "message": "1 GB d'emmagatzematge xifrat per als fitxers adjunts." }, "premiumSignUpEmergency": { - "message": "Emergency access." + "message": "Accés d’emergència." }, "premiumSignUpTwoStepOptions": { "message": "Opcions propietàries de doble factor com ara YubiKey i Duo." @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Compra Premium" }, - "premiumPurchaseAlert": { - "message": "Podeu comprar la vostra subscripció a la caixa forta web de bitwarden.com. Voleu visitar el lloc web ara?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1287,7 +1365,7 @@ } }, "premiumPriceV2": { - "message": "All for just $PRICE$ per year!", + "message": "Tot per només per $PRICE$ a l'any!", "placeholders": { "price": { "content": "$1", @@ -1317,10 +1395,10 @@ "message": "Introduïu el codi de verificació de 6 dígits de l'aplicació autenticadora." }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "Temps d'espera d'autenticació" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "La sessió d'autenticació s'ha esgotat. Reinicieu el procés d'inici de sessió." }, "enterVerificationCodeEmail": { "message": "Introduïu el codi de verificació de 6 dígits que s'ha enviat per correu electrònic a $EMAIL$.", @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Recorda'm" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Envia el codi de verificació altra vegada" }, "useAnotherTwoStepMethod": { "message": "Utilitzeu un altre mètode d'inici de sessió en dues passes" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Introduïu la vostra YubiKey al port USB de l'ordinador i, a continuació, premeu el seu botó." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Obri una pestanya nova" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "Autenticar WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "Inici de sessió no disponible" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Opcions d'inici de sessió en dues passes" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Heu perdut l'accés a tots els vostres proveïdors de dos factors? Utilitzeu el vostre codi de recuperació per desactivar tots els proveïdors de dos factors del vostre compte." }, @@ -1390,7 +1490,7 @@ "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP Security Key" + "message": "Clau de seguretat OTP de Yubico" }, "yubiKeyDesc": { "message": "Utilitzeu una YubiKey per accedir al vostre compte. Funciona amb els dispositius YubiKey 4, 4 Nano, 4C i NEO." @@ -1413,19 +1513,16 @@ "message": "Correu electrònic" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "Introduïu el codi que us hem enviat al correu electrònic." }, "selfHostedEnvironment": { "message": "Entorn d'allotjament propi" }, - "selfHostedEnvironmentFooter": { - "message": "Especifiqueu l'URL base de la vostra instal·lació de Bitwarden allotjada en un entorn propi." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { - "message": "For advanced configuration, you can specify the base URL of each service independently." + "message": "Per a la configuració avançada, podeu especificar l'URL base de cada servei de manera independent." }, "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." @@ -1433,14 +1530,11 @@ "customEnvironment": { "message": "Entorn personalitzat" }, - "customEnvironmentFooter": { - "message": "Per a usuaris avançats. Podeu especificar l'URL base de cada servei independentment." - }, "baseUrl": { "message": "URL del servidor" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "URL del servidor autoallotjat", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -1466,10 +1560,10 @@ "description": "Represents the message for allowing the user to enable the autofill overlay" }, "autofillSuggestionsSectionTitle": { - "message": "Autofill suggestions" + "message": "Suggeriments d'emplenament automàtic" }, "showInlineMenuLabel": { - "message": "Show autofill suggestions on form fields" + "message": "Mostra suggeriments d'emplenament automàtic als camps del formulari" }, "showInlineMenuIdentitiesLabel": { "message": "Display identities as suggestions" @@ -1478,10 +1572,10 @@ "message": "Display cards as suggestions" }, "showInlineMenuOnIconSelectionLabel": { - "message": "Display suggestions when icon is selected" + "message": "Mostra suggeriments quan la icona està seleccionada" }, "showInlineMenuOnFormFieldsDescAlt": { - "message": "Applies to all logged in accounts." + "message": "S'aplica a tots els comptes connectats." }, "turnOffBrowserBuiltInPasswordManagerSettings": { "message": "Desactiveu la configuració integrada del gestor de contrasenyes del vostre navegador per evitar conflictes." @@ -1502,7 +1596,7 @@ "description": "Overlay appearance select option for showing the field on click of the overlay icon" }, "enableAutoFillOnPageLoadSectionTitle": { - "message": "Autofill on page load" + "message": "Emplenament automàtic a la càrrega de la pàgina" }, "enableAutoFillOnPageLoad": { "message": "Habilita l'emplenament automàtic en carregar la pàgina" @@ -1514,7 +1608,7 @@ "message": "Els llocs web compromesos o no fiables poden aprofitar-se de l'emplenament automàtic en carregar de la pàgina." }, "learnMoreAboutAutofillOnPageLoadLinkText": { - "message": "Learn more about risks" + "message": "Més informació sobre riscs" }, "learnMoreAboutAutofill": { "message": "Obteniu més informació sobre l'emplenament automàtic" @@ -1544,13 +1638,13 @@ "message": "Obri la caixa forta a la barra lateral" }, "commandAutofillLoginDesc": { - "message": "Autofill the last used login for the current website" + "message": "Emplenament automàtic amb l'últim inici de sessió utilitzat per al lloc web actual" }, "commandAutofillCardDesc": { - "message": "Autofill the last used card for the current website" + "message": "Emplenament automàtic amb l'última targeta utilitzada per al lloc web actual" }, "commandAutofillIdentityDesc": { - "message": "Autofill the last used identity for the current website" + "message": "Emplenament automàtic amb l'última identitat utilitzada per al lloc web actual" }, "commandGeneratePasswordDesc": { "message": "Genera i copia una nova contrasenya aleatòria al porta-retalls." @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Arrossega per ordenar" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Text" }, @@ -1768,10 +1865,10 @@ "message": "Identitat" }, "typeSshKey": { - "message": "SSH key" + "message": "Clau SSH" }, "newItemHeader": { - "message": "New $TYPE$", + "message": "Nou $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1780,7 +1877,7 @@ } }, "editItemHeader": { - "message": "Edit $TYPE$", + "message": "Edita $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1789,7 +1886,7 @@ } }, "viewItemHeader": { - "message": "View $TYPE$", + "message": "Mostra $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1801,13 +1898,13 @@ "message": "Historial de les contrasenyes" }, "generatorHistory": { - "message": "Generator history" + "message": "Historial del generador" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "Neteja l'historial del generador" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "Si continueu, totes les entrades se suprimiran permanentment de l'historial del generador. Esteu segur que voleu continuar?" }, "back": { "message": "Arrere" @@ -1816,7 +1913,7 @@ "message": "Col·leccions" }, "nCollections": { - "message": "$COUNT$ collections", + "message": "$COUNT$ col·leccions", "placeholders": { "count": { "content": "$1", @@ -1846,7 +1943,7 @@ "message": "Notes segures" }, "sshKeys": { - "message": "SSH Keys" + "message": "Claus SSH" }, "clear": { "message": "Esborra", @@ -1872,7 +1969,7 @@ "description": "Domain name. Ex. website.com" }, "baseDomainOptionRecommended": { - "message": "Base domain (recommended)", + "message": "Domini base (recomanat)", "description": "Domain name. Ex. website.com" }, "domainName": { @@ -1926,10 +2023,10 @@ "message": "No hi ha cap contrasenya a llistar." }, "clearHistory": { - "message": "Clear history" + "message": "Neteja l'historial" }, "nothingToShow": { - "message": "Nothing to show" + "message": "Res a mostrar" }, "nothingGeneratedRecently": { "message": "You haven't generated anything recently" @@ -1993,10 +2090,10 @@ "message": "Desbloqueja amb codi PIN" }, "setYourPinTitle": { - "message": "Set PIN" + "message": "Estableix el PIN" }, "setYourPinButton": { - "message": "Set PIN" + "message": "Estableix el PIN" }, "setYourPinCode": { "message": "Configureu el vostre codi PIN per desbloquejar Bitwarden. La configuració del PIN es restablirà si tanqueu la sessió definitivament." @@ -2017,7 +2114,7 @@ "message": "Desbloqueja amb biomètrica" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "Desbloqueja amb contrasenya mestra" }, "awaitDesktop": { "message": "S’espera confirmació des de l’escriptori" @@ -2029,7 +2126,7 @@ "message": "Bloqueja amb la contrasenya mestra en reiniciar el navegador" }, "lockWithMasterPassOnRestart1": { - "message": "Require master password on browser restart" + "message": "Sol·licita la contrasenya mestra en reiniciar el navegador" }, "selectOneCollection": { "message": "Heu d'escollir com a mínim una col·lecció." @@ -2041,16 +2138,19 @@ "message": "Clona" }, "passwordGenerator": { - "message": "Password generator" + "message": "Generador de contrasenyes" }, "usernameGenerator": { - "message": "Username generator" + "message": "Generador de nom d'usuari" + }, + "useThisEmail": { + "message": "Utilitza aquest correu" }, "useThisPassword": { - "message": "Use this password" + "message": "Utilitzeu aquesta contrasenya" }, "useThisUsername": { - "message": "Use this username" + "message": "Utilitzeu aquest nom d'usuari" }, "securePasswordGenerated": { "message": "Secure password generated! Don't forget to also update your password on the website." @@ -2063,11 +2163,23 @@ "message": "to create a strong unique password", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "Acció quan acabe el temps d'espera de la caixa forta" }, "vaultTimeoutAction1": { - "message": "Timeout action" + "message": "Acció després del temps d'espera" + }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" }, "lock": { "message": "Bloqueja", @@ -2096,7 +2208,7 @@ "message": "Element restaurat" }, "alreadyHaveAccount": { - "message": "Already have an account?" + "message": "Ja teniu un compte?" }, "vaultTimeoutLogOutConfirmation": { "message": "En tancar la sessió s'eliminarà tot l'accés a la vostra caixa forta i es requerirà una autenticació en línia després del període de temps d'espera. Esteu segur que voleu utilitzar aquesta configuració?" @@ -2108,7 +2220,7 @@ "message": "Ompli automàticament i guarda" }, "fillAndSave": { - "message": "Fill and save" + "message": "Ompli i guarda" }, "autoFillSuccessAndSavedUri": { "message": "Element emplenat automàticament i URI guardat" @@ -2195,10 +2307,10 @@ "message": "Anul·la subscripció" }, "atAnyTime": { - "message": "at any time." + "message": "en qualsevol moment." }, "byContinuingYouAgreeToThe": { - "message": "By continuing, you agree to the" + "message": "En continuar, acceptes el" }, "and": { "message": "i" @@ -2324,6 +2436,12 @@ "message": "Dominis", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Dominis bloquejats" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "Dominis exclosos" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden no demanarà que es guarden les dades d'inici de sessió d'aquests dominis per a tots els comptes iniciats. Heu d'actualitzar la pàgina perquè els canvis tinguen efecte." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Canvieu-ho a la configuració" + }, + "change": { + "message": "Canvia" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Descarta" + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2376,7 +2609,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDetails": { - "message": "Detalls de l'enviament", + "message": "Detalls del Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTypeText": { @@ -2393,7 +2626,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "hideTextByDefault": { - "message": "Ocultar el text per defecte" + "message": "Amaga el text per defecte" }, "expired": { "message": "Caducat" @@ -2440,7 +2673,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deleteSendPermanentConfirmation": { - "message": "Are you sure you want to permanently delete this Send?", + "message": "Esteu segur que voleu suprimir permanentment aquest Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editSend": { @@ -2451,7 +2684,7 @@ "message": "Data de supressió" }, "deletionDateDescV2": { - "message": "L'enviament s'esborrarà permanentment en aquesta data.", + "message": "El Send se suprimirà permanentment en aquesta data.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -2496,7 +2729,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSendSuccessfully": { - "message": "Send created successfully!", + "message": "Send creat correctament!", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendExpiresInHoursSingle": { @@ -2589,7 +2822,7 @@ "message": "Es requereix verificació del correu electrònic" }, "emailVerifiedV2": { - "message": "Email verified" + "message": "Correu electrònic verificat" }, "emailVerificationRequiredDesc": { "message": "Heu de verificar el correu electrònic per utilitzar aquesta característica. Podeu verificar el vostre correu electrònic a la caixa forta web." @@ -2631,7 +2864,7 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "de $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -2775,10 +3008,10 @@ } }, "exportingOrganizationVaultTitle": { - "message": "Exporting organization vault" + "message": "S'està exportant la caixa forta de l’organització" }, "exportingOrganizationVaultDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "message": "Només s'exportarà la caixa forta de l'organització associada a $ORGANIZATION$. No s'inclouran els elements de les caixes fortes individuals ni d'altres organitzacions.", "placeholders": { "organization": { "content": "$1", @@ -2789,14 +3022,28 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Error de desxifrat" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden no ha pogut desxifrar els elements de la caixa forta que s'indiquen a continuació." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contacteu amb el servei d'atenció al client", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "per evitar la pèrdua de dades addicionals.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Genera un nom d'usuari" }, "generateEmail": { - "message": "Generate email" + "message": "Genera correu electrònic" }, "spinboxBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$.", + "message": "El valor ha d'estar entre $MIN$ i $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2810,7 +3057,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "message": " Utilitzeu $RECOMMENDED$ caràcters o més per generar una contrasenya segura.", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2820,7 +3067,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "message": " Utilitzeu $RECOMMENDED$ paraules o més per generar una frase de pas segura.", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2861,7 +3108,7 @@ "message": "Genera un àlies de correu electrònic amb un servei de reenviament extern." }, "forwarderDomainName": { - "message": "Email domain", + "message": "Domini del correu electrònic", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "La configuració s'ha editat" - }, - "environmentEditedClick": { - "message": "Feu clic ací" - }, - "environmentEditedReset": { - "message": "per restablir els paràmetres preconfigurats" - }, "serverVersion": { "message": "Versió del servidor" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Inici de sessió amb contrasenya mestra" }, - "loggingInAs": { - "message": "Has iniciat sessió com" - }, - "notYou": { - "message": "No sou vosaltres?" - }, "newAroundHere": { "message": "Nou per ací?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Inici de sessió amb dispositiu" }, - "loginWithDeviceEnabledInfo": { - "message": "L'inici de sessió amb el dispositiu ha d'estar activat a la configuració de l'aplicació Bitwarden. Necessiteu una altra opció?" - }, "fingerprintPhraseHeader": { "message": "Frase d'empremta digital" }, @@ -3068,29 +3321,35 @@ "message": "Torna a enviar la notificació" }, "viewAllLogInOptions": { - "message": "View all log in options" - }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" + "message": "Veure totes les opcions d'inici de sessió" }, "notificationSentDevice": { "message": "S'ha enviat una notificació al vostre dispositiu." }, - "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDeviceAnchor": { + "message": "aplicació web" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "aNotificationWasSentToYourDevice": { + "message": "S'ha enviat una notificació al vostre dispositiu" }, "youWillBeNotifiedOnceTheRequestIsApproved": { - "message": "You will be notified once the request is approved" + "message": "Se us notificarà un vegada s'haja aprovat la sol·licitud" }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "Necessiteu una altra opció?" }, "loginInitiated": { "message": "S'ha iniciat la sessió" }, + "logInRequestSent": { + "message": "Sol·licitud enviada" + }, "exposedMasterPassword": { "message": "Contrasenya mestra exposada" }, @@ -3146,22 +3405,22 @@ "message": "Configuració d'emplenament automàtic" }, "autofillKeyboardShortcutSectionTitle": { - "message": "Autofill shortcut" + "message": "Drecera d'emplenament automàtic" }, "autofillKeyboardShortcutUpdateLabel": { - "message": "Change shortcut" + "message": "Canvia la drecera" }, "autofillKeyboardManagerShortcutsLabel": { - "message": "Manage shortcuts" + "message": "Gestiona les dreceres" }, "autofillShortcut": { "message": "Drecera de teclat d'emplenament automàtic" }, "autofillLoginShortcutNotSet": { - "message": "The autofill login shortcut is not set. Change this in the browser's settings." + "message": "La drecera d'inici de sessió no està configurada. Canvieu-ho a la configuració del navegador." }, "autofillLoginShortcutText": { - "message": "The autofill login shortcut is $COMMAND$. Manage all shortcuts in the browser's settings.", + "message": "La drecera d'emplenament automàtic és $COMMAND$. Gestioneu totes les dreceres a la configuració del navegador.", "placeholders": { "command": { "content": "$1", @@ -3205,32 +3464,29 @@ "requestAdminApproval": { "message": "Sol·liciteu l'aprovació de l'administrador" }, - "approveWithMasterPassword": { - "message": "Aprova amb contrasenya mestra" - }, "ssoIdentifierRequired": { "message": "Es requereix un identificador SSO de l'organització." }, "creatingAccountOn": { - "message": "Creating account on" + "message": "Creant compte en" }, "checkYourEmail": { - "message": "Check your email" + "message": "Comprova el correu" }, "followTheLinkInTheEmailSentTo": { - "message": "Follow the link in the email sent to" + "message": "Seguiu l'enllaç del correu electrònic enviat a" }, "andContinueCreatingYourAccount": { - "message": "and continue creating your account." + "message": "i continua creant el compte." }, "noEmail": { - "message": "No email?" + "message": "Sense correu electrònic?" }, "goBack": { "message": "Torna arrere" }, "toEditYourEmailAddress": { - "message": "to edit your email address." + "message": "per editar l'adreça de correu electrònic." }, "eu": { "message": "UE", @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "La vostra sol·licitud s'ha enviat a l'administrador." }, - "youWillBeNotifiedOnceApproved": { - "message": "Se us notificarà una vegada aprovat." - }, "troubleLoggingIn": { "message": "Teniu problemes per iniciar la sessió?" }, @@ -3273,11 +3526,11 @@ "message": "Dispositiu de confiança" }, "sendsNoItemsTitle": { - "message": "No active Sends", + "message": "No hi ha Sends actius", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "message": "Utilitzeu Send per compartir informació xifrada de manera segura amb qualsevol persona.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -3354,10 +3607,10 @@ } }, "singleFieldNeedsAttention": { - "message": "1 field needs your attention." + "message": "1 camp necessita la vostra atenció." }, "multipleFieldsNeedAttention": { - "message": "$COUNT$ fields need your attention.", + "message": "$COUNT$ camps necessiten la vostra atenció.", "placeholders": { "count": { "content": "$1", @@ -3396,38 +3649,6 @@ "message": "Redueix/Amplia", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Voleu importar les vostres dades a Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protegiu les vostres dades de LastPass i importeu-les a Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Guarda com a fitxer sense xifrar", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Importa a Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "S'està important...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Les dades s'han importat correctament!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "S'ha produït un error en importar. Consulteu la consola per obtenir més informació.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "S'ha trobat un error de xarxa durant la importació.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alies de domini" }, @@ -3444,7 +3665,7 @@ "description": "Message appearing below the autofill on load message when master password reprompt is set for a vault item." }, "toggleSideNavigation": { - "message": "Toggle side navigation" + "message": "Canvia a la navegació lateral" }, "skipToContent": { "message": "Vés al contingut" @@ -3506,7 +3727,7 @@ "description": "Screen reader text (aria-label) for new item button in overlay" }, "newLogin": { - "message": "New login", + "message": "Inici de sessió nou", "description": "Button text to display within inline menu when there are no matching items on a login field" }, "addNewLoginItemAria": { @@ -3522,7 +3743,7 @@ "description": "Screen reader text (aria-label) for new card button within inline menu" }, "newIdentity": { - "message": "New identity", + "message": "Identitat nova", "description": "Button text to display within inline menu when there are no matching items on an identity field" }, "addNewIdentityItemAria": { @@ -3616,7 +3837,7 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + "message": "S'ha produït un error en connectar amb el servei Duo. Utilitzeu un mètode d'inici de sessió en dos passos diferent o poseu-vos en contacte amb Duo per obtenir ajuda." }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Inicieu DUO i seguiu els passos per finalitzar la sessió." @@ -3711,10 +3932,10 @@ "message": "Clau de pas" }, "accessing": { - "message": "Accessing" + "message": "Accedint a" }, "loggedInExclamation": { - "message": "Logged in!" + "message": "Connectat!" }, "passkeyNotCopied": { "message": "La clau de pas no es copiarà" @@ -3726,7 +3947,7 @@ "message": "Verificació requerida pel lloc iniciador. Aquesta funció encara no s'ha implementat per als comptes sense contrasenya mestra." }, "logInWithPasskeyQuestion": { - "message": "Log in with passkey?" + "message": "Inici de sessió amb clau de pas?" }, "passkeyAlreadyExists": { "message": "Ja hi ha una clau de pas per a aquesta aplicació." @@ -3741,7 +3962,7 @@ "message": "No matching logins for this site" }, "searchSavePasskeyNewLogin": { - "message": "Search or save passkey as new login" + "message": "Cerca o guarda la clau de pas com a nou inici de sessió" }, "confirm": { "message": "Confirma-ho" @@ -3753,7 +3974,7 @@ "message": "Guarda la clau de pas com a nou inici de sessió" }, "chooseCipherForPasskeySave": { - "message": "Choose a login to save this passkey to" + "message": "Trieu un inici de sessió per guardar aquesta clau de pas" }, "chooseCipherForPasskeyAuth": { "message": "Choose a passkey to log in with" @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Activa el compte" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Comptes disponibles" }, @@ -3902,11 +4126,11 @@ "description": "Label indicating the most common import formats" }, "confirmContinueToBrowserSettingsTitle": { - "message": "Continue to browser settings?", + "message": "Voleu continuar a la configuració del navegador?", "description": "Title for dialog which asks if the user wants to proceed to a relevant browser settings page" }, "confirmContinueToHelpCenter": { - "message": "Continue to Help Center?", + "message": "Voleu continuar cap al Centre d'ajuda?", "description": "Title for dialog which asks if the user wants to proceed to a relevant Help Center page" }, "confirmContinueToHelpCenterPasswordManagementContent": { @@ -3954,7 +4178,7 @@ "description": "Notification message for when saving credentials has succeeded." }, "passwordSaved": { - "message": "Password saved!", + "message": "Contrasenya guardada!", "description": "Notification message for when saving credentials has succeeded." }, "updateCipherAttemptSuccess": { @@ -3962,7 +4186,7 @@ "description": "Notification message for when updating credentials has succeeded." }, "passwordUpdated": { - "message": "Password updated!", + "message": "Contrasenya actualitzada!", "description": "Notification message for when updating credentials has succeeded." }, "saveCipherAttemptFailed": { @@ -3979,22 +4203,25 @@ "message": "Clau de pas suprimida" }, "autofillSuggestions": { - "message": "Autofill suggestions" + "message": "Suggeriments d'emplenament automàtic" + }, + "itemSuggestions": { + "message": "Suggested items" }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, "yourVaultIsEmpty": { - "message": "Your vault is empty" + "message": "La caixa forta està buida" }, "noItemsMatchSearch": { - "message": "No items match your search" + "message": "No s'ha trobat cap element que coincidisca amb la cerca" }, "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, "copyInfoTitle": { - "message": "Copy info - $ITEMNAME$", + "message": "Copia info - $ITEMNAME$", "description": "Title for a button that opens a menu with options to copy information from an item.", "placeholders": { "itemname": { @@ -4014,7 +4241,7 @@ } }, "moreOptionsLabel": { - "message": "More options, $ITEMNAME$", + "message": "Més opcions, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -4024,7 +4251,7 @@ } }, "moreOptionsTitle": { - "message": "More options - $ITEMNAME$", + "message": "Més opcions - $ITEMNAME$", "description": "Title for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -4053,32 +4280,46 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "No values to copy" }, "assignToCollections": { - "message": "Assign to collections" + "message": "Assigna a col·leccions" }, "copyEmail": { - "message": "Copy email" + "message": "Copia el correu electrònic" }, "copyPhone": { - "message": "Copy phone" + "message": "Copia telèfon" }, "copyAddress": { - "message": "Copy address" + "message": "Copia l'adreça" }, "adminConsole": { "message": "Consola d'administració" }, "accountSecurity": { - "message": "Account security" + "message": "Seguretat del compte" }, "notifications": { - "message": "Notifications" + "message": "Notificacions" }, "appearance": { - "message": "Appearance" + "message": "Aparença" }, "errorAssigningTargetCollection": { "message": "S'ha produït un error en assignar la col·lecció de destinació." @@ -4087,7 +4328,7 @@ "message": "S'ha produït un error en assignar la carpeta de destinació." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Mostra elements en $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -4097,7 +4338,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Torna a $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -4110,7 +4351,7 @@ "message": "Nou" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Suprimeix $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -4128,15 +4369,6 @@ "itemName": { "message": "Nom d'element" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organization is deactivated" }, @@ -4154,7 +4386,7 @@ "message": "Informació addicional" }, "itemHistory": { - "message": "Item history" + "message": "Historial d'elements" }, "lastEdited": { "message": "Última edició" @@ -4166,19 +4398,19 @@ "message": "Enllaçat" }, "copySuccessful": { - "message": "Copy Successful" + "message": "Còpia correcta" }, "upload": { - "message": "Upload" + "message": "Puja" }, "addAttachment": { - "message": "Add attachment" + "message": "Afegeix adjunt" }, "maxFileSizeSansPunctuation": { - "message": "Maximum file size is 500 MB" + "message": "La mida màxima del fitxer és de 500 MB" }, "deleteAttachmentName": { - "message": "Delete attachment $NAME$", + "message": "Suprimeix adjunt $NAME$", "placeholders": { "name": { "content": "$1", @@ -4196,7 +4428,7 @@ } }, "permanentlyDeleteAttachmentConfirmation": { - "message": "Are you sure you want to permanently delete this attachment?" + "message": "Esteu segur que voleu suprimir definitivament aquest adjunt?" }, "premium": { "message": "Premium" @@ -4208,7 +4440,7 @@ "message": "Filtres" }, "filterVault": { - "message": "Filter vault" + "message": "Filtra dades" }, "filterApplied": { "message": "One filter applied" @@ -4304,16 +4536,16 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "Habilita l'emplenament automàtic en carregar la pàgina?" }, "cardExpiredTitle": { - "message": "Expired card" + "message": "Targeta de crèdit caducada" }, "cardExpiredMessage": { "message": "If you've renewed it, update the card's information" }, "cardDetails": { - "message": "Card details" + "message": "Dades de la targeta" }, "cardBrandDetails": { "message": "$BRAND$ details", @@ -4328,7 +4560,7 @@ "message": "Enable animations" }, "showAnimations": { - "message": "Show animations" + "message": "Mostra animacions" }, "addAccount": { "message": "Afig compte" @@ -4340,15 +4572,15 @@ "message": "Dades" }, "passkeys": { - "message": "Passkeys", + "message": "Claus de pas", "description": "A section header for a list of passkeys." }, "passwords": { - "message": "Passwords", + "message": "Contrasenyes", "description": "A section header for a list of passwords." }, "logInWithPasskeyAriaLabel": { - "message": "Log in with passkey", + "message": "Inicieu sessió amb la clau de pas", "description": "ARIA label for the inline menu button that logs in with a passkey." }, "assign": { @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4493,22 +4728,13 @@ } }, "successfullyAssignedCollections": { - "message": "Successfully assigned collections" + "message": "Col·leccions assignades correctament" }, "nothingSelected": { - "message": "You have not selected anything." - }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } + "message": "No heu seleccionat res." }, "itemsMovedToOrg": { - "message": "Items moved to $ORGNAME$", + "message": "S'han desplaçat elements a $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -4517,7 +4743,7 @@ } }, "itemMovedToOrg": { - "message": "Item moved to $ORGNAME$", + "message": "S'ha desplaçat un element a $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -4546,49 +4772,43 @@ "message": "Item Location" }, "fileSend": { - "message": "File Send" + "message": "Send de fitxer" }, "fileSends": { - "message": "File Sends" + "message": "Sends de fitxer" }, "textSend": { - "message": "Text Send" + "message": "Send de text" }, "textSends": { - "message": "Text Sends" - }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" + "message": "Sends de text" }, "accountActions": { - "message": "Account actions" + "message": "Accions del compte" }, "showNumberOfAutofillSuggestions": { - "message": "Show number of login autofill suggestions on extension icon" + "message": "Mostra el nombre de suggeriments d'emplenament automàtic d'inici de sessió a la icona d'extensió" }, "showQuickCopyActions": { - "message": "Show quick copy actions on Vault" + "message": "Mostra accions de còpia ràpida a la caixa forta" }, "systemDefault": { - "message": "System default" + "message": "Per defecte del sistema" }, "enterprisePolicyRequirementsApplied": { - "message": "Enterprise policy requirements have been applied to this setting" + "message": "Els requisits de la política empresarial s'han aplicat a aquesta configuració" }, "sshPrivateKey": { - "message": "Private key" + "message": "Clau privada" }, "sshPublicKey": { - "message": "Public key" + "message": "Clau pública" }, "sshFingerprint": { - "message": "Fingerprint" + "message": "Empremta digital" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "Tipus de clau" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -4603,10 +4823,10 @@ "message": "RSA 4096-Bit" }, "retry": { - "message": "Retry" + "message": "Torneu-ho a provar" }, "vaultCustomTimeoutMinimum": { - "message": "Minimum custom timeout is 1 minute." + "message": "El temps d'espera personalitzat mínim és d'1 minut." }, "additionalContentAvailable": { "message": "Additional content is available" @@ -4615,10 +4835,10 @@ "message": "File saved to device. Manage from your device downloads." }, "showCharacterCount": { - "message": "Show character count" + "message": "Mostra el recompte de caràcters" }, "hideCharacterCount": { - "message": "Hide character count" + "message": "Amaga el recompte de caràcters" }, "itemsInTrash": { "message": "Items in trash" @@ -4630,26 +4850,53 @@ "message": "Items you delete will appear here and be permanently deleted after 30 days" }, "trashWarning": { - "message": "Items that have been in trash more than 30 days will automatically be deleted" + "message": "Els elements que porten més de 30 dies a la paperera se suprimiran automàticament" }, "restore": { - "message": "Restore" + "message": "Restaura" }, "deleteForever": { - "message": "Delete forever" + "message": "Suprimeix per sempre" }, "noEditPermissions": { - "message": "You don't have permission to edit this item" + "message": "No tens permisos per editar aquest fitxer" + }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." }, "authenticating": { - "message": "Authenticating" + "message": "S'està autenticant" }, "fillGeneratedPassword": { "message": "Fill generated password", "description": "Heading for the password generator within the inline menu" }, "passwordRegenerated": { - "message": "Password regenerated", + "message": "Contrasenya regenerada", "description": "Notification message for when a password has been regenerated" }, "saveLoginToBitwarden": { @@ -4657,11 +4904,11 @@ "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { - "message": "Space", + "message": "Espai", "description": "Represents the space key in screen reader content as a readable word" }, "tildeCharacterDescriptor": { - "message": "Tilde", + "message": "Accent", "description": "Represents the ~ key in screen reader content as a readable word" }, "backtickCharacterDescriptor": { @@ -4669,7 +4916,7 @@ "description": "Represents the ` key in screen reader content as a readable word" }, "exclamationCharacterDescriptor": { - "message": "Exclamation mark", + "message": "Signe d'exclamació", "description": "Represents the ! key in screen reader content as a readable word" }, "atSignCharacterDescriptor": { @@ -4681,7 +4928,7 @@ "description": "Represents the # key in screen reader content as a readable word" }, "dollarSignCharacterDescriptor": { - "message": "Dollar sign", + "message": "Signe del dòlar", "description": "Represents the $ key in screen reader content as a readable word" }, "percentSignCharacterDescriptor": { @@ -4689,7 +4936,7 @@ "description": "Represents the % key in screen reader content as a readable word" }, "caretCharacterDescriptor": { - "message": "Caret", + "message": "Circumflex", "description": "Represents the ^ key in screen reader content as a readable word" }, "ampersandCharacterDescriptor": { @@ -4697,23 +4944,23 @@ "description": "Represents the & key in screen reader content as a readable word" }, "asteriskCharacterDescriptor": { - "message": "Asterisk", + "message": "Asterisc", "description": "Represents the * key in screen reader content as a readable word" }, "parenLeftCharacterDescriptor": { - "message": "Left parenthesis", + "message": "Parèntesi esquerre", "description": "Represents the ( key in screen reader content as a readable word" }, "parenRightCharacterDescriptor": { - "message": "Right parenthesis", + "message": "Parèntesi dret", "description": "Represents the ) key in screen reader content as a readable word" }, "hyphenCharacterDescriptor": { - "message": "Underscore", + "message": "Subratllat", "description": "Represents the _ key in screen reader content as a readable word" }, "underscoreCharacterDescriptor": { - "message": "Hyphen", + "message": "Guió", "description": "Represents the - key in screen reader content as a readable word" }, "plusCharacterDescriptor": { @@ -4725,19 +4972,19 @@ "description": "Represents the = key in screen reader content as a readable word" }, "braceLeftCharacterDescriptor": { - "message": "Left brace", + "message": "Clau esquerra", "description": "Represents the { key in screen reader content as a readable word" }, "braceRightCharacterDescriptor": { - "message": "Right brace", + "message": "Clau dreta", "description": "Represents the } key in screen reader content as a readable word" }, "bracketLeftCharacterDescriptor": { - "message": "Left bracket", + "message": "Claudàtor esquerra", "description": "Represents the [ key in screen reader content as a readable word" }, "bracketRightCharacterDescriptor": { - "message": "Right bracket", + "message": "Claudàtor dret", "description": "Represents the ] key in screen reader content as a readable word" }, "pipeCharacterDescriptor": { @@ -4745,7 +4992,7 @@ "description": "Represents the | key in screen reader content as a readable word" }, "backSlashCharacterDescriptor": { - "message": "Back slash", + "message": "Barra Invertida", "description": "Represents the back slash key in screen reader content as a readable word" }, "colonCharacterDescriptor": { @@ -4753,7 +5000,7 @@ "description": "Represents the : key in screen reader content as a readable word" }, "semicolonCharacterDescriptor": { - "message": "Semicolon", + "message": "Punt i coma", "description": "Represents the ; key in screen reader content as a readable word" }, "doubleQuoteCharacterDescriptor": { @@ -4765,23 +5012,23 @@ "description": "Represents the ' key in screen reader content as a readable word" }, "lessThanCharacterDescriptor": { - "message": "Less than", + "message": "Menor que", "description": "Represents the < key in screen reader content as a readable word" }, "greaterThanCharacterDescriptor": { - "message": "Greater than", + "message": "Major que", "description": "Represents the > key in screen reader content as a readable word" }, "commaCharacterDescriptor": { - "message": "Comma", + "message": "Coma", "description": "Represents the , key in screen reader content as a readable word" }, "periodCharacterDescriptor": { - "message": "Period", + "message": "Punt", "description": "Represents the . key in screen reader content as a readable word" }, "questionCharacterDescriptor": { - "message": "Question mark", + "message": "Signe d'interrogació", "description": "Represents the ? key in screen reader content as a readable word" }, "forwardSlashCharacterDescriptor": { @@ -4789,22 +5036,22 @@ "description": "Represents the / key in screen reader content as a readable word" }, "lowercaseAriaLabel": { - "message": "Lowercase" + "message": "Minúscules" }, "uppercaseAriaLabel": { - "message": "Uppercase" + "message": "Majúscules" }, "generatedPassword": { - "message": "Generated password" + "message": "Contrasenya generada" }, "compactMode": { - "message": "Compact mode" + "message": "Mode compacte" }, "beta": { "message": "Beta" }, "importantNotice": { - "message": "Important notice" + "message": "Noticia important" }, "setupTwoStepLogin": { "message": "Set up two-step login" @@ -4816,7 +5063,7 @@ "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." }, "remindMeLater": { - "message": "Remind me later" + "message": "Recorda-m'ho més tard" }, "newDeviceVerificationNoticePageOneFormContent": { "message": "Do you have reliable access to your email, $EMAIL$?", @@ -4828,24 +5075,69 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "No, jo no" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { "message": "Yes, I can reliably access my email" }, "turnOnTwoStepLogin": { - "message": "Turn on two-step login" + "message": "Activa l'inici de sessió en dos passos" }, "changeAcctEmail": { "message": "Change account email" }, "extensionWidth": { - "message": "Extension width" + "message": "Amplada d'extensió" }, "wide": { - "message": "Wide" + "message": "Ample" }, "extraWide": { - "message": "Extra wide" + "message": "Extra ample" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index bcc7020ffb3..e106d371d57 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Nápověda k hlavnímu heslu (volitelné)" }, + "passwordStrengthScore": { + "message": "Skóre síly hesla: $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Přidat se k organizaci" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Kopírovat poznámky" }, + "copy": { + "message": "Kopírovat", + "description": "Copy to clipboard" + }, "fill": { "message": "Vyplnit", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Zadejte svou e-mailovou adresu, na kterou Vám zašleme nápovědu k heslu" }, - "passwordHint": { - "message": "Nápověda pro heslo" - }, - "enterEmailToGetHint": { - "message": "Zadejte e-mailovou adresu pro zaslání nápovědy k hlavnímu heslu." - }, "getMasterPasswordHint": { "message": "Zaslat nápovědu k hlavnímu heslu" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Upravit složku" }, + "editFolderWithName": { + "message": "Upravit složku: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "Nová složka" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Vygenerovat heslovou frázi" }, + "passwordGenerated": { + "message": "Heslo bylo vygenerováno" + }, + "passphraseGenerated": { + "message": "Heslová fráze byla vygenerována" + }, + "usernameGenerated": { + "message": "Uživatelské jméno bylo vygenerováno" + }, + "emailGenerated": { + "message": "E-mail byl vygenerován" + }, "regeneratePassword": { "message": "Vygenerovat jiné heslo" }, @@ -454,22 +482,6 @@ "length": { "message": "Délka" }, - "uppercase": { - "message": "Velká písmena (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Malá písmena (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Číslice (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Speciální znaky (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Zahrnout", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Zahrnout speciální znaky", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Počet slov" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Váš webový prohlížeč nepodporuje automatické kopírování do schránky. Musíte ho zkopírovat ručně." }, - "verifyIdentity": { - "message": "Ověřit identitu" + "verifyYourIdentity": { + "message": "Ověřte svou totožnost" + }, + "weDontRecognizeThisDevice": { + "message": "Toto zařízení nepoznáváme. Zadejte kód zaslaný na Váš e-mail pro ověření Vaší totožnosti." + }, + "continueLoggingIn": { + "message": "Pokračovat v přihlášení" }, "yourVaultIsLocked": { "message": "Váš trezor je uzamčen. Pro pokračování musíte zadat hlavní heslo." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Přihlásit se do Bitwardenu" }, + "enterTheCodeSentToYourEmail": { + "message": "Zadejte kód odeslaný na Váš e-mail" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Zadejte kód z Vaší ověřovací aplikace" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Stiskněte svůj YubiKey pro ověření" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Pro Váš účet je nutné dvoufázové přihlášení. Pro dokončení přihlášení postupujte podle následujících kroků." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Postupujte podle kroků níže pro dokončení přihlášení." + }, "restartRegistration": { "message": "Restartovat registraci" }, @@ -875,6 +904,9 @@ "no": { "message": "Ne" }, + "location": { + "message": "Umístění" + }, "unexpectedError": { "message": "Vyskytla se neočekávaná chyba." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Požádá o přidání položky, pokud nebyla nalezena v trezoru. Platí pro všechny přihlášené účty." }, - "showCardsInVaultView": { - "message": "Zobrazit karty jako návrhy automatického vyplňování v zobrazení trezoru" + "showCardsInVaultViewV2": { + "message": "Vždy zobrazit karty jako návrhy automatického vyplňování v zobrazení trezoru" }, "showCardsCurrentTab": { "message": "Zobrazit platební karty na obrazovce Karta" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Pro snadné vyplnění zobrazí platební karty na obrazovce Karta." }, - "showIdentitiesInVaultView": { - "message": "Zobrazit identity jako návrhy automatického vyplňování v zobrazení trezoru" + "showIdentitiesInVaultViewV2": { + "message": "Vždy zobrazit identity jako návrhy automatického vyplňování v zobrazení trezoru" }, "showIdentitiesCurrentTab": { "message": "Zobrazit identity na obrazovce Karta" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Klepněte na položky pro automatické vyplnění v zobrazení trezoru" }, + "clickToAutofill": { + "message": "Klepněte na položky v návrhu automatického vyplňování pro vyplnění" + }, "clearClipboard": { "message": "Vymazat schránku", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Uložit" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ uloženo do Bitwardenu.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ aktualizováno v Bitwardenu.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Uložit jako nové přihlašovací údaje", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Aktualizovat přihlašovací údaje", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Uložit přihlašovací údaje?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Aktualizovat existující přihlašovací údaje?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Přihlašovací údaje byly uloženy", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Přihlašovací údaje byly aktualizovány", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Chyba při ukládání", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Ale ne! Nemohli jsme to uložit. Zkuste zadat podrobnosti ručně.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Zeptat se na aktualizaci existujícího přihlášení" }, @@ -1084,10 +1169,6 @@ "message": "Světlý", "description": "Light color" }, - "solarizedDark": { - "message": "Tmavý (solarizovaný)", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Exportovat z" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Zakoupit členství Premium" }, - "premiumPurchaseAlert": { - "message": "Prémiové členství můžete zakoupit na webové stránce bitwarden.com. Chcete tuto stránku nyní otevřít?" - }, "premiumPurchaseAlertV2": { "message": "Premium si můžete zakoupit v nastavení účtu ve webové aplikaci Bitwarden." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Zapamatovat mě" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Neptat se na tomto zařízení 30 dnů" + }, "sendVerificationCodeEmailAgain": { "message": "Znovu zaslat ověřovací kód na e-mail" }, "useAnotherTwoStepMethod": { "message": "Použít jinou metodu dvoufázového přihlášení" }, + "selectAnotherMethod": { + "message": "Vybrat jinou metodu", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Použít obnovovací kód" + }, "insertYubiKey": { "message": "Vložte YubiKey do USB portu Vašeho počítače a stiskněte jeho tlačítko." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Otevřít novou kartu" }, + "openInNewTab": { + "message": "Otevřít v nové kartě" + }, "webAuthnAuthenticate": { "message": "Ověřit WebAuthn" }, + "readSecurityKey": { + "message": "Přečíst bezpečnostní klíč" + }, + "awaitingSecurityKeyInteraction": { + "message": "Čeká se na interakci s bezpečnostním klíčem..." + }, "loginUnavailable": { "message": "Přihlášení není dostupné" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Volby dvoufázového přihlášení" }, + "selectTwoStepLoginMethod": { + "message": "Vyberte metodu dvoufázového přihlášení" + }, "recoveryCodeDesc": { "message": "Ztratili jste přístup ke všem nastaveným poskytovatelům dvoufázového přihlášení? Použijte obnovovací kód pro vypnutí dvoufázového přihlášení." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Vlastní hostované prostředí" }, - "selfHostedEnvironmentFooter": { - "message": "Zadejte základní URL adresu vlastní hostované aplikace Bitwarden." - }, "selfHostedBaseUrlHint": { "message": "Zadejte základní URL adresu Vaší vlastní hostované aplikace Bitwarden. Příklad: https://bitwarden.spolecnost.cz" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Vlastní prostředí" }, - "customEnvironmentFooter": { - "message": "Pro pokročilé uživatele. Můžete zadat základní URL adresu každé služby zvlášť." - }, "baseUrl": { "message": "URL serveru" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Přetáhnutím seřadíte" }, + "dragToReorder": { + "message": "Přesuňte tažením" + }, "cfTypeText": { "message": "Text" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Generátor uživatelského jména" }, + "useThisEmail": { + "message": "Použít tento e-mail" + }, "useThisPassword": { "message": "Použít toto heslo" }, @@ -2063,12 +2163,24 @@ "message": "pro vytvoření silného jedinečného hesla", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Přizpůsobení trezoru" + }, "vaultTimeoutAction": { "message": "Akce při vypršení časového limitu" }, "vaultTimeoutAction1": { "message": "Akce vypršení časového limitu" }, + "newCustomizationOptionsCalloutTitle": { + "message": "Nové volby přizpůsobení" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Přizpůsobte si svůj trezor s rychlými kopírovacími akcemi, kompaktním režimem a dalším!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "Zobrazit všechna nastavení vzhledu" + }, "lock": { "message": "Zamknout", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domény", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blokované domény" + }, + "learnMoreAboutBlockedDomains": { + "message": "Zjistěte více o blokovaných doménách" + }, "excludedDomains": { "message": "Vyloučené domény" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden nebude žádat o uložení přihlašovacích údajů pro tyto domény pro všechny přihlášené účty. Aby se změny projevily, musíte stránku obnovit." }, + "blockedDomainsDesc": { + "message": "Automatické vyplňování a další související funkce nebudou pro tyto webové stránky nabízeny. Aby se změny projevily, musíte stránku aktualizovat." + }, + "autofillBlockedNoticeV2": { + "message": "Automatické vyplňování je pro tuto stránku zablokováno." + }, + "autofillBlockedNoticeGuidance": { + "message": "Změňte to v nastavení" + }, + "change": { + "message": "Změnit" + }, + "changeButtonTitle": { + "message": "Změnit heslo - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "Ohrožená hesla" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ Vás žádá o změnu 1 hesla, protože je v ohrožení.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ Vás žádá o změnu $COUNT$ hesel, protože jsou v ohrožení.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Vaše organizace Vás žádají o změnu $COUNT$ hesel, protože jsou v ohrožení.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Zkontrolovat a změnit jedno ohrožené heslo" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Zkontrolovat a změnit $COUNT$ ohrožených hesel", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Změnit ohrožená hesla rychleji" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Aktualizujte svá nastavení, abyste mohli rychle automaticky vyplňovat hesla a generovat nová hesla." + }, + "reviewAtRiskLogins": { + "message": "Kontrola rizikových přihlášení" + }, + "reviewAtRiskPasswords": { + "message": "Kontrola rizikových hesel" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Hesla Vaší organizace jsou ohrožena, protože jsou slabá, opakovaně používaná nebo odhalená.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Ilustrace seznamu přihlášení, která jsou riziková" + }, + "generatePasswordSlideDesc": { + "message": "Rychle vygeneruje silné, unikátní heslo s nabídkou automatického vyplňování Bitwarden na rizikových stránkách.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Ilustrace nabídky automatického vyplňování Bitwarden zobrazující vygenerované heslo" + }, + "updateInBitwarden": { + "message": "Aktualizovat v Bitwardenu" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden Vás poté požádá o aktualizaci hesla ve správci hesel.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Ilustrace oznámení v Bitwardenu, která uživatele vyzývá k aktualizaci přihlášení" + }, + "turnOnAutofill": { + "message": "Zapnout automatické vyplňování" + }, + "turnedOnAutofill": { + "message": "Automatické vyplňování bylo zapnuto" + }, + "dismiss": { + "message": "Zavřít" + }, "websiteItemLabel": { "message": "Webová stránka $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Změny v zablokovaných doménách byly uloženy" + }, "excludedDomainsSavedSuccess": { "message": "Vyloučené změny domény byly uloženy" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Chyba" }, + "decryptionError": { + "message": "Chyba dešifrování" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nemohl dešifrovat níže uvedené položky v trezoru." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktujte zákaznickou podporu,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "abyste zabránili ztrátě dat.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Vygenerovat uživatelské jméno" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ odmítl Váš požadavek. Kontaktujte svého poskytovatele služeb o pomoc.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ odmítl Váš požadavek: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Nelze získat maskované ID e-mailového účtu $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Nastavení byla upravena" - }, - "environmentEditedClick": { - "message": "Klepněte zde" - }, - "environmentEditedReset": { - "message": "pro obnovení do přednastavených nastavení" - }, "serverVersion": { "message": "Verze serveru" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Přihlásit se pomocí hlavního hesla" }, - "loggingInAs": { - "message": "Přihlašování jako" - }, - "notYou": { - "message": "Nejste to Vy?" - }, "newAroundHere": { "message": "Jste tu noví?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Přihlásit se zařízením" }, - "loginWithDeviceEnabledInfo": { - "message": "Přihlášení zařízením musí být nastaveno v aplikaci Bitwarden pro počítač. Potřebujete další volby?" - }, "fingerprintPhraseHeader": { "message": "Fráze otisku prstu" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "Zobrazit všechny volby přihlášení" }, - "viewAllLoginOptionsV1": { - "message": "Zobrazit všechny volby přihlášení" - }, "notificationSentDevice": { "message": "Na Vaše zařízení bylo odesláno oznámení." }, + "notificationSentDevicePart1": { + "message": "Odemknout Bitwarden na Vašem zařízení nebo na" + }, + "notificationSentDeviceAnchor": { + "message": "webová aplikace" + }, + "notificationSentDevicePart2": { + "message": "Před schválením se ujistěte, že fráze otisku prstu odpovídá frázi níže." + }, "aNotificationWasSentToYourDevice": { "message": "Na Vaše zařízení bylo odesláno oznámení" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Ujistěte se, že je Váš trezor odemčen a fráze otisku prstu se shodují s druhým zařízením" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "Budete upozorněni, jakmile bude žádost schválena" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Bylo zahájeno přihlášení" }, + "logInRequestSent": { + "message": "Požadavek odeslán" + }, "exposedMasterPassword": { "message": "Odhalené hlavní heslo" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Žádost o schválení správcem" }, - "approveWithMasterPassword": { - "message": "Schválit hlavním heslem" - }, "ssoIdentifierRequired": { "message": "Je vyžadován SSO identifikátor organizace." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Váš požadavek byl odeslán Vašemu správci." }, - "youWillBeNotifiedOnceApproved": { - "message": "Po schválení budete upozorněni." - }, "troubleLoggingIn": { "message": "Potíže s přihlášením?" }, @@ -3396,38 +3649,6 @@ "message": "Přepnout sbalení", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Importovat data do Bitwardenu?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Chránit Vaše data LastPass a importovat do Bitwardenu?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Uložit jako nešifrovaný soubor", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Importovat do Bitwardenu", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importování...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data byla úspěšně importována!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Chyba při importu. Podrobnosti naleznete v konzoli.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Při importu došlo k chybě sítě.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Doména aliasu" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Aktivní účet" }, + "bitwardenAccount": { + "message": "Účet Bitwarden" + }, "availableAccounts": { "message": "Dostupné účty" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Návrhy automatického vyplňování" }, + "itemSuggestions": { + "message": "Navrhované položky" + }, "autofillSuggestionsTip": { "message": "Uložit přihlašovací údaje pro tuto stránku do automatického vyplňování" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Kopírovat $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "Žádné hodnoty ke zkopírování" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Název položky" }, - "cannotRemoveViewOnlyCollections": { - "message": "Nemůžete odebrat kolekce s oprávněními jen pro zobrazení: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organizace je deaktivována" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Změnit pořadí URI webové stránky. Použijte šipky pro posunutí položky nahoru nebo dolů." + }, "reorderFieldUp": { "message": "$LABEL$ - přesunuto nahoru, pozice $INDEX$ z $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "Nevybrali jste žádné položky." }, - "movedItemsToOrg": { - "message": "Vybrané položky přesunuty do $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Položky přesunuty do $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Sends s texty" }, - "bitwardenNewLook": { - "message": "Bitwarden má nový vzhled!" - }, - "bitwardenNewLookDesc": { - "message": "Je snazší a intuitivnější než kdy jindy automaticky vyplňovat a vyhledávat z karty trezor. Mrkněte se!" - }, "accountActions": { "message": "Činnosti účtu" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "Nemáte oprávnění upravit tuto položku" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrické odemknutí je nedostupné, protože je potřeba nejprve odemknout pomocí PIN nebo hesla." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrické odemknutí je momentálně nedostupné." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrické odemknutí není dostupné kvůli chybnému nastavení systémových souborů." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrické odemknutí není dostupné kvůli chybnému nastavení systémových souborů." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometrické odemknutí není dostupné, protože je aplikace Bitwarden zavřena." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometrické odemknutí není dostupné, protože není povoleno pro $EMAIL$ v desktopové aplikaci Bitwarden.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrické odemknutí je momentálně z neznámého důvodu nedostupné." + }, "authenticating": { "message": "Ověřování" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra široký" + }, + "sshKeyWrongPassword": { + "message": "Zadané heslo není správné." + }, + "importSshKey": { + "message": "Importovat" + }, + "confirmSshKeyPassword": { + "message": "Potvrdit heslo" + }, + "enterSshKeyPasswordDesc": { + "message": "Zadejte heslo pro SSH klíč." + }, + "enterSshKeyPassword": { + "message": "Zadejte heslo" + }, + "invalidSshKey": { + "message": "SSH klíč je neplatný" + }, + "sshKeyTypeUnsupported": { + "message": "Typ SSH klíče není podporován" + }, + "importSshKeyFromClipboard": { + "message": "Importovat klíč ze schránky" + }, + "sshKeyImported": { + "message": "SSH klíč byl úspěšně importován" + }, + "cannotRemoveViewOnlyCollections": { + "message": "Nemůžete odebrat kolekce s oprávněními jen pro zobrazení: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Aktualizujte aplikaci pro stolní počítač" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "Chcete-li použít biometrické odemknutí, aktualizujte aplikaci pro stolní počítač nebo v nastavení vypněte odemknutí otiskem prstů." + }, + "changeAtRiskPassword": { + "message": "Změnit ohrožené heslo" } } diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index 3bc85b10fb5..83d09d13273 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden Password Manager", + "message": "Rheolydd cyfrineiriau Bitwarden", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "message": "Gartref, yn y gweithle, neu ar fynd, mae Bitwarden yn diogelu eich holl gyfrineiriau a gwybodaeth sensitif", "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { @@ -20,7 +20,7 @@ "message": "Creu cyfrif" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Newydd i Bitwarden?" }, "logInWithPasskey": { "message": "Log in with passkey" @@ -29,7 +29,7 @@ "message": "Use single sign-on" }, "welcomeBack": { - "message": "Welcome back" + "message": "Croeso nôl" }, "setAStrongPassword": { "message": "Gosod cyfrinair cryf" @@ -80,11 +80,20 @@ "masterPassHint": { "message": "Master password hint (optional)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "Ymuno â $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -162,7 +171,7 @@ "message": "Copy fingerprint" }, "copyCustomField": { - "message": "Copy $FIELD$", + "message": "Copïo $FIELD$", "placeholders": { "field": { "content": "$1", @@ -171,13 +180,17 @@ } }, "copyWebsite": { - "message": "Copy website" + "message": "Copïo'r wefan" }, "copyNotes": { "message": "Copy notes" }, + "copy": { + "message": "Copïo", + "description": "Copy to clipboard" + }, "fill": { - "message": "Fill", + "message": "Llenwi", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." }, "autoFill": { @@ -203,7 +216,7 @@ "message": "Cynhyrchu cyfrinair (wedi'i gopïo)" }, "copyElementIdentifier": { - "message": "Copy custom field name" + "message": "Copïo enw maes addasedig" }, "noMatchingLogins": { "message": "No matching logins" @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Anfon awgrym o'ch prif gyfrinair" }, @@ -310,19 +317,19 @@ "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "twoStepLogin": { - "message": "Mewngofnodi dau agm" + "message": "Mewngofnodi dau gam" }, "logOut": { "message": "Allgofnodi" }, "aboutBitwarden": { - "message": "About Bitwarden" + "message": "Ynghylch Bitwarden" }, "about": { "message": "Ynghylch" }, "moreFromBitwarden": { - "message": "More from Bitwarden" + "message": "Mwy gan Bitwarden" }, "continueToBitwardenDotCom": { "message": "Continue to bitwarden.com?" @@ -372,6 +379,15 @@ "editFolder": { "message": "Golygu ffolder" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "Ffolder newydd" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Generate passphrase" }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "regeneratePassword": { "message": "Ailgynhyrchu cyfrinair" }, @@ -454,22 +482,6 @@ "length": { "message": "Hyd" }, - "uppercase": { - "message": "Priflythrennau (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Llythrennau bach (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Rhifau (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Nodau arbennig (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Include", "description": "Card header for password generator include block" @@ -499,13 +511,9 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "Cynnwys nodau arbennig", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Nifer o eiriau" }, @@ -526,7 +534,7 @@ "message": "Isafswm nodau arbennig" }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "Osgoi nodau amwys", "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { @@ -576,7 +584,7 @@ "message": "Nodiadau" }, "privateNote": { - "message": "Private note" + "message": "Nodyn preifat" }, "note": { "message": "Nodyn" @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Dyw eich porwr gwe ddim yn cefnogi copïo drwy'r clipfwrdd yn hawdd. Copïwch â llaw yn lle." }, - "verifyIdentity": { - "message": "Gwirio'ch hunaniaeth" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "Mae eich cell ar glo. Gwiriwch eich hunaniaeth i barhau." @@ -779,7 +793,7 @@ "message": "Mae eich cyfrif newydd wedi cael ei greu! Gallwch bellach fewngofnodi." }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "Mae eich cyfrif wedi cael ei greu!" }, "youHaveBeenLoggedIn": { "message": "You have been logged in!" @@ -849,10 +863,25 @@ "message": "Mae eich sesiwn wedi dod i ben." }, "logIn": { - "message": "Log in" + "message": "Mewngofnodi" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Mewngofnodi i Bitwarden" + }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." }, "restartRegistration": { "message": "Restart registration" @@ -875,6 +904,9 @@ "no": { "message": "Na" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "An unexpected error has occurred." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "Show cards on Tab page" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "List card items on the Tab page for easy autofill." }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "Show identities on Tab page" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "Clirio'r clipfwrdd", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Cadw" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Cadw fel manylion mewngofnodi newydd", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Ask to update existing login" }, @@ -1084,10 +1169,6 @@ "message": "Golau", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized dark", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Export from" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Prynu aelodaeth uwch" }, - "premiumPurchaseAlert": { - "message": "You can purchase Premium membership on the bitwarden.com web vault. Do you want to visit the website now?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Fy nghofio i" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Anfon ebost â chod dilysu eto" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Rhowch eich YubiKey i mewn i borth USB eich cyfrifiadur, yna gwasgwch y botwm." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Agor tab newydd" }, + "openInNewTab": { + "message": "Agor mewn tab newydd" + }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "Login unavailable" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Dewisiadau mewngofnodi dau gam" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Lost access to all of your two-factor providers? Use your recovery code to turn off all two-factor providers from your account." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Self-hosted environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Amgylchedd addasedig" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "Server URL" }, @@ -1553,7 +1647,7 @@ "message": "Autofill the last used identity for the current website" }, "commandGeneratePasswordDesc": { - "message": "Generate and copy a new random password to the clipboard" + "message": "Cynhyrchu a chopïo cyfrinair hap newydd i'r clipfwrdd" }, "commandLockVaultDesc": { "message": "Cloi'r gell" @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Drag to sort" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Testun" }, @@ -1768,10 +1865,10 @@ "message": "Hunaniaeth" }, "typeSshKey": { - "message": "SSH key" + "message": "Allwedd SSH" }, "newItemHeader": { - "message": "New $TYPE$", + "message": "$TYPE$ newydd", "placeholders": { "type": { "content": "$1", @@ -1846,7 +1943,7 @@ "message": "Nodiadau diogel" }, "sshKeys": { - "message": "SSH Keys" + "message": "Allweddi SSH" }, "clear": { "message": "Clirio", @@ -2041,16 +2138,19 @@ "message": "Clone" }, "passwordGenerator": { - "message": "Password generator" + "message": "Cynhyrchydd cyfrineiriau" }, "usernameGenerator": { - "message": "Username generator" + "message": "Cynhyrychydd enwau defnyddiwr" + }, + "useThisEmail": { + "message": "Defnyddio'r ebost hwn" }, "useThisPassword": { - "message": "Use this password" + "message": "Defnyddio'r cyfrinair hwn" }, "useThisUsername": { - "message": "Use this username" + "message": "Defnyddio'r enw defnyddiwr hwn" }, "securePasswordGenerated": { "message": "Secure password generated! Don't forget to also update your password on the website." @@ -2063,12 +2163,24 @@ "message": "to create a strong unique password", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "Vault timeout action" }, "vaultTimeoutAction1": { "message": "Timeout action" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "Cloi", "description": "Verb form: to make secure or inaccessible by" @@ -2096,7 +2208,7 @@ "message": "Item restored" }, "alreadyHaveAccount": { - "message": "Already have an account?" + "message": "Oes gennych chi gyfrif eisoes?" }, "vaultTimeoutLogOutConfirmation": { "message": "Logging out will remove all access to your vault and requires online authentication after the timeout period. Are you sure you want to use this setting?" @@ -2108,7 +2220,7 @@ "message": "Llenwi'n awtomatig a chadw" }, "fillAndSave": { - "message": "Fill and save" + "message": "Llenwi a chadw" }, "autoFillSuccessAndSavedUri": { "message": "Item autofilled and URI saved" @@ -2324,6 +2436,12 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "Parthau wedi'u heithrio" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Gwall" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Cynhyrchu enw defnyddiwr" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Settings have been edited" - }, - "environmentEditedClick": { - "message": "Cliciwch yma" - }, - "environmentEditedReset": { - "message": "to reset to pre-configured settings" - }, "serverVersion": { "message": "Fersiwn y gweinydd" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Mewngofnodi â'ch prif gyfrinair" }, - "loggingInAs": { - "message": "Yn mewngofnodi fel" - }, - "notYou": { - "message": "Nid chi?" - }, "newAroundHere": { "message": "Ydych chi'n newydd?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Mewngofnodi â dyfais" }, - "loginWithDeviceEnabledInfo": { - "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" - }, "fingerprintPhraseHeader": { "message": "Ymadrodd unigryw" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "View all log in options" }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" - }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "You will be notified once the request is approved" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trafferth mewngofnodi?" }, @@ -3396,38 +3649,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Hoffech chi fewnforio'ch data i Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Yn mewnforio...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Cafodd y data ei fewnforio'n llwyddiannus!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, @@ -3506,7 +3727,7 @@ "description": "Screen reader text (aria-label) for new item button in overlay" }, "newLogin": { - "message": "New login", + "message": "Manylion mewngofnodi newydd", "description": "Button text to display within inline menu when there are no matching items on a login field" }, "addNewLoginItemAria": { @@ -3514,7 +3735,7 @@ "description": "Screen reader text (aria-label) for new login button within inline menu" }, "newCard": { - "message": "New card", + "message": "Cerdyn newydd", "description": "Button text to display within inline menu when there are no matching items on a credit card field" }, "addNewCardItemAria": { @@ -3522,7 +3743,7 @@ "description": "Screen reader text (aria-label) for new card button within inline menu" }, "newIdentity": { - "message": "New identity", + "message": "Hunaniaeth newydd", "description": "Button text to display within inline menu when there are no matching items on an identity field" }, "addNewIdentityItemAria": { @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Cyfrif gweithredol" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Cyfrifon ar gael" }, @@ -3979,7 +4203,10 @@ "message": "Passkey removed" }, "autofillSuggestions": { - "message": "Autofill suggestions" + "message": "Awgrymiadau" + }, + "itemSuggestions": { + "message": "Suggested items" }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "No values to copy" }, @@ -4072,10 +4313,10 @@ "message": "Admin Console" }, "accountSecurity": { - "message": "Account security" + "message": "Diogelwch eich cyfrif" }, "notifications": { - "message": "Notifications" + "message": "Hysbysiadau" }, "appearance": { "message": "Golwg" @@ -4107,7 +4348,7 @@ } }, "new": { - "message": "New" + "message": "Newydd" }, "removeItem": { "message": "Remove $NAME$", @@ -4128,15 +4369,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organization is deactivated" }, @@ -4403,7 +4635,7 @@ "message": "Edit field" }, "editFieldLabel": { - "message": "Edit $LABEL$", + "message": "Golygu $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4412,7 +4644,7 @@ } }, "deleteCustomField": { - "message": "Delete $LABEL$", + "message": "Dileu $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4421,7 +4653,7 @@ } }, "fieldAdded": { - "message": "$LABEL$ added", + "message": "Ychwanegwyd $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "You have not selected anything." }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, @@ -4579,16 +4799,16 @@ "message": "Enterprise policy requirements have been applied to this setting" }, "sshPrivateKey": { - "message": "Private key" + "message": "Allwedd breifat" }, "sshPublicKey": { - "message": "Public key" + "message": "Allwedd gyhoeddus" }, "sshFingerprint": { "message": "Fingerprint" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "Math o allwedd" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4819,7 +5066,7 @@ "message": "Remind me later" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", + "message": "A oes gennych chi fynediad dibynadwy i'ch ebost, $EMAIL$?", "placeholders": { "email": { "content": "$1", @@ -4828,10 +5075,10 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "Nac oes" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" + "message": "Oes, mae gen i fynediad dibynadwy i fy ebost" }, "turnOnTwoStepLogin": { "message": "Turn on two-step login" @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra wide" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index 433cdebbb17..69c8b28d29a 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Hovedadgangskodetip (valgfrit)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Bliv medlem af organisation" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Kopiér notater" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { "message": "Udfyld", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Angiv kontoens e-mailadresse og få et adgangskodetip fremsendt" }, - "passwordHint": { - "message": "Adgangskodetip" - }, - "enterEmailToGetHint": { - "message": "Indtast din kontos e-mailadresse for at modtage dit hovedadgangskodetip." - }, "getMasterPasswordHint": { "message": "Få hovedadgangskodetip" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Redigér mappe" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "Ny mappe" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Generér adgangssætning" }, + "passwordGenerated": { + "message": "Adgangskode genereret" + }, + "passphraseGenerated": { + "message": "Adgangssætning genereret" + }, + "usernameGenerated": { + "message": "Brugernavn genereret" + }, + "emailGenerated": { + "message": "E-mail genereret" + }, "regeneratePassword": { "message": "Regenerér adgangskode" }, @@ -454,22 +482,6 @@ "length": { "message": "Længde" }, - "uppercase": { - "message": "Store bogstaver (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Små bogstaver (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Tal (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Specialtegn (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Inkludér", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Inkludér specialtegn", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Antal ord" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Din webbrowser understøtter ikke udklipsholder kopiering. Kopiér det manuelt i stedet." }, - "verifyIdentity": { - "message": "Bekræft identitet" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "Denne enhed er ikke genkendt. Angiv koden i den tilsendte e-mail for at bekræfte identiteten." + }, + "continueLoggingIn": { + "message": "Fortsæt med at logge ind" }, "yourVaultIsLocked": { "message": "Din boks er låst. Bekræft din identitet for at fortsætte." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Log ind på Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Genstart registrering" }, @@ -875,6 +904,9 @@ "no": { "message": "Nej" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "Der opstod en uventet fejl." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Anmod om at tilføje et emne, hvis intet ikke findes i boksen. Gælder alle indloggede konti." }, - "showCardsInVaultView": { - "message": "Vis kort som Autoudfyldningsforslag ved Boks-visning" + "showCardsInVaultViewV2": { + "message": "Vis altid kort som Autoudfyldningsforslag ved Boks-visning" }, "showCardsCurrentTab": { "message": "Vis kort på fanebladet" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Vis kortemner på siden Fane for nem autoudfyldning." }, - "showIdentitiesInVaultView": { - "message": "Vis identiteter som Autoudfyldningsforslag ved Boks-visning" + "showIdentitiesInVaultViewV2": { + "message": "Vis altid identiteter som Autoudfyldningsforslag ved Boks-visning" }, "showIdentitiesCurrentTab": { "message": "Vis identiteter på fanebladet" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Klik på emner for at autoudfylde i Boks-visning" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "Ryd udklipsholder", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Gem" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Bed om at opdatere eksisterende login" }, @@ -1084,10 +1169,6 @@ "message": "Lys", "description": "Light color" }, - "solarizedDark": { - "message": "Solariseret mørk", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Eksportér fra" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Køb premium" }, - "premiumPurchaseAlert": { - "message": "Du kan købe premium-medlemskab i bitwarden.com web-boksen. Vil du besøge hjemmesiden nu?" - }, "premiumPurchaseAlertV2": { "message": "Der kan købes Premium fra kontoindstillingerne via Bitwarden web-appen." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Husk mig" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verifikationskode-email igen" }, "useAnotherTwoStepMethod": { "message": "Brug en anden to-trins login metode" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Indsæt din YubiKey i din computers USB-port, og tryk derefter på dens knap." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Åbn ny fane" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "Godkend WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "Login ikke tilgængelig" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "To-trins-login indstillinger" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Mistet adgang til alle dine to-faktor-udbydere? Brug din genoprettelseskode til at deaktivere alle to-faktor udbydere på din konto." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Selv-hosted miljø" }, - "selfHostedEnvironmentFooter": { - "message": "Angiv grund-URL'en i din lokal-hostede Bitwarden-installation." - }, "selfHostedBaseUrlHint": { "message": "Angiv basis-URL'en for den lokalt-hosted Bitwarden-installation. Eks.: https://bitwarden.firma.dk" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Brugerdefineret miljø" }, - "customEnvironmentFooter": { - "message": "Til avancerede brugere. Du kan angive grund-URL'en for hver service uafhængigt." - }, "baseUrl": { "message": "Server URL" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Træk for at sortere" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Tekst" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Brugernavngenerator" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "Anvend denne adgangskode" }, @@ -2063,12 +2163,24 @@ "message": "til at oprette en stærk, unik adgangskode", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "Boks timeout-handling" }, "vaultTimeoutAction1": { "message": "Timeouthandling" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "Lås", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domæner", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blokerede domæner" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "Ekskluderede domæner" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden vil ikke anmode om at gemme login-detaljer for disse domæner for alle indloggede konti. Siden skal opfriskes for at effektuere ændringerne." }, + "blockedDomainsDesc": { + "message": "Autofyldning og andre relaterede funktioner tilbydes ikke på disse websteder. Siden skal opdateres for at effektuere ændringerne." + }, + "autofillBlockedNoticeV2": { + "message": "Autoudfyldning blokeret for dette websted." + }, + "autofillBlockedNoticeGuidance": { + "message": "Ændr dette i Indstillinger" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "Websted $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blokeret domæne-ændringer gemt" + }, "excludedDomainsSavedSuccess": { "message": "Ekskluderet domæne-ændringer gemt" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Fejl" }, + "decryptionError": { + "message": "Dekrypteringsfejl" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden kunne ikke dekryptere boks-emne(r) anført nedenfor." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontakt kundeservice", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "for at undgå yderligere tab af data.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generér brugernavn" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Kan ikke få $SERVICENAME$ maskeret e-mailkonto-ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Indstillinger er blevet redigeret" - }, - "environmentEditedClick": { - "message": "Klik her" - }, - "environmentEditedReset": { - "message": "for at nulstille til forudkonfigurerede indstillinger" - }, "serverVersion": { "message": "Server version" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Log ind med hovedadgangskode" }, - "loggingInAs": { - "message": "Logger ind som" - }, - "notYou": { - "message": "Ikke dig?" - }, "newAroundHere": { "message": "Ny her?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Log ind med enhed" }, - "loginWithDeviceEnabledInfo": { - "message": "Log ind med enhed skal være opsat i indstillingerne i Bitwarden mobil-appen. Behov for en anden mulighed?" - }, "fingerprintPhraseHeader": { "message": "Fingeraftrykssætning" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "Vis alle indlogningsmuligheder" }, - "viewAllLoginOptionsV1": { - "message": "Vis alle indlogningsmuligheder" - }, "notificationSentDevice": { "message": "En notifikation er sendt til din enhed." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "En notifikation er sendt til enheden" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Sørg for, at boksen er oplåst, samt at fingeraftrykssætningen matcher på den anden enhed" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "Man vil få besked, når anmodningen er godkendt" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Indlogning påbegyndt" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Kompromitteret hovedadgangskode" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Anmod om admin-godkendelse" }, - "approveWithMasterPassword": { - "message": "Godkend med hovedadgangskode" - }, "ssoIdentifierRequired": { "message": "Organisations SSO-identifikator kræves." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Anmodningen er sendt til din admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "Du underrettes, når godkendelse foreligger." - }, "troubleLoggingIn": { "message": "Problemer med at logge ind?" }, @@ -3396,38 +3649,6 @@ "message": "Fold sammen/ud", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Importér data til Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Beskyt LastPass-data og importér dem til Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Gem som ukrypteret fil", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Importér til Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importerer...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data er nu importeret!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Fejl under import. Tjek konsollen for detaljer.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Netværksfejl opstod under import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Aliasdomæne" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Aktiv konto" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Tilgængelige konti" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Autoudfyldningsforslag" }, + "itemSuggestions": { + "message": "Foreslåede emner" + }, "autofillSuggestionsTip": { "message": "Gem et loginemne for dette websted til autoudfyldning" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "Ingen værdier at kopiere" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Emnenavn" }, - "cannotRemoveViewOnlyCollections": { - "message": "Samlinger med kun tilladelsen Vis kan ikke fjernes: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organisation er deaktiveret" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ flyttet op, position $INDEX$ af $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "Ingenting er valgt." }, - "movedItemsToOrg": { - "message": "Valgte emner flyttet til $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Emner flyttet til $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Tekst-Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden har fået et nyt look!" - }, - "bitwardenNewLookDesc": { - "message": "Det er lettere og mere intuitivt end nogensinde at autoudfylde og søge via fanen Boks. Tag et kig omkring!" - }, "accountActions": { "message": "Kontohandlinger" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "Ingen tilladelse til at redigere dette emne" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrisk oplåsning er utilgængelig, da PIN- eller adgangskode kræves først." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrisk oplåsning er p.t. utilgængelig." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrisk oplåsning er utilgængelig grundet fejlopsatte systemfiler." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrisk oplåsning er utilgængelig grundet fejlopsatte systemfiler." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometrisk oplåsning er utilgængelig, da Bitwarden-appen er lukket." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometrisk oplåsning er utilgængelig, da det ikke er aktiveret for $EMAIL$ i Bitwarden computer-appen.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrisk oplåsning er p.t. utilgængelig grundet en ukendt årsag." + }, "authenticating": { "message": "Godkender" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Ekstra bred" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "Samlinger med kun tilladelsen Vis kan ikke fjernes: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Opdatér venligst computerapplikationen" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "For brug af biometrisk oplåsning skal computerapplikationen opdateres eller fingeraftryksoplåsning deaktiveres i computerindstillingerne." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index 2fc5297205a..a7439db6432 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Master-Passwort-Hinweis (optional)" }, + "passwordStrengthScore": { + "message": "Bewertung der Passwortstärke $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Organisation beitreten" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Notizen kopieren" }, + "copy": { + "message": "Kopieren", + "description": "Copy to clipboard" + }, "fill": { "message": "Ausfüllen", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Gib deine E-Mail-Adresse ein und dein Passwort-Hinweis wird dir zugesandt" }, - "passwordHint": { - "message": "Passwort-Hinweis" - }, - "enterEmailToGetHint": { - "message": "Gib die E-Mail-Adresse deines Kontos ein, um den Hinweis für dein Master-Passwort zu erhalten." - }, "getMasterPasswordHint": { "message": "Hinweis zum Masterpasswort erhalten" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Ordner bearbeiten" }, + "editFolderWithName": { + "message": "Ordner bearbeiten: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "Neuer Ordner" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Passphrase generieren" }, + "passwordGenerated": { + "message": "Passwort generiert" + }, + "passphraseGenerated": { + "message": "Passphrase generiert" + }, + "usernameGenerated": { + "message": "Benutzername generiert" + }, + "emailGenerated": { + "message": "E-Mail-Adresse generiert" + }, "regeneratePassword": { "message": "Passwort neu generieren" }, @@ -454,22 +482,6 @@ "length": { "message": "Länge" }, - "uppercase": { - "message": "Großbuchstaben (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Kleinbuchstaben (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Zahlen (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Sonderzeichen (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Einschließen", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Sonderzeichen einschließen", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Anzahl der Wörter" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Den Browser unterstützt das einfache Kopieren nicht. Bitte kopiere es manuell." }, - "verifyIdentity": { - "message": "Identität verifizieren" + "verifyYourIdentity": { + "message": "Verifiziere deine Identität" + }, + "weDontRecognizeThisDevice": { + "message": "Wir erkennen dieses Gerät nicht. Gib den an deine E-Mail-Adresse gesendeten Code ein, um deine Identität zu verifizieren." + }, + "continueLoggingIn": { + "message": "Anmeldung fortsetzen" }, "yourVaultIsLocked": { "message": "Dein Tresor ist gesperrt. Verifiziere deine Identität, um fortzufahren." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Bei Bitwarden anmelden" }, + "enterTheCodeSentToYourEmail": { + "message": "Gib den an deine E-Mail-Adresse gesendeten Code ein" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Gib den Code aus deiner Authenticator-App ein" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Drücke zum Authentifizieren auf deinen YubiKey" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Die Duo-Zwei-Faktor-Authentifizierung ist für dein Konto erforderlich. Folge den Schritten unten, um die Anmeldung abzuschließen." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Folge den Schritten unten, um die Anmeldung abzuschließen." + }, "restartRegistration": { "message": "Registrierung neu starten" }, @@ -875,6 +904,9 @@ "no": { "message": "Nein" }, + "location": { + "message": "Standort" + }, "unexpectedError": { "message": "Ein unerwarteter Fehler ist aufgetreten." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Nach dem Hinzufügen eines Eintrags fragen, wenn er nicht in deinem Tresor gefunden wurde. Gilt für alle angemeldeten Konten." }, - "showCardsInVaultView": { - "message": "Karten als Vorschläge zum Auto-Ausfüllen in der Tresor-Ansicht anzeigen" + "showCardsInVaultViewV2": { + "message": "Karten immer als Auto-Ausfüllen-Vorschläge in der Tresor-Ansicht anzeigen" }, "showCardsCurrentTab": { "message": "Karten auf Tab Seite anzeigen" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Karten-Einträge auf der Tab Seite anzeigen, um das Auto-Ausfüllen zu vereinfachen." }, - "showIdentitiesInVaultView": { - "message": "Identitäten als Vorschläge zum Auto-Ausfüllen in der Tresor-Ansicht anzeigen" + "showIdentitiesInVaultViewV2": { + "message": "Identitäten immer als Auto-Ausfüllen-Vorschläge in der Tresor-Ansicht anzeigen" }, "showIdentitiesCurrentTab": { "message": "Identitäten auf Tab Seite anzeigen" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Klicke auf Einträge zum automatischen Ausfüllen in der Tresor-Ansicht" }, + "clickToAutofill": { + "message": "Klick auf Einträge im Auto-Ausfüllen-Vorschlag zum Ausfüllen" + }, "clearClipboard": { "message": "Zwischenablage leeren", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Ja, jetzt speichern" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ in Bitwarden gespeichert.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ in Bitwarden aktualisiert.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Als neue Zugangsdaten speichern", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Zugangsdaten aktualisieren", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Zugangsdaten speichern?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Bestehende Zugangsdaten aktualisieren?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Zugangsdaten gespeichert", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Zugangsdaten aktualisiert", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Fehler beim Speichern", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh nein! Das konnten wir nicht speichern. Versuch, die Details manuell einzugeben.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Nach dem Aktualisieren bestehender Zugangsdaten fragen" }, @@ -1084,10 +1169,6 @@ "message": "Hell", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized Dark", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Export aus" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Premium-Mitgliedschaft kaufen" }, - "premiumPurchaseAlert": { - "message": "Du kannst deine Premium-Mitgliedschaft im Bitwarden.com Web-Tresor kaufen. Möchtest du die Website jetzt besuchen?" - }, "premiumPurchaseAlertV2": { "message": "Du kannst Premium über deine Kontoeinstellungen in der Bitwarden Web-App kaufen." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Angemeldet bleiben" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Für 30 Tage auf diesem Gerät nicht mehr fragen" + }, "sendVerificationCodeEmailAgain": { "message": "E-Mail mit Bestätigungscode erneut versenden" }, "useAnotherTwoStepMethod": { "message": "Verwende eine andere zweistufige Login-Methode" }, + "selectAnotherMethod": { + "message": "Wähle eine andere Methode", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Verwende deinen Wiederherstellungscode" + }, "insertYubiKey": { "message": "Stecke deinen YubiKey in den USB-Port Ihres Computers, dann berühre den Button." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Neuen Tab öffnen" }, + "openInNewTab": { + "message": "In neuem Tab öffnen" + }, "webAuthnAuthenticate": { "message": "Authentifiziere WebAuthn" }, + "readSecurityKey": { + "message": "Sicherheitsschlüssel auslesen" + }, + "awaitingSecurityKeyInteraction": { + "message": "Warte auf Sicherheitsschlüssel-Interaktion..." + }, "loginUnavailable": { "message": "Anmeldung nicht verfügbar" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Optionen für Zwei-Faktor-Authentifizierung" }, + "selectTwoStepLoginMethod": { + "message": "Zwei-Faktor-Authentifizierungsmethode auswählen" + }, "recoveryCodeDesc": { "message": "Zugang zu allen Zwei-Faktor Anbietern verloren? Benutze deinen Wiederherstellungscode, um alle Zwei-Faktor Anbieter in deinem Konto zu deaktivieren." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Selbst gehostete Umgebung" }, - "selfHostedEnvironmentFooter": { - "message": "Bitte gib die Basis-URL deiner selbst gehosteten Bitwarden-Installation an." - }, "selfHostedBaseUrlHint": { "message": "Gib die Basis-URL deiner vor Ort gehosteten Bitwarden-Installation an. Beispiel: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Benutzerdefinierte Umgebung" }, - "customEnvironmentFooter": { - "message": "Für fortgeschrittene Benutzer. Du kannst die Basis-URL der jeweiligen Dienste unabhängig voneinander festlegen." - }, "baseUrl": { "message": "Server URL" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Zum Sortieren ziehen" }, + "dragToReorder": { + "message": "Ziehen zum Umsortieren" + }, "cfTypeText": { "message": "Text" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Benutzernamen-Generator" }, + "useThisEmail": { + "message": "Diese E-Mail-Adresse verwenden" + }, "useThisPassword": { "message": "Dieses Passwort verwenden" }, @@ -2063,12 +2163,24 @@ "message": ", um ein starkes einzigartiges Passwort zu erstellen", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Tresor-Personalisierung" + }, "vaultTimeoutAction": { "message": "Aktion bei Tresor-Timeout" }, "vaultTimeoutAction1": { "message": "Timeout-Aktion" }, + "newCustomizationOptionsCalloutTitle": { + "message": "Neue Personalisierungs-Optionen" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Personalisiere deinen Tresor mit Schnellkopier-Aktionen, Kompaktmodus und mehr!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "Alle Aussehen-Einstellungen anzeigen" + }, "lock": { "message": "Sperren", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Gesperrte Domains" + }, + "learnMoreAboutBlockedDomains": { + "message": "Erfahre mehr über blockierte Domains" + }, "excludedDomains": { "message": "Ausgeschlossene Domains" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden wird für alle angemeldeten Konten nicht danach fragen Zugangsdaten für diese Domains speichern. Du musst die Seite neu laden, damit die Änderungen wirksam werden." }, + "blockedDomainsDesc": { + "message": "Automatisches Ausfüllen und andere zugehörige Funktionen werden für diese Webseiten nicht angeboten. Du musst die Seite neu laden, damit die Änderungen wirksam werden." + }, + "autofillBlockedNoticeV2": { + "message": "Automatisches Ausfüllen ist für diese Website gesperrt." + }, + "autofillBlockedNoticeGuidance": { + "message": "Dies in den Einstellungen ändern" + }, + "change": { + "message": "Ändern" + }, + "changeButtonTitle": { + "message": "Passwort ändern - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "Gefährdete Passwörter" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ fordert dich auf, ein Passwort zu ändern, da es gefährdet ist.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ fordert dich auf, diese $COUNT$ Passwörter zu ändern, da diese gefährdet sind.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Deine Organisationen fordern dich auf, diese $COUNT$ Passwörter zu ändern, da diese gefährdet sind.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Überprüfe und ändere ein gefährdetes Passwort" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Überprüfe und ändere $COUNT$ gefährdete Passwörter", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Ändere gefährdete Passwörter schneller" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Aktualisiere deine Einstellungen, damit du deine Passwörter schnell automatisch ausfüllen kannst und neue generieren kannst" + }, + "reviewAtRiskLogins": { + "message": "Überprüfung gefährdeter Zugangsdaten" + }, + "reviewAtRiskPasswords": { + "message": "Überprüfung gefährdeter Passwörter" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Die Passwörter deiner Organisationen sind gefährdet, weil sie schwach, wiederverwendet und/oder kompromittiert sind.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration einer Liste gefährdeter Zugangsdaten" + }, + "generatePasswordSlideDesc": { + "message": "Generiere schnell ein starkes, einzigartiges Passwort mit dem Bitwarden Auto-Ausfüllen-Menü auf der gefährdeten Website.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration des Bitwarden Auto-Ausfüllen-Menüs, das ein generiertes Passwort anzeigt" + }, + "updateInBitwarden": { + "message": "In Bitwarden aktualisieren" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden wird dich dann auffordern, das Passwort im Passwort-Manager zu aktualisieren.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration einer Bitwarden-Benachrichtigung, die den Benutzer dazu auffordert, die Zugangsdaten zu aktualisieren" + }, + "turnOnAutofill": { + "message": "Auto-Ausfüllen aktivieren" + }, + "turnedOnAutofill": { + "message": "Auto-Ausfüllen aktiviert" + }, + "dismiss": { + "message": "Verwerfen" + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Änderungen gesperrter Domains gespeichert" + }, "excludedDomainsSavedSuccess": { "message": "Änderungen der ausgeschlossenen Domain gespeichert" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Fehler" }, + "decryptionError": { + "message": "Entschlüsselungsfehler" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden konnte folgende(n) Tresor-Eintrag/Einträge nicht entschlüsseln." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktiere unser Customer Success Team", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": ", um zusätzlichen Datenverlust zu vermeiden.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Benutzername generieren" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ hat deine Anfrage abgelehnt. Bitte wende dich an deinen Dienstanbieter für Unterstützung.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ hat deine Anfrage abgelehnt: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Maskierte E-Mail-Konto-ID für $SERVICENAME$ konnte nicht abgerufen werden.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Einstellungen wurden bearbeitet" - }, - "environmentEditedClick": { - "message": "Hier klicken" - }, - "environmentEditedReset": { - "message": "um auf vorkonfigurierte Einstellungen zurückzusetzen" - }, "serverVersion": { "message": "Server-Version" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Mit Master-Passwort anmelden" }, - "loggingInAs": { - "message": "Anmelden als" - }, - "notYou": { - "message": "Nicht du?" - }, "newAroundHere": { "message": "Neu hier?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Mit Gerät anmelden" }, - "loginWithDeviceEnabledInfo": { - "message": "Die Anmeldung über ein Gerät muss in den Einstellungen der Bitwarden App eingerichtet werden. Benötigst du eine andere Option?" - }, "fingerprintPhraseHeader": { "message": "Fingerabdruck-Phrase" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "Alle Anmeldeoptionen anzeigen" }, - "viewAllLoginOptionsV1": { - "message": "Alle Anmeldeoptionen anzeigen" - }, "notificationSentDevice": { "message": "Eine Benachrichtigung wurde an dein Gerät gesendet." }, + "notificationSentDevicePart1": { + "message": "Entsperre Bitwarden auf deinem Gerät oder mit der" + }, + "notificationSentDeviceAnchor": { + "message": "Web-App" + }, + "notificationSentDevicePart2": { + "message": "Stelle vor der Genehmigung sicher, dass die Fingerabdruck-Phrase mit der unten stehenden übereinstimmt." + }, "aNotificationWasSentToYourDevice": { "message": "Eine Benachrichtigung wurde an dein Gerät gesendet" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Stelle sicher, dass dein Konto entsperrt ist und die Fingerabdruck-Phrase mit der vom anderen Gerät übereinstimmt" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "Du wirst benachrichtigt, sobald die Anfrage genehmigt wurde" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Anmeldung eingeleitet" }, + "logInRequestSent": { + "message": "Anfrage gesendet" + }, "exposedMasterPassword": { "message": "Kompromittiertes Master-Passwort" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Admin-Genehmigung anfragen" }, - "approveWithMasterPassword": { - "message": "Mit Master-Passwort genehmigen" - }, "ssoIdentifierRequired": { "message": "SSO-Kennung der Organisation erforderlich." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Deine Anfrage wurde an deinen Administrator gesendet." }, - "youWillBeNotifiedOnceApproved": { - "message": "Nach einer Genehmigung wirst du benachrichtigt." - }, "troubleLoggingIn": { "message": "Probleme beim Anmelden?" }, @@ -3396,38 +3649,6 @@ "message": "Ein-/ausklappen", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Deine Daten in Bitwarden importieren?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Deine LastPass-Daten schützen und in Bitwarden importieren?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Als unverschlüsselte Datei speichern", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "In Bitwarden importieren", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Wird importiert...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Daten erfolgreich importiert!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Fehler beim Importieren. Überprüfe die Konsole für Details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Netzwerkfehler beim Importieren.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias-Domain" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Aktives Konto" }, + "bitwardenAccount": { + "message": "Bitwarden-Konto" + }, "availableAccounts": { "message": "Verfügbare Konten" }, @@ -3930,7 +4154,7 @@ "description": "Dialog title facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutofillDescription": { - "message": "Das Ignorieren dieser Option kann zu Konflikten zwischen dem Bitwarden Auto-Ausfüllen Menü und dem Browser führen.", + "message": "Das Ignorieren dieser Option kann zu Konflikten zwischen den Bitwarden Vorschlägen zum Auto-Ausfüllen und denen deines Browsers führen.", "description": "Dialog message facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutoFillSettings": { @@ -3979,10 +4203,13 @@ "message": "Passkey entfernt" }, "autofillSuggestions": { - "message": "Vorschläge zum Auto-Ausfüllen" + "message": "Auto-Ausfüllen-Vorschläge" + }, + "itemSuggestions": { + "message": "Vorgeschlagene Einträge" }, "autofillSuggestionsTip": { - "message": "Speichere einen Login-Eintrag für diese Seite zum automatischen Ausfüllen" + "message": "Speichere einen Zugangsdaten-Eintrag für diese Seite zum automatischen Ausfüllen" }, "yourVaultIsEmpty": { "message": "Dein Tresor hat keine Einträge" @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "$FIELD$, $VALUE$ kopieren", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "Keine Werte zum Kopieren" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Eintrags-Name" }, - "cannotRemoveViewOnlyCollections": { - "message": "Du kannst Sammlungen mit Leseberechtigung nicht entfernen: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organisation ist deaktiviert" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Website-URI umsortieren. Verwende die Pfeiltasten, um den Eintrag nach oben oder unten zu bewegen." + }, "reorderFieldUp": { "message": "$LABEL$ nach oben verschoben, Position $INDEX$ von $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "Du hast nichts ausgewählt." }, - "movedItemsToOrg": { - "message": "Ausgewählte Einträge in $ORGNAME$ verschoben", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Einträge verschoben nach $ORGNAME$", "placeholders": { @@ -4557,17 +4783,11 @@ "textSends": { "message": "Text-Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden hat einen neuen Look!" - }, - "bitwardenNewLookDesc": { - "message": "Auto-Ausfüllen und Suchen vom Tresor-Tab ist einfacher und intuitiver als je zuvor. Schau dich um!" - }, "accountActions": { "message": "Konto-Aktionen" }, "showNumberOfAutofillSuggestions": { - "message": "Anzahl der Vorschläge zum Auto-Ausfüllen von Zugangsdaten auf dem Erweiterungssymbol anzeigen" + "message": "Anzahl der Auto-Ausfüllen-Vorschläge von Zugangsdaten auf dem Erweiterungssymbol anzeigen" }, "showQuickCopyActions": { "message": "Schnellkopier-Aktionen im Tresor anzeigen" @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "Du bist nicht berechtigt, diesen Eintrag zu bearbeiten" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrisches Entsperren ist nicht verfügbar, da zuerst mit PIN oder Passwort entsperrt werden muss." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrisches Entsperren ist derzeit nicht verfügbar." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrisches Entsperren ist aufgrund falsch konfigurierter Systemdateien nicht verfügbar." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrisches Entsperren ist aufgrund falsch konfigurierter Systemdateien nicht verfügbar." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometrisches Entsperren ist nicht verfügbar, da die Bitwarden Desktop-App geschlossen ist." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometrisches Entsperren ist nicht verfügbar, da es für $EMAIL$ in der Bitwarden Desktop-App nicht aktiviert ist.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrisches Entsperren ist derzeit aus einem unbekannten Grund nicht verfügbar." + }, "authenticating": { "message": "Authentifizierung" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra breit" + }, + "sshKeyWrongPassword": { + "message": "Dein eingegebenes Passwort ist falsch." + }, + "importSshKey": { + "message": "Importieren" + }, + "confirmSshKeyPassword": { + "message": "Passwort bestätigen" + }, + "enterSshKeyPasswordDesc": { + "message": "Gib das Passwort für den SSH-Schlüssel ein." + }, + "enterSshKeyPassword": { + "message": "Passwort eingeben" + }, + "invalidSshKey": { + "message": "Der SSH-Schlüssel ist ungültig" + }, + "sshKeyTypeUnsupported": { + "message": "Der SSH-Schlüsseltyp wird nicht unterstützt" + }, + "importSshKeyFromClipboard": { + "message": "Schlüssel aus Zwischenablage importieren" + }, + "sshKeyImported": { + "message": "SSH-Schlüssel erfolgreich importiert" + }, + "cannotRemoveViewOnlyCollections": { + "message": "Du kannst Sammlungen mit Leseberechtigung nicht entfernen: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Bitte aktualisiere deine Desktop-Anwendung" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "Um biometrisches Entsperren zu verwenden, aktualisiere bitte deine Desktop-Anwendung oder deaktiviere die Entsperrung per Fingerabdruck in den Desktop-Einstellungen." + }, + "changeAtRiskPassword": { + "message": "Gefährdetes Passwort ändern" } } diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index a33bc4d0e20..fc07f12a48f 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Υπόδειξη κύριου κωδικού πρόσβασης (προαιρετικό)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Συμμετοχή στον οργανισμό" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Αντιγραφή σημειώσεων" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { "message": "Συμπλήρωση", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Εισαγάγετε τη διεύθυνση email του λογαριασμού σας και θα σας αποσταλεί η υπόδειξη για τον κωδικό πρόσβασης" }, - "passwordHint": { - "message": "Υπόδειξη κωδικού πρόσβασης" - }, - "enterEmailToGetHint": { - "message": "Εισαγάγετε τη διεύθυνση email του λογαριασμού σας, για να λάβετε την υπόδειξη του κύριου κωδικού." - }, "getMasterPasswordHint": { "message": "Λήψη υπόδειξης κύριου κωδικού" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Επεξεργασία φακέλου" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "Νέος φάκελος" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Δημιουργία φράσης πρόσβασης" }, + "passwordGenerated": { + "message": "Ο κωδικός δημιουργήθηκε" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "regeneratePassword": { "message": "Επαναδημιουργία κωδικού πρόσβασης" }, @@ -454,22 +482,6 @@ "length": { "message": "Μήκος" }, - "uppercase": { - "message": "Κεφαλαία (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Πεζά (α-ω)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Αριθμοί (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Ειδικοί χαρακτήρες (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Συμπερίληψη", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Συμπερίληψη ειδικών χαρακτήρων", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Αριθμός λέξεων" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Το πρόγραμμα περιήγησης ιστού δεν υποστηρίζει εύκολη αντιγραφή πρόχειρου. Αντιγράψτε το με το χέρι αντ'αυτού." }, - "verifyIdentity": { - "message": "Επιβεβαίωση ταυτότητας" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "Το vault σας είναι κλειδωμένο. Επαληθεύστε τον κύριο κωδικό πρόσβασης για να συνεχίσετε." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Σύνδεση στο Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Επανεκκίνηση εγγραφής" }, @@ -875,6 +904,9 @@ "no": { "message": "Όχι" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "Παρουσιάστηκε ένα μη αναμενόμενο σφάλμα." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Ζητήστε να προσθέσετε ένα αντικείμενο αν δε βρεθεί στο θησαυ/κιό σας. Ισχύει για όλους τους συνδεδεμένους λογαριασμούς." }, - "showCardsInVaultView": { - "message": "Εμφάνιση καρτών ως προτάσεις αυτόματης συμπλήρωσης στην προβολή Θησαυ/κίου" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "Εμφάνιση καρτών στη σελίδα Καρτέλας" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Εμφάνισε τα αντικείμενα κάρτες στη σελίδα Καρτέλα για εύκολη αυτόματη συμπλήρωση." }, - "showIdentitiesInVaultView": { - "message": "Εμφάνιση ταυτοτήτων ως προτάσεις αυτόματης συμπλήρωσης στην προβολή Θησαυ/κίου" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "Εμφάνιση ταυτοτήτων στη σελίδα καρτέλας" @@ -1005,7 +1037,10 @@ "message": "Λίστα στοιχείων ταυτότητας στη σελίδα Καρτέλας για εύκολη αυτόματη συμπλήρωση." }, "clickToAutofillOnVault": { - "message": "Click items to autofill on Vault view" + "message": "Κάντε κλικ στα αντικείμενα για αυτόματη συμπλήρωση στην προβολή Θησαυ/κίου" + }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" }, "clearClipboard": { "message": "Εκκαθάριση Πρόχειρου", @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Ναι, Αποθήκευση Τώρα" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Ζητήστε να ενημερώσετε την υπάρχουσα σύνδεση" }, @@ -1084,10 +1169,6 @@ "message": "Φωτεινό", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized Σκούρο", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Εξαγωγή από" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Αγορά Premium έκδοσης" }, - "premiumPurchaseAlert": { - "message": "Μπορείτε να αγοράσετε συνδρομή Premium στο διαδικτυακό θησαυ/κιο του bitwarden.com. Θέλετε να επισκεφθείτε την ιστοσελίδα τώρα;" - }, "premiumPurchaseAlertV2": { "message": "Μπορείτε να αγοράσετε το Premium από τις ρυθμίσεις του λογαριασμού σας στην διαδικτυακή εφαρμογή Bitwarden." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Να με θυμάσαι" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Αποστολή email κωδικού επαλήθευσης ξανά" }, "useAnotherTwoStepMethod": { "message": "Χρήση άλλης μεθόδου σύνδεσης δύο παραγόντων" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Τοποθετήστε το YubiKey στη θύρα USB του υπολογιστή σας και έπειτα κάντε κλικ στο κουμπί του." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Άνοιγμα νέας καρτέλας" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "Ταυτοποίηση WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "Μη διαθέσιμη σύνδεση" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Επιλογές σύνδεσης δύο βημάτων" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Έχετε χάσει την πρόσβαση σε όλους τους παρόχους δύο παραγόντων; Χρησιμοποιήστε τον κωδικό ανάκτησης για να απενεργοποιήσετε όλους τους παρόχους δύο παραγόντων από το λογαριασμό σας." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Αυτο-φιλοξενούμενο περιβάλλον" }, - "selfHostedEnvironmentFooter": { - "message": "Καθορίστε τη βασική διεύθυνση URL, της εγκατάστασης του Bitwarden που φιλοξενείται στο χώρο σας." - }, "selfHostedBaseUrlHint": { "message": "Καθορίστε το βασικό URL της εγκατάστασης Bitwarden στο χώρο σας. Παράδειγμα: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Προσαρμοσμένο περιβάλλον" }, - "customEnvironmentFooter": { - "message": "Για προχωρημένους χρήστες. Μπορείτε να ορίσετε ανεξάρτητα τη βασική διεύθυνση URL κάθε υπηρεσίας." - }, "baseUrl": { "message": "URL Διακομιστή" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Σύρετε για ταξινόμηση" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Κείμενο" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Γεννήτρια ονόματος χρήστη" }, + "useThisEmail": { + "message": "Χρήση αυτού του email" + }, "useThisPassword": { "message": "Χρήση αυτού του κωδικού πρόσβασης" }, @@ -2063,12 +2163,24 @@ "message": "για να δημιουργήσετε έναν ισχυρό μοναδικό κωδικό πρόσβασης", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "Ενέργεια Χρόνου Λήξης Vault" }, "vaultTimeoutAction1": { "message": "Ενέργεια κατά τη λήξη χρονικού ορίου" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "Κλείδωμα", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Τομείς", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Αποκλεισμένοι τομείς" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "Εξαιρούμενοι Τομείς" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Το Bitwarden δε θα ρωτήσει για να αποθηκεύσετε τα στοιχεία σύνδεσης για αυτούς τους τομείς, για όλους τους συνδεδεμένους λογαριασμούς. Πρέπει να ανανεώσετε τη σελίδα για να τεθούν σε ισχύ οι αλλαγές." }, + "blockedDomainsDesc": { + "message": "Η αυτόματη συμπλήρωση και άλλες σχετικές λειτουργίες δεν θα προσφερθούν για αυτούς τους ιστότοπους. Πρέπει να ανανεώσετε τη σελίδα για να τεθούν σε ισχύ οι αλλαγές." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Αλλαγή" + }, + "changeButtonTitle": { + "message": "Αλλαγή κωδικού πρόσβασης - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "Κωδικοί πρόσβασης σε κίνδυνο" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Ενεργοποίηση αυτόματης συμπλήρωσης" + }, + "turnedOnAutofill": { + "message": "Απενεργοποίηση αυτόματης συμπλήρωσης" + }, + "dismiss": { + "message": "Απόρριψη" + }, "websiteItemLabel": { "message": "Ιστοσελίδα $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Οι αλλαγές αποκλεισμένων τομέων αποθηκεύτηκαν" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Σφάλμα" }, + "decryptionError": { + "message": "Σφάλμα αποκρυπτογράφησης" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Το Bitwarden δεν μπόρεσε να αποκρυπτογραφήσει τα αντικείμενα θησαυ/κίου που αναφέρονται παρακάτω." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Δημιουργία ονόματος χρήστη" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Αδύνατη η απόκτηση του $SERVICENAME$ καμουφλαρισμένου ID διεύθυνσης ηλ. ταχυδρομείου.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Οι ρυθμίσεις έχουν επεξεργαστεί" - }, - "environmentEditedClick": { - "message": "Κάντε κλικ εδώ" - }, - "environmentEditedReset": { - "message": "επαναφορά στις προ-ρυθμισμένες ρυθμίσεις" - }, "serverVersion": { "message": "Έκδοση διακομιστή" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Συνδεθείτε με τον κύριο κωδικό πρόσβασης" }, - "loggingInAs": { - "message": "Σύνδεση ως" - }, - "notYou": { - "message": "Δεν είστε εσείς;" - }, "newAroundHere": { "message": "Είστε νέος/α εδώ;" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Σύνδεση με τη χρήση συσκευής" }, - "loginWithDeviceEnabledInfo": { - "message": "Η σύνδεση με τη χρήση συσκευής πρέπει να οριστεί στις ρυθμίσεις της εφαρμογής Bitwarden. Χρειάζεστε κάποια άλλη επιλογή;" - }, "fingerprintPhraseHeader": { "message": "Φράση δακτυλικού αποτυπώματος" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "Δείτε όλες τις επιλογές σύνδεσης" }, - "viewAllLoginOptionsV1": { - "message": "Δείτε όλες τις επιλογές σύνδεσης" - }, "notificationSentDevice": { "message": "Μια ειδοποίηση έχει σταλεί στη συσκευή σας." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "Μια ειδοποίηση στάλθηκε στη συσκευή σας" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Βεβαιωθείτε ότι ο λογαριασμός σας είναι ξεκλείδωτος και ότι η φράση δακτυλικού αποτυπώματος ταιριάζει στην άλλη συσκευή" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "Θα ειδοποιηθείτε μόλις εγκριθεί η αίτηση" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Η σύνδεση ξεκίνησε" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Εκτεθειμένος Κύριος Κωδικός Πρόσβασης" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Αίτηση έγκρισης διαχειριστή" }, - "approveWithMasterPassword": { - "message": "Έγκριση με κύριο κωδικό πρόσβασης" - }, "ssoIdentifierRequired": { "message": "Απαιτείται αναγνωριστικό οργανισμού SSO." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Το αίτημά σας εστάλη στον διαχειριστή σας." }, - "youWillBeNotifiedOnceApproved": { - "message": "Θα ειδοποιηθείτε μόλις εγκριθεί." - }, "troubleLoggingIn": { "message": "Δεν μπορείτε να συνδεθείτε;" }, @@ -3396,38 +3649,6 @@ "message": "Εναλλαγή σύμπτυξης", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Εισαγωγή των δεδομένων σας στο Bitwarden;", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Προστατέψτε τα δεδομένα LastPass και εισαγάγετε στο Bitwarden;", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Αποθήκευση ως μη κρυπτογραφημένο αρχείο", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Εισαγωγή στο Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Εισαγωγή...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Επιτυχής εισαγωγή δεδομένων!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Σφάλμα εισαγωγής. Ελέγξτε την κονσόλα για λεπτομέρειες.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Εμφανίστηκε σφάλμα δικτύου κατά την εισαγωγή.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Ψευδώνυμο τομέα" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Ενεργός λογαριασμός" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Διαθέσιμοι λογαριασμοί" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Προτάσεις αυτόματης συμπλήρωσης" }, + "itemSuggestions": { + "message": "Προτεινόμενα στοιχεία" + }, "autofillSuggestionsTip": { "message": "Αποθηκεύστε ένα αντικείμενο σύνδεσης για την αυτόματη συμπλήρωση αυτού του ιστοτόπου" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "Δεν υπάρχουν τιμές για αντιγραφή" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Όνομα αντικειμένου" }, - "cannotRemoveViewOnlyCollections": { - "message": "Δεν μπορείτε να αφαιρέσετε συλλογές που έχουν μόνο δικαιώματα Προβολής: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Ο οργανισμός απενεργοποιήθηκε" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ μετακινήθηκε πάνω, θέση $INDEX$ από $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "Δεν έχετε επιλέξει τίποτα." }, - "movedItemsToOrg": { - "message": "Τα επιλεγμένα αντικείμενα μετακινήθηκαν στο $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Τα αντικείμενα μεταφέρθηκαν στο $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Send κειμένων" }, - "bitwardenNewLook": { - "message": "Το Bitwarden έχει μια νέα εμφάνιση!" - }, - "bitwardenNewLookDesc": { - "message": "Είναι πιο ευκολότερο και πιο διαισθητικό από ποτέ στην αυτόματη συμπλήρωση και αναζήτηση από την καρτέλα Θησαυ/κιο. Ρίξτε μια ματιά τριγύρω!" - }, "accountActions": { "message": "Ενέργειες λογαριασμού" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "Δεν έχετε δικαίωμα να επεξεργαστείτε αυτό το αντικείμενο" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Το βιομετρικό ξεκλείδωμα δεν είναι διαθέσιμο επειδή απαιτείται πρώτα το ξεκλείδωμα με PIN ή κωδικό πρόσβασης." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Το βιομετρικό ξεκλείδωμα δεν είναι διαθέσιμο προς το παρόν." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Το βιομετρικό ξεκλείδωμα δεν είναι διαθέσιμο λόγω εσφαλμένων ρυθμίσεων αρχείων συστήματος." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Το βιομετρικό ξεκλείδωμα δεν είναι διαθέσιμο λόγω εσφαλμένων ρυθμίσεων αρχείων συστήματος." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Το βιομετρικό ξεκλείδωμα δεν είναι διαθέσιμο επειδή η εφαρμογή Bitwarden επιφάνειας εργασίας είναι κλειστή." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Ταυτοποίηση" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Εξαιρετικά φαρδύ" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "Δεν μπορείτε να αφαιρέσετε συλλογές που έχουν μόνο δικαιώματα Προβολής: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index b72a909252b..16f462b7f17 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Master password hint (optional)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Copy notes" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill":{ "message": "Fill", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Edit folder" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "New folder" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Generate passphrase" }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "regeneratePassword": { "message": "Regenerate password" }, @@ -454,22 +482,6 @@ "length": { "message": "Length" }, - "uppercase": { - "message": "Uppercase (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Lowercase (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Numbers (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Special characters (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Include", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Number of words" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Your web browser does not support easy clipboard copying. Copy it manually instead." }, - "verifyIdentity": { - "message": "Verify identity" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "Your vault is locked. Verify your identity to continue." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Restart registration" }, @@ -875,6 +904,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "An unexpected error has occurred." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "Show cards on Tab page" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "List card items on the Tab page for easy autofill." }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "Show identities on Tab page" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "Clear clipboard", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Save" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Ask to update existing login" }, @@ -1084,10 +1169,6 @@ "message": "Light", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized dark", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Export from" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Purchase Premium" }, - "premiumPurchaseAlert": { - "message": "You can purchase Premium membership on the bitwarden.com web vault. Do you want to visit the website now?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Open new tab" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "Login unavailable" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Lost access to all of your two-factor providers? Use your recovery code to turn off all two-factor providers from your account." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Self-hosted environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Custom environment" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "Server URL" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Drag to sort" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Text" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Username generator" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "Use this password" }, @@ -2063,12 +2163,24 @@ "message": "to create a strong unique password", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "Vault timeout action" }, "vaultTimeoutAction1": { "message": "Timeout action" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "Lock", "description": "Verb form: to make secure or inaccessible by" @@ -2327,6 +2439,9 @@ "blockedDomains": { "message": "Blocked domains" }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2339,11 +2454,114 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -2949,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3030,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Settings have been edited" - }, - "environmentEditedClick": { - "message": "Click here" - }, - "environmentEditedReset": { - "message": "to reset to pre-configured settings" - }, "serverVersion": { "message": "Server version" }, @@ -3069,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -3084,9 +3311,6 @@ "loginWithDevice": { "message": "Log in with device" }, - "loginWithDeviceEnabledInfo": { - "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" - }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, @@ -3099,18 +3323,21 @@ "viewAllLogInOptions": { "message": "View all log in options" }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" - }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "You will be notified once the request is approved" }, @@ -3120,6 +3347,9 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -3234,9 +3464,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, @@ -3283,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3425,38 +3649,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, @@ -3887,6 +4079,9 @@ "activeAccount": { "message": "Active account" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Available accounts" }, @@ -4010,6 +4205,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4082,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "No values to copy" }, @@ -4157,15 +4369,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organization is deactivated" }, @@ -4467,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4527,15 +4733,6 @@ "nothingSelected": { "message": "You have not selected anything." }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -4586,12 +4783,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, @@ -4903,5 +5094,50 @@ }, "extraWide": { "message": "Extra wide" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index 2c13f1b9259..1381afbdc6e 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Master password hint (optional)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organisation" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Copy notes" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { "message": "Fill", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Edit folder" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "New folder" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Generate passphrase" }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "regeneratePassword": { "message": "Regenerate password" }, @@ -454,22 +482,6 @@ "length": { "message": "Length" }, - "uppercase": { - "message": "Uppercase (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Lowercase (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Numbers (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Special characters (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Include", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Number of words" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Your web browser does not support easy clipboard copying. Copy it manually instead." }, - "verifyIdentity": { - "message": "Verify identity" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognise this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "Your vault is locked. Verify your identity to continue." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Restart registration" }, @@ -875,6 +904,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "An unexpected error has occurred." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "Show cards on Tab page" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "List card items on the Tab page for easy auto-fill." }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "Show identities on Tab page" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "Clear clipboard", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Save" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Ask to update existing login" }, @@ -1084,10 +1169,6 @@ "message": "Light", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized dark", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Export from" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Purchase premium" }, - "premiumPurchaseAlert": { - "message": "You can purchase Premium membership on the bitwarden.com web vault. Do you want to visit the website now?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Open new tab" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "Login unavailable" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Lost access to all of your two-factor providers? Use your recovery code to turn off all two-factor providers from your account." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Self-hosted environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premise hosted Bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Custom environment" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "Server URL" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Drag to sort" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Text" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Username generator" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "Use this password" }, @@ -2063,12 +2163,24 @@ "message": "to create a strong unique password", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customisation" + }, "vaultTimeoutAction": { "message": "Vault timeout action" }, "vaultTimeoutAction1": { "message": "Timeout action" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customisation options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customise your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "Lock", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organisations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organisation passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Settings have been edited" - }, - "environmentEditedClick": { - "message": "Click here" - }, - "environmentEditedReset": { - "message": "to reset to pre-configured settings" - }, "serverVersion": { "message": "Server version" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Log in with device" }, - "loginWithDeviceEnabledInfo": { - "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" - }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "View all log in options" }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" - }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "You will be notified once the request is approved" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "ssoIdentifierRequired": { "message": "Organisation SSO identifier is required." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3396,38 +3649,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Active account" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Available accounts" }, @@ -3979,7 +4203,10 @@ "message": "Passkey removed" }, "autofillSuggestions": { - "message": "Auto-fill suggestions" + "message": "Autofill suggestions" + }, + "itemSuggestions": { + "message": "Suggested items" }, "autofillSuggestionsTip": { "message": "Save a login item for this site to auto-fill" @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "No values to copy" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organisation is deactivated" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "You have not selected anything." }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra wide" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index b6031381d2d..6b14a358148 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Master password hint (optional)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organisation" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Copy notes" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { "message": "Fill", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Edit folder" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "New folder" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Generate passphrase" }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "regeneratePassword": { "message": "Regenerate password" }, @@ -454,22 +482,6 @@ "length": { "message": "Length" }, - "uppercase": { - "message": "Uppercase (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Lowercase (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Numbers (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Special Characters (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Include", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Number of words" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Your web browser does not support easy clipboard copying. Copy it manually instead." }, - "verifyIdentity": { - "message": "Verify Identity" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognise this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "Your vault is locked. Verify your identity to continue." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Restart registration" }, @@ -875,6 +904,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "An unexpected error has occurred." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "Show cards on Tab page" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "List card items on the Tab page for easy auto-fill." }, - "showIdentitiesInVaultView": { - "message": "Show identifies as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "Show identities on Tab page" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "Clear clipboard", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Yes, save now" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Ask to update existing login" }, @@ -1084,10 +1169,6 @@ "message": "Light", "description": "Light color" }, - "solarizedDark": { - "message": "Solarised Dark", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Export from" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Purchase premium" }, - "premiumPurchaseAlert": { - "message": "You can purchase premium membership on the bitwarden.com web vault. Do you want to visit the website now?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Open new tab" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "Login unavailable" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Lost access to all of your two-factor providers? Use your recovery code to disable all two-factor providers from your account." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Self-hosted environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premise hosted Bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Custom environment" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "Server URL" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Drag to sort" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Text" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Username generator" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "Use this password" }, @@ -2063,12 +2163,24 @@ "message": "to create a strong unique password", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customisation" + }, "vaultTimeoutAction": { "message": "Vault timeout action" }, "vaultTimeoutAction1": { "message": "Timeout action" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customisation options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customise your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "Lock", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "Excluded Domains" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organisations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organisation passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate Username" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Settings have been edited" - }, - "environmentEditedClick": { - "message": "Click here" - }, - "environmentEditedReset": { - "message": "to reset to pre-configured settings" - }, "serverVersion": { "message": "Server Version" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Log in with device" }, - "loginWithDeviceEnabledInfo": { - "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" - }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "View all log in options" }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" - }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "You will be notified once the request is approved" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "ssoIdentifierRequired": { "message": "Organisation SSO identifier is required." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3396,38 +3649,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Active account" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Available accounts" }, @@ -3979,7 +4203,10 @@ "message": "Passkey removed" }, "autofillSuggestions": { - "message": "Auto-fill suggestions" + "message": "Autofill suggestions" + }, + "itemSuggestions": { + "message": "Suggested items" }, "autofillSuggestionsTip": { "message": "Save a login item for this site to auto-fill" @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "No values to copy" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organisation is deactivated" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "You have not selected anything." }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra wide" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index d26716dd909..4d430d23337 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -23,10 +23,10 @@ "message": "¿Nuevo en Bitwarden?" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Iniciar sesión con clave de acceso" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "Usar inicio de sesión único" }, "welcomeBack": { "message": "Bienvenido de nuevo" @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Pista de contraseña maestra (opcional)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Incorporarse a la organización" }, @@ -120,7 +129,7 @@ "message": "Copiar contraseña" }, "copyPassphrase": { - "message": "Copy passphrase" + "message": "Copiar frase de contraseña" }, "copyNote": { "message": "Copiar nota" @@ -153,13 +162,13 @@ "message": "Copiar número de licencia" }, "copyPrivateKey": { - "message": "Copy private key" + "message": "Copiar llave privada" }, "copyPublicKey": { - "message": "Copy public key" + "message": "Copiar llave pública" }, "copyFingerprint": { - "message": "Copy fingerprint" + "message": "Copiar huella" }, "copyCustomField": { "message": "Copiar $FIELD$", @@ -176,8 +185,12 @@ "copyNotes": { "message": "Copiar notas" }, + "copy": { + "message": "Copiar", + "description": "Copy to clipboard" + }, "fill": { - "message": "Fill", + "message": "Rellenar", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." }, "autoFill": { @@ -193,10 +206,10 @@ "message": "Autocompletar identidad" }, "fillVerificationCode": { - "message": "Fill verification code" + "message": "Rellenar código de verificación" }, "fillVerificationCodeAria": { - "message": "Fill Verification Code", + "message": "Rellenar código de verificación", "description": "Aria label for the heading displayed the inline menu for totp code autofill" }, "generatePasswordCopied": { @@ -248,13 +261,7 @@ "message": "Solicitar pista de la contraseña" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { - "message": "Enter your account email address and your password hint will be sent to you" - }, - "passwordHint": { - "message": "Pista de contraseña" - }, - "enterEmailToGetHint": { - "message": "Introduce el correo electrónico de tu cuenta para recibir la pista de tu contraseña maestra." + "message": "Introduce la dirección de correo electrónico de tu cuenta y se te enviará la pista de tu contraseña" }, "getMasterPasswordHint": { "message": "Obtener pista de la contraseña maestra" @@ -372,6 +379,15 @@ "editFolder": { "message": "Editar carpeta" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "Carpeta nueva" }, @@ -379,13 +395,13 @@ "message": "Nombre de carpeta" }, "folderHintText": { - "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + "message": "Anida una carpeta añadiendo el nombre de la carpeta padre seguido de un “/”. Ejemplo: Social/Foros" }, "noFoldersAdded": { "message": "Ninguna carpeta añadida" }, "createFoldersToOrganize": { - "message": "Create folders to organize your vault items" + "message": "Crear carpetas para organizar los elementos de la caja fuerte" }, "deleteFolderPermanently": { "message": "¿Confirma que quiere eliminar permanentemente esta carpeta?" @@ -443,7 +459,19 @@ "message": "Generar contraseña" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "Generar frase de contraseña" + }, + "passwordGenerated": { + "message": "Contraseña generada" + }, + "passphraseGenerated": { + "message": "Frase de contraseña generada" + }, + "usernameGenerated": { + "message": "Nombre de usuario generado" + }, + "emailGenerated": { + "message": "Correo electrónico generado" }, "regeneratePassword": { "message": "Regenerar contraseña" @@ -454,22 +482,6 @@ "length": { "message": "Longitud" }, - "uppercase": { - "message": "Mayúsculas (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Minúsculas (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Números (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Caracteres especiales (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Incluir", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Incluir caracteres especiales", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Número de palabras" }, @@ -530,7 +538,7 @@ "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { - "message": "Enterprise policy requirements have been applied to your generator options.", + "message": "Los requisitos de política empresarial se han aplicado a las opciones de generador.", "description": "Indicates that a policy limits the credential generator screen." }, "searchVault": { @@ -600,7 +608,7 @@ "message": "Iniciar página web" }, "launchWebsiteName": { - "message": "Launch website $ITEMNAME$", + "message": "Iniciar sitio web $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Tu navegador web no soporta copiar al portapapeles facilmente. Cópialo manualmente." }, - "verifyIdentity": { - "message": "Verificar identidad" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "No reconocemos este dispositivo. Introduce el código enviado a tu correo electrónico para verificar tu identidad." + }, + "continueLoggingIn": { + "message": "Continuar el inicio de sesión" }, "yourVaultIsLocked": { "message": "Tu caja fuerte está bloqueada. Verifica tu identidad para continuar." @@ -779,10 +793,10 @@ "message": "¡Tu nueva cuenta ha sido creada! Ahora puedes acceder." }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "¡Tu nueva cuenta ha sido creada!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "¡Has iniciado sesión!" }, "youSuccessfullyLoggedIn": { "message": "Accedió correctamente a su cuenta" @@ -797,7 +811,7 @@ "message": "Código de verificación requerido." }, "webauthnCancelOrTimeout": { - "message": "The authentication was cancelled or took too long. Please try again." + "message": "La autenticación fue cancelada o tardó demasiado. Por favor, inténtalo de nuevo." }, "invalidVerificationCode": { "message": "Código de verificación no válido" @@ -825,16 +839,16 @@ "message": "Escanee el código QR del autenticador desde la página web actual" }, "totpHelperTitle": { - "message": "Make 2-step verification seamless" + "message": "Hacer la verificación en dos pasos sencilla" }, "totpHelper": { - "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + "message": "Bitwarden puede almacenar y rellenar códigos de verificación en dos pasos. Copia y pega la clave en este campo." }, "totpHelperWithCapture": { - "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + "message": "Bitwarden puede almacenar y llenar códigos de verificación en dos pasos. Seleccione el icono de la cámara para tomar una captura de pantalla del código QR de este sitio web, o copie y pegue la clave en este campo." }, "learnMoreAboutAuthenticators": { - "message": "Learn more about authenticators" + "message": "Más información sobre los autenticadores" }, "copyTOTP": { "message": "Copiar clave de autenticador (TOTP)" @@ -852,7 +866,22 @@ "message": "Acceder" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Iniciar sesión en Bitwarden" + }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." }, "restartRegistration": { "message": "Reiniciar registro" @@ -875,6 +904,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "Ha ocurrido un error inesperado." }, @@ -888,10 +920,10 @@ "message": "La autenticación en dos pasos hace que tu cuenta sea mucho más segura, requiriendo que introduzcas un código de seguridad de una aplicación de autenticación cada vez que accedes. La autenticación en dos pasos puede ser habilitada en la caja fuerte web de bitwarden.com. ¿Quieres visitar ahora el sitio web?" }, "twoStepLoginConfirmationContent": { - "message": "Make your account more secure by setting up two-step login in the Bitwarden web app." + "message": "Haz tu cuenta más segura al configurar el inicio de sesión en dos pasos en la aplicación web de Bitwarden." }, "twoStepLoginConfirmationTitle": { - "message": "Continue to web app?" + "message": "¿Continuar en la aplicación web?" }, "editedFolder": { "message": "Carpeta editada" @@ -978,7 +1010,7 @@ "message": "Pedir que se añada el inicio de sesión" }, "vaultSaveOptionsTitle": { - "message": "Save to vault options" + "message": "Guardar en opciones de caja fuerte" }, "addLoginNotificationDesc": { "message": "La opción \"Notificación para añadir entradas\" pregunta automáticamente si quieres guardar nuevas entradas en tu caja fuerte cuando te identificas en un sitio web por primera vez." @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Pide que se agregue un elemento si no se encuentra uno en su caja fuerte. Se aplica a todas las cuentas que hayan iniciado sesión." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Mostrar siempre las tarjetas como sugerencias de autorrelleno en la vista de caja fuerte" }, "showCardsCurrentTab": { "message": "Mostrar las tarjetas en la pestaña" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Listar los elementos de tarjetas en la página para facilitar el auto-rellenado." }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Mostrar siempre identidades como sugerencias de autorrelleno en la vista de caja fuerte" }, "showIdentitiesCurrentTab": { "message": "Mostrar las identidades en la página" @@ -1005,7 +1037,10 @@ "message": "Listar los elementos de identidad en la página para facilitar el auto-rellenado." }, "clickToAutofillOnVault": { - "message": "Click items to autofill on Vault view" + "message": "Haga clic en los elementos para autocompletar en la vista de caja fuerte" + }, + "clickToAutofill": { + "message": "Haz clic en los elementos de la sugerencia de autorrelleno para rellenar" }, "clearClipboard": { "message": "Vaciar portapapeles", @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Guardar" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Solicitar la actualización de los datos de inicio de sesión existentes" }, @@ -1084,10 +1169,6 @@ "message": "Claro", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized Dark", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Exportar desde" }, @@ -1206,7 +1287,7 @@ "message": "Archivo" }, "fileToShare": { - "message": "File to share" + "message": "Archivo a compartir" }, "selectFile": { "message": "Selecciona un archivo." @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Comprar Premium" }, - "premiumPurchaseAlert": { - "message": "Puedes comprar la membresía Premium en la caja fuerte web de bitwarden.com. ¿Quieres visitar el sitio web ahora?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1275,7 +1353,7 @@ "message": "Gracias por apoyar el desarrollo de Bitwarden." }, "premiumFeatures": { - "message": "Upgrade to Premium and receive:" + "message": "Actualiza a Premium y recibe:" }, "premiumPrice": { "message": "¡Todo por solo %price% /año!", @@ -1287,7 +1365,7 @@ } }, "premiumPriceV2": { - "message": "All for just $PRICE$ per year!", + "message": "¡Todo por sólo $PRICE$/año!", "placeholders": { "price": { "content": "$1", @@ -1317,10 +1395,10 @@ "message": "Introduce el código de verificación de 6 dígitos de tu aplicación autenticadora." }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "Tiempo de autenticación agotado" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "Se ha agotado el tiempo de la sesión de autenticación. Por favor, inicie nuevamente el proceso de inicio de sesión." }, "enterVerificationCodeEmail": { "message": "Introduce el código de verificación de 6 dígitos que te ha sido enviado por correo electrónico", @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Recordarme" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Reenviar código de verificación por correo electrónico" }, "useAnotherTwoStepMethod": { "message": "Utilizar otro método de autenticación en dos pasos" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Inserta tu YubiKey en el puerto USB de tu equipo y posteriormente pulsa su botón." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Abrir nueva pestaña" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "Autenticar WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "Entrada no disponible" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Opciones de la autenticación en dos pasos" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "¿Has perdido el acceso a todos tus métodos de autenticación en dos pasos? Utiliza tu código de recuperación para deshabilitar todos los métodos de autenticación en dos pasos de tu cuenta." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Entorno de alojamiento propio" }, - "selfHostedEnvironmentFooter": { - "message": "Especifica la URL base de tu instalación de Bitwarden de alojamiento propio." - }, "selfHostedBaseUrlHint": { "message": "Especifica la dirección URL base de la instalación de Bitwarden alojada localmente. Ejemplo: https://bitwarden.company.com" }, @@ -1433,14 +1530,11 @@ "customEnvironment": { "message": "Entorno personalizado" }, - "customEnvironmentFooter": { - "message": "Para usuarios avanzados. Puedes especificar la URL base de cada servicio de forma independiente." - }, "baseUrl": { "message": "URL del servidor" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "URL del servidor autoalojado", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -1466,7 +1560,7 @@ "description": "Represents the message for allowing the user to enable the autofill overlay" }, "autofillSuggestionsSectionTitle": { - "message": "Autofill suggestions" + "message": "Sugerencias de autocompletar" }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" @@ -1502,7 +1596,7 @@ "description": "Overlay appearance select option for showing the field on click of the overlay icon" }, "enableAutoFillOnPageLoadSectionTitle": { - "message": "Autofill on page load" + "message": "Autocompletar al cargar la página" }, "enableAutoFillOnPageLoad": { "message": "Habilitar autorrellenar al cargar la página" @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Arrastrar para ordenar" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Texto" }, @@ -1768,7 +1865,7 @@ "message": "Identidad" }, "typeSshKey": { - "message": "SSH key" + "message": "Llave SSH" }, "newItemHeader": { "message": "Nuevo $TYPE$", @@ -1801,10 +1898,10 @@ "message": "Historial de contraseñas" }, "generatorHistory": { - "message": "Generator history" + "message": "Historial del generador" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "Borrar historial del generador" }, "cleargGeneratorHistoryDescription": { "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" @@ -1846,7 +1943,7 @@ "message": "Notas seguras" }, "sshKeys": { - "message": "SSH Keys" + "message": "Llaves SSH" }, "clear": { "message": "Limpiar", @@ -1926,13 +2023,13 @@ "message": "No hay contraseñas que listar." }, "clearHistory": { - "message": "Clear history" + "message": "Limpiar historial" }, "nothingToShow": { - "message": "Nothing to show" + "message": "No se encontraron resultados" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "No has generado nada recientemente" }, "remove": { "message": "Eliminar" @@ -1993,16 +2090,16 @@ "message": "Desbloquear con PIN" }, "setYourPinTitle": { - "message": "Set PIN" + "message": "Establecer PIN" }, "setYourPinButton": { - "message": "Set PIN" + "message": "Establecer PIN" }, "setYourPinCode": { "message": "Establece tu código PIN para desbloquear Bitwarden. Tus ajustes de PIN se reiniciarán si alguna vez cierras tu sesión completamente de la aplicación." }, "setYourPinCode1": { - "message": "Your PIN will be used to unlock Bitwarden instead of your master password. Your PIN will reset if you ever fully log out of Bitwarden." + "message": "Su PIN se utilizará para desbloquear Bitwarden en lugar de su contraseña maestra. Su PIN se restablecerá si alguna vez cierra completamente la sesión de Bitwarden." }, "pinRequired": { "message": "Código PIN requerido." @@ -2017,7 +2114,7 @@ "message": "Desbloquear con biométricos" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "Desbloquear con contraseña maestra" }, "awaitDesktop": { "message": "Esperando la confirmación por parte del escritorio" @@ -2029,7 +2126,7 @@ "message": "Bloquear con contraseña maestra al reiniciar el navegador" }, "lockWithMasterPassOnRestart1": { - "message": "Require master password on browser restart" + "message": "Bloquear con contraseña maestra al reiniciar el navegador" }, "selectOneCollection": { "message": "Debes seleccionar al menos una colección." @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Generador de nombres de usuario" }, + "useThisEmail": { + "message": "Usar esta dirección de correo electrónico" + }, "useThisPassword": { "message": "Usar esta contraseña" }, @@ -2053,21 +2153,33 @@ "message": "Usar este nombre de usuario" }, "securePasswordGenerated": { - "message": "Secure password generated! Don't forget to also update your password on the website." + "message": "¡Contraseña segura generada! No olvide actualizar su contraseña en el sitio web." }, "useGeneratorHelpTextPartOne": { - "message": "Use the generator", + "message": "Usar el generador", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "useGeneratorHelpTextPartTwo": { - "message": "to create a strong unique password", + "message": "para crear una contraseña única fuerte", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Personalización de la caja fuerte" + }, "vaultTimeoutAction": { "message": "Acción de tiempo de espera de la caja fuerte" }, "vaultTimeoutAction1": { - "message": "Timeout action" + "message": "Acción de tiempo agotado" + }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" }, "lock": { "message": "Bloquear", @@ -2189,7 +2301,7 @@ "message": "Su nueva contraseña maestra no cumple con los requisitos de la política." }, "receiveMarketingEmailsV2": { - "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." + "message": "Obtenga consejos, anuncios y oportunidades de investigación de Bitwarden en su bandeja de entrada." }, "unsubscribe": { "message": "Cancelar suscripción" @@ -2264,7 +2376,7 @@ "message": "Las cuentas son distintas" }, "nativeMessagingWrongUserKeyTitle": { - "message": "Biometric key missmatch" + "message": "Desajuste de clave biométrica" }, "nativeMessagingWrongUserKeyDesc": { "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." @@ -2324,6 +2436,12 @@ "message": "Dominios", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Dominios bloqueados" + }, + "learnMoreAboutBlockedDomains": { + "message": "Más información sobre dominios bloqueados" + }, "excludedDomains": { "message": "Dominios excluidos" }, @@ -2333,8 +2451,120 @@ "excludedDomainsDescAlt": { "message": "Bitwarden no pedirá que se guarden los datos de acceso para estos dominios en todas las sesiones iniciadas. Debe actualizar la página para que los cambios surtan efecto." }, + "blockedDomainsDesc": { + "message": "El autorrelleno y otras funcionalidades relacionadas no se ofrecerán para estos sitios web. Debe actualizar la página para que los cambios surtan efecto." + }, + "autofillBlockedNoticeV2": { + "message": "Autorelleno está desactivado para este sitio web." + }, + "autofillBlockedNoticeGuidance": { + "message": "Cambia esto en los ajustes" + }, + "change": { + "message": "Cambiar" + }, + "changeButtonTitle": { + "message": "Cambiar contraseña - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "Contraseñas de riesgo" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Activar autorrelleno" + }, + "turnedOnAutofill": { + "message": "Autorrelleno activado" + }, + "dismiss": { + "message": "Ignorar" + }, "websiteItemLabel": { - "message": "Website $number$ (URI)", + "message": "Sitio web $number$ (URI)", "placeholders": { "number": { "content": "$1", @@ -2351,14 +2581,17 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Cambios de dominio bloqueados guardados" + }, "excludedDomainsSavedSuccess": { - "message": "Excluded domain changes saved" + "message": "Cambios de dominio excluidos guardados" }, "limitSendViews": { - "message": "Limit views" + "message": "Limitar visualizaciones" }, "limitSendViewsHint": { - "message": "No one can view this Send after the limit is reached.", + "message": "Nadie puede ver este envío después de que se alcance el límite.", "description": "Displayed under the limit views field on Send" }, "limitSendViewsCount": { @@ -2553,7 +2786,7 @@ "message": "Para elegir un archivo usando Safari, salga a una nueva ventana haciendo clic en este anouncio." }, "popOut": { - "message": "Pop out" + "message": "Salir" }, "sendFileCalloutHeader": { "message": "Antes de empezar" @@ -2574,7 +2807,7 @@ "message": "Hubo un error al guardar las fechas de eliminación y caducidad." }, "hideYourEmail": { - "message": "Hide your email address from viewers." + "message": "Ocultar mi dirección de correo electrónico a los destinatarios." }, "passwordPrompt": { "message": "Volver a preguntar contraseña maestra" @@ -2607,7 +2840,7 @@ "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." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "Tu organización ha deshabilitado el cifrado de dispositivos de confianza. Por favor, establece una contraseña maestra para acceder a tu caja fuerte." }, "resetPasswordPolicyAutoEnroll": { "message": "Inscripción automática" @@ -2631,7 +2864,7 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "de $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -2789,14 +3022,28 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Error de descifrado" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden no pudo descifrar el/los elemento(s) de la bóveda listados a continuación." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contacto con atención al cliente", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "para evitar pérdida de datos adicionales.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generar nombre de usuario" }, "generateEmail": { - "message": "Generate email" + "message": "Crear correo electrónico" }, "spinboxBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$.", + "message": "El valor debe estar entre $MIN$ y $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2810,7 +3057,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "message": " Usa $RECOMMENDED$ caracteres o más para generar una contraseña segura.", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "No se puede obtener el ID de la cuenta de correo electrónico enmascarado de $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Se han editado los ajustes" - }, - "environmentEditedClick": { - "message": "Haga click aquí" - }, - "environmentEditedReset": { - "message": "para restablecer a los ajustes por defecto" - }, "serverVersion": { "message": "Versión del servidor" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Iniciar sesión con contraseña maestra" }, - "loggingInAs": { - "message": "Iniciando sesión como" - }, - "notYou": { - "message": "¿No eres tú?" - }, "newAroundHere": { "message": "¿Nuevo por aquí?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Acceder con un dispositivo" }, - "loginWithDeviceEnabledInfo": { - "message": "El acceso con dispositivo debe prepararse en la configuración de la aplicación Bitwarden. ¿Necesita otra opción?" - }, "fingerprintPhraseHeader": { "message": "Frase de huella" }, @@ -3068,29 +3321,35 @@ "message": "Reenviar notificación" }, "viewAllLogInOptions": { - "message": "View all log in options" - }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" + "message": "Ver todas las opciones de inicio de sesión" }, "notificationSentDevice": { "message": "Se ha enviado una notificación a tu dispositivo." }, - "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "notificationSentDevicePart1": { + "message": "Desbloquear Bitwarden en tu dispositivo o en el" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Asegúrese de que la frase de huella digital coincide con la siguiente antes de aprobar." + }, + "aNotificationWasSentToYourDevice": { + "message": "Se ha enviado una notificación a tu dispositivo." }, "youWillBeNotifiedOnceTheRequestIsApproved": { - "message": "You will be notified once the request is approved" + "message": "Serás notificado una vez que la solicitud sea aprobada" }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "¿Necesitas otra opción?" }, "loginInitiated": { "message": "Inicio de sesión en proceso" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Contraseña maestra comprometida" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Solicitar aprobación del administrador" }, - "approveWithMasterPassword": { - "message": "Aprobar con contraseña maestra" - }, "ssoIdentifierRequired": { "message": "Se requiere un identificador único de inicio de sesión de la organización." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Su solicitud ha sido enviada a su administrador." }, - "youWillBeNotifiedOnceApproved": { - "message": "Se le notificará una vez aprobado." - }, "troubleLoggingIn": { "message": "¿Problemas para iniciar sesión?" }, @@ -3396,38 +3649,6 @@ "message": "Colapsar/Expandir", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "¿Quiere importar sus datos a Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "¿Quiere proteger sus datos de LastPass e importarlos a Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Guardar como archivo no cifrado", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Importar a Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importando...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Se importaron los datos correctamente.", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Se produjo un error al importar. Revise la consola para obtener detalles.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Se produjo un error de red durante la importación.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Seudónimo del dominio" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Cuenta activa" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Cuentas disponibles" }, @@ -3962,7 +4186,7 @@ "description": "Notification message for when updating credentials has succeeded." }, "passwordUpdated": { - "message": "Password updated!", + "message": "Contraseña actualizada", "description": "Notification message for when updating credentials has succeeded." }, "saveCipherAttemptFailed": { @@ -3979,7 +4203,10 @@ "message": "Clave de acceso eliminada" }, "autofillSuggestions": { - "message": "Autocompletar sugerencias" + "message": "Autofill suggestions" + }, + "itemSuggestions": { + "message": "Suggested items" }, "autofillSuggestionsTip": { "message": "Guarda un elemento de inicio de sesión para este sitio para autocompletar" @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "No hay valores para copiar" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Nombre del elemento" }, - "cannotRemoveViewOnlyCollections": { - "message": "No puedes eliminar colecciones con permisos de solo visualización: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "La organización está desactivada" }, @@ -4241,11 +4473,11 @@ } }, "cardNumberEndsWith": { - "message": "card number ends with", + "message": "el número de tarjeta termina en", "description": "Used within the inline menu to provide an aria description when users are attempting to fill a card cipher." }, "loginCredentials": { - "message": "Login credentials" + "message": "Credenciales de inicio de sesión" }, "authenticatorKey": { "message": "Authenticator key" @@ -4270,13 +4502,13 @@ "message": "Website added" }, "addWebsite": { - "message": "Add website" + "message": "Añadir página web" }, "deleteWebsite": { - "message": "Delete website" + "message": "Eliminar página web" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "Por defecto ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -4304,10 +4536,10 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "¿Autocompletar al cargar la página?" }, "cardExpiredTitle": { - "message": "Expired card" + "message": "Tarjeta caducada" }, "cardExpiredMessage": { "message": "If you've renewed it, update the card's information" @@ -4348,11 +4580,11 @@ "description": "A section header for a list of passwords." }, "logInWithPasskeyAriaLabel": { - "message": "Log in with passkey", + "message": "Iniciar sesión con clave de acceso", "description": "ARIA label for the inline menu button that logs in with a passkey." }, "assign": { - "message": "Assign" + "message": "Asignar" }, "bulkCollectionAssignmentDialogDescriptionSingular": { "message": "Only organization members with access to these collections will be able to see the item." @@ -4412,7 +4644,7 @@ } }, "deleteCustomField": { - "message": "Delete $LABEL$", + "message": "Eliminar $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4421,7 +4653,7 @@ } }, "fieldAdded": { - "message": "$LABEL$ added", + "message": "$LABEL$ añadido", "placeholders": { "label": { "content": "$1", @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4471,7 +4706,7 @@ } }, "personalItemWithOrgTransferWarningSingular": { - "message": "1 item will be permanently transferred to $ORG$. You will no longer own this item.", + "message": "1 elemento será transferido permanentemente a $ORG$. Ya no tendrás este objeto.", "placeholders": { "org": { "content": "$1", @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "You have not selected anything." }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden tiene un aspecto nuevo." - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Acciones de cuenta" }, @@ -4603,13 +4823,13 @@ "message": "RSA 4096-Bit" }, "retry": { - "message": "Retry" + "message": "Reintentar" }, "vaultCustomTimeoutMinimum": { - "message": "Minimum custom timeout is 1 minute." + "message": "El tiempo de espera mínimo personalizado es de 1 minuto." }, "additionalContentAvailable": { - "message": "Additional content is available" + "message": "Contenido adicional disponible" }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." @@ -4641,55 +4861,82 @@ "noEditPermissions": { "message": "No tiene permiso de editar este elemento" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { - "message": "Authenticating" + "message": "Autenticando" }, "fillGeneratedPassword": { - "message": "Fill generated password", + "message": "Rellenar contraseña generada", "description": "Heading for the password generator within the inline menu" }, "passwordRegenerated": { - "message": "Password regenerated", + "message": "Contraseña generada", "description": "Notification message for when a password has been regenerated" }, "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "message": "¿Guardar inicio de sesión en Bitwarden?", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { - "message": "Space", + "message": "Espacio", "description": "Represents the space key in screen reader content as a readable word" }, "tildeCharacterDescriptor": { - "message": "Tilde", + "message": "Virgulilla", "description": "Represents the ~ key in screen reader content as a readable word" }, "backtickCharacterDescriptor": { - "message": "Backtick", + "message": "tilde invertida", "description": "Represents the ` key in screen reader content as a readable word" }, "exclamationCharacterDescriptor": { - "message": "Exclamation mark", + "message": "Signo de exclamación", "description": "Represents the ! key in screen reader content as a readable word" }, "atSignCharacterDescriptor": { - "message": "At sign", + "message": "Arroba", "description": "Represents the @ key in screen reader content as a readable word" }, "hashSignCharacterDescriptor": { - "message": "Hash sign", + "message": "Almohadilla", "description": "Represents the # key in screen reader content as a readable word" }, "dollarSignCharacterDescriptor": { - "message": "Dollar sign", + "message": "Signo de dólar", "description": "Represents the $ key in screen reader content as a readable word" }, "percentSignCharacterDescriptor": { - "message": "Percent sign", + "message": "Signo de porcentaje", "description": "Represents the % key in screen reader content as a readable word" }, "caretCharacterDescriptor": { - "message": "Caret", + "message": "Acento circunflejo", "description": "Represents the ^ key in screen reader content as a readable word" }, "ampersandCharacterDescriptor": { @@ -4697,108 +4944,108 @@ "description": "Represents the & key in screen reader content as a readable word" }, "asteriskCharacterDescriptor": { - "message": "Asterisk", + "message": "Asterisco", "description": "Represents the * key in screen reader content as a readable word" }, "parenLeftCharacterDescriptor": { - "message": "Left parenthesis", + "message": "Paréntesis de apertura", "description": "Represents the ( key in screen reader content as a readable word" }, "parenRightCharacterDescriptor": { - "message": "Right parenthesis", + "message": "Paréntesis de cierre", "description": "Represents the ) key in screen reader content as a readable word" }, "hyphenCharacterDescriptor": { - "message": "Underscore", + "message": "Barra baja", "description": "Represents the _ key in screen reader content as a readable word" }, "underscoreCharacterDescriptor": { - "message": "Hyphen", + "message": "Guión", "description": "Represents the - key in screen reader content as a readable word" }, "plusCharacterDescriptor": { - "message": "Plus", + "message": "Más", "description": "Represents the + key in screen reader content as a readable word" }, "equalsCharacterDescriptor": { - "message": "Equals", + "message": "Igual", "description": "Represents the = key in screen reader content as a readable word" }, "braceLeftCharacterDescriptor": { - "message": "Left brace", + "message": "Abrir llave", "description": "Represents the { key in screen reader content as a readable word" }, "braceRightCharacterDescriptor": { - "message": "Right brace", + "message": "Cerrar llave", "description": "Represents the } key in screen reader content as a readable word" }, "bracketLeftCharacterDescriptor": { - "message": "Left bracket", + "message": "Abrir corchete", "description": "Represents the [ key in screen reader content as a readable word" }, "bracketRightCharacterDescriptor": { - "message": "Right bracket", + "message": "Cerrar corchete", "description": "Represents the ] key in screen reader content as a readable word" }, "pipeCharacterDescriptor": { - "message": "Pipe", + "message": "Barra vertical", "description": "Represents the | key in screen reader content as a readable word" }, "backSlashCharacterDescriptor": { - "message": "Back slash", + "message": "Barra invertida", "description": "Represents the back slash key in screen reader content as a readable word" }, "colonCharacterDescriptor": { - "message": "Colon", + "message": "Dos puntos", "description": "Represents the : key in screen reader content as a readable word" }, "semicolonCharacterDescriptor": { - "message": "Semicolon", + "message": "Punto y coma", "description": "Represents the ; key in screen reader content as a readable word" }, "doubleQuoteCharacterDescriptor": { - "message": "Double quote", + "message": "Comilla Doble", "description": "Represents the double quote key in screen reader content as a readable word" }, "singleQuoteCharacterDescriptor": { - "message": "Single quote", + "message": "Comillas simples", "description": "Represents the ' key in screen reader content as a readable word" }, "lessThanCharacterDescriptor": { - "message": "Less than", + "message": "Menor que", "description": "Represents the < key in screen reader content as a readable word" }, "greaterThanCharacterDescriptor": { - "message": "Greater than", + "message": "Mayor que", "description": "Represents the > key in screen reader content as a readable word" }, "commaCharacterDescriptor": { - "message": "Comma", + "message": "Coma", "description": "Represents the , key in screen reader content as a readable word" }, "periodCharacterDescriptor": { - "message": "Period", + "message": "Punto", "description": "Represents the . key in screen reader content as a readable word" }, "questionCharacterDescriptor": { - "message": "Question mark", + "message": "Signo de interrogación", "description": "Represents the ? key in screen reader content as a readable word" }, "forwardSlashCharacterDescriptor": { - "message": "Forward slash", + "message": "Barra diagonal", "description": "Represents the / key in screen reader content as a readable word" }, "lowercaseAriaLabel": { - "message": "Lowercase" + "message": "Minúscula" }, "uppercaseAriaLabel": { - "message": "Uppercase" + "message": "Mayúsculas" }, "generatedPassword": { - "message": "Generated password" + "message": "Contraseña generada" }, "compactMode": { - "message": "Compact mode" + "message": "Modo compacto" }, "beta": { "message": "Beta" @@ -4831,21 +5078,66 @@ "message": "No, I do not" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" + "message": "Sí, puedo acceder a mi correo electrónico de forma fiable" }, "turnOnTwoStepLogin": { - "message": "Turn on two-step login" + "message": "Activar inicio de sesión en dos pasos" }, "changeAcctEmail": { - "message": "Change account email" + "message": "Cambiar la cuenta de correo electrónico" }, "extensionWidth": { - "message": "Extension width" + "message": "Ancho de extensión" }, "wide": { - "message": "Wide" + "message": "Ancho" }, "extraWide": { - "message": "Extra wide" + "message": "Extraancho" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "No puedes eliminar colecciones con permisos de solo visualización: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Por favor, actualiza tu aplicación de escritorio" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "Para utilizar el desbloqueo biométrico, por favor actualice su aplicación de escritorio o desactive el desbloqueo de huella dactilar en los ajustes del escritorio." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index 069135ddb08..2daf5f9d7f0 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Ülemparooli vihje (ei ole kohustuslik)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Liitu organisatsiooniga" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Copy notes" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { "message": "Fill", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Parooli vihje" - }, - "enterEmailToGetHint": { - "message": "Ülemparooli vihje saamiseks sisesta oma konto e-posti aadress." - }, "getMasterPasswordHint": { "message": "Tuleta ülemparool vihjega meelde" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Muuda kausta" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "Uus kaust" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Generate passphrase" }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "regeneratePassword": { "message": "Genereeri parool uuesti" }, @@ -454,22 +482,6 @@ "length": { "message": "Pikkus" }, - "uppercase": { - "message": "Suurtäht (A-Z) ", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Väiketäht (a-z) ", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Numbrid (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Erimärgid (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Kasuta", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Kasuta sümboleid", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Sõnade arv" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Kasutatav brauser ei toeta lihtsat lõikelaua kopeerimist. Kopeeri see käsitsi." }, - "verifyIdentity": { - "message": "Identiteedi kinnitamine" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "Hoidla on lukus. Jätkamiseks sisesta ülemparool." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Alusta registreerimist uuesti" }, @@ -875,6 +904,9 @@ "no": { "message": "Ei" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "Tekkis ootamatu viga." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "Kuva \"Kaart\" vaates kaardiandmed" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Kuvab \"Kaart\" vaates kaardiandmeid, et neid saaks kiiresti sisestada" }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "Kuva \"Kaart\" vaates identiteete" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "Lõikelaua sisu kustutamine", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Jah, salvesta see" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Paku olemasolevate andmete uuendamist" }, @@ -1084,10 +1169,6 @@ "message": "Hele", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized tume", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Export from" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Osta Premium" }, - "premiumPurchaseAlert": { - "message": "Bitwardeni premium versiooni saab osta bitwarden.com veebihoidlas. Avan veebihoidla?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Jäta mind meelde" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Saada kinnituskood uuesti e-postile" }, "useAnotherTwoStepMethod": { "message": "Kasuta teist kaheastmelist sisselogimise meetodit" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Sisesta oma YubiKey arvuti USB porti ja kliki sellele nupule." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Ava uus vahekaart" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "WebAuthn kinnitamine" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "Sisselogimine ei ole saadaval" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Kaheastmelise sisselogimise valikud" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Puudub ligipääs kaheastmelise kinnitamise teenusele? Kasuta Taastamise koodi, et kaheastmeline kinnitamine oma kontol välja lülitada." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Self-hosted Environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premise hosted Bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Kohandatud keskkond" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "Serveri URL" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Lohista sorteerimiseks" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Tekst" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Kasutajanime genereerija" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "Kasuta seda parooli" }, @@ -2063,12 +2163,24 @@ "message": "et luua tugev ja ainulaadne parool", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "Hoidla ajalõpu tegevus" }, "vaultTimeoutAction1": { "message": "Timeout action" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "Lukusta", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "Väljajäetud domeenid" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Viga" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Genereeri kasutajanimi" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Seaded on uuendatud" - }, - "environmentEditedClick": { - "message": "Kliki siia," - }, - "environmentEditedReset": { - "message": "et taastada eelseadistatud seaded" - }, "serverVersion": { "message": "Serveri versioon" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Logi sisse ülemparooliga" }, - "loggingInAs": { - "message": "Sisselogimas kui" - }, - "notYou": { - "message": "Pole sina?" - }, "newAroundHere": { "message": "Oled siin uus?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Logi sisse seadme kaudu" }, - "loginWithDeviceEnabledInfo": { - "message": "Bitwardeni rakenduse seadistuses peab olema konfigureeritud sisselogimine läbi seadme. Vajad teist valikut?" - }, "fingerprintPhraseHeader": { "message": "Unikaalne sõnajada" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "View all log in options" }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" - }, "notificationSentDevice": { "message": "Sinu seadmesse saadeti teavitus." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "You will be notified once the request is approved" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Sisselogimine on käivitatud" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Ülemparool on haavatav" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Küsi admini kinnitust" }, - "approveWithMasterPassword": { - "message": "Kinnita ülemparooliga" - }, "ssoIdentifierRequired": { "message": "Nõutav on organisatsiooni SSO identifikaator." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Kinnituspäring saadeti adminile." }, - "youWillBeNotifiedOnceApproved": { - "message": "Kinnitamise järel saad selle kohta teavituse." - }, "troubleLoggingIn": { "message": "Kas sisselogimisel on probleeme?" }, @@ -3396,38 +3649,6 @@ "message": "Peida", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Impordin andmed Bitwardenisse?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Kaitse oma LastPassi andmeid ja impordi need Bitwardenisse?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Salvesta ilma krüpteeringuta failina", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Impordi Bitwardenisse", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importimine...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Andmed on edukalt imporditud!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Ilmnes viga. Vaata täpsemaid andmeid konsoolist.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Importimisel ilmnes võrgu viga.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domeen" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Active account" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Available accounts" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "No values to copy" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organization is deactivated" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "You have not selected anything." }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra wide" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index 47a56583c64..b1e1ca3526b 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Pasahitz nagusirako pista (aukerakoa)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Copy notes" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { "message": "Fill", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Pasahitza gogoratzeko pista" - }, - "enterEmailToGetHint": { - "message": "Sartu zure kontuko emaila pasahitz nagusiaren pista jasotzeko." - }, "getMasterPasswordHint": { "message": "Jaso pasahitz nagusiaren pista" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Editatu Karpeta" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "New folder" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Generate passphrase" }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "regeneratePassword": { "message": "Berrezarri pasahitza" }, @@ -454,22 +482,6 @@ "length": { "message": "Luzera" }, - "uppercase": { - "message": "Letra larria (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Letra txikia (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Zenbakiak (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Karaktere bereziak (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Include", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Hitz kopurua" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Zure web nabigatzaileak ez du onartzen arbelean erraz kopiatzea. Eskuz kopiatu." }, - "verifyIdentity": { - "message": "Zure identitatea egiaztatu" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "Zure kutxa gotorra blokeatuta dago. Egiaztatu zure identitatea jarraitzeko." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Restart registration" }, @@ -875,6 +904,9 @@ "no": { "message": "Ez" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "Ustekabeko akatsa gertatu da." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "Erakutsi txartelak fitxa orrian" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Erakutsi elementuen txartelak fitxa orrian, erraz auto-betetzeko." }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "Erakutsi identitateak fitxa orrian" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "Hustu arbela", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Gorde" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Galdetu uneko saio-hasiera eguneratzeko" }, @@ -1084,10 +1169,6 @@ "message": "Argia", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized iluna", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Export from" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Premium erosi" }, - "premiumPurchaseAlert": { - "message": "Zure premium bazkidetza bitwarden.com webguneko kutxa gotorrean ordaindu dezakezu. Orain bisitatu nahi duzu webgunea?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Gogora nazazu" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Berbidali email bidezko egiaztatze-kodea." }, "useAnotherTwoStepMethod": { "message": "Erabili bi urratseko saio hasierarako beste modu bat" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Sartu zure YubiKey-a ordenagailuko USB atakan, ondoren, sakatu bere botoia." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Ireki fitxa berria" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "WebAuthn autentifikatu" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "Ez dago eskuragarri saio-hasierarik" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Bi urratseko saio hasieraren aukerak" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Bi urratseko egiaztatzeko modu guztietarako sarbidea galdu duzu? Erabili zure berreskuratze-kodea zure kontuko bi urratseko egiaztatze hornitzaile guztiak desaktibatzeko." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Ostatze ingurune propioa" }, - "selfHostedEnvironmentFooter": { - "message": "Bitwarden instalatzeko, zehaztu ostatatze propioaren oinarrizko URL-a." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Ingurune pertsonalizatua" }, - "customEnvironmentFooter": { - "message": "Erabiltzaile aurreratuentzat. Zerbitzu bakoitzarentzako oinarrizko URL-a zehaztu dezakezu independienteki." - }, "baseUrl": { "message": "Zerbitzariaren URL-a" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Arrastatu txukuntzeko" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Testua" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Username generator" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "Use this password" }, @@ -2063,12 +2163,24 @@ "message": "to create a strong unique password", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "Kutxa gotorraren itxaronaldiaren ekintza" }, "vaultTimeoutAction1": { "message": "Timeout action" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "Blokeatu", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "Kanporatutako domeinuak" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Akatsa" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Sortu erabiltzaile izena" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Ezarpenak editatu dira" - }, - "environmentEditedClick": { - "message": "Sakatu hemen" - }, - "environmentEditedReset": { - "message": "ezarpen lehenetsiak ezartzeko" - }, "serverVersion": { "message": "Zerbitzariaren bertsioa" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Hasi saioa pasahitz nagusiarekin" }, - "loggingInAs": { - "message": "Honela hasi saioa" - }, - "notYou": { - "message": "Ez zara zu?" - }, "newAroundHere": { "message": "Berria hemendik?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Log in with device" }, - "loginWithDeviceEnabledInfo": { - "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" - }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "View all log in options" }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" - }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "You will be notified once the request is approved" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Eskatu administratzailearen onarpena" }, - "approveWithMasterPassword": { - "message": "Onartu pasahitz nagusiarekin" - }, "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Zure eskaera zure administratzaileari bidali zaio." }, - "youWillBeNotifiedOnceApproved": { - "message": "Jakinaraziko zaizu onartzen denean." - }, "troubleLoggingIn": { "message": "Arazoak saioa hasterakoan?" }, @@ -3396,38 +3649,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Inportatzen...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Datuak zuzen inportatu dira!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Errorea gertatu da inportatzean. Begiratu xehetasunak kontsolan.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Sareko errorea gertatu da inportatzerakoan.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Active account" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Available accounts" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "No values to copy" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organization is deactivated" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "You have not selected anything." }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra wide" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index 2b8a5e20da1..2b3d66ad104 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "یادآور کلمه عبور اصلی (اختیاری)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Copy notes" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { "message": "Fill", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "یادآور کلمه عبور" - }, - "enterEmailToGetHint": { - "message": "برای دریافت یادآور کلمه عبور اصلی خود نشانی ایمیل‌تان را وارد کنید." - }, "getMasterPasswordHint": { "message": "دریافت یادآور کلمه عبور اصلی" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "ويرايش پوشه" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "New folder" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Generate passphrase" }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "regeneratePassword": { "message": "تولید مجدد کلمه عبور" }, @@ -454,22 +482,6 @@ "length": { "message": "طول" }, - "uppercase": { - "message": "حروف بزرگ (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "حروف کوچک (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "اعداد (‪0-9‬)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "نویسه‌های ویژه (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Include", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "تعداد کلمات" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "مرورگر شما از کپی کلیپ بورد آسان پشتیبانی نمی‌کند. به جای آن به صورت دستی کپی کنید." }, - "verifyIdentity": { - "message": "تأیید هویت" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "گاوصندوق شما قفل شده است. برای ادامه هویت خود را تأیید کنید." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Restart registration" }, @@ -875,6 +904,9 @@ "no": { "message": "خیر" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "یک خطای غیر منتظره رخ داده است." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "نمایش کارت‌ها در صفحه برگه" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "برای پر کردن خودکار آسان، موارد کارت را در صفحه برگه فهرست کن." }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "نشان دادن هویت در صفحه برگه" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "پاکسازی کلیپ بورد", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "ذخیره" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "درخواست برای به‌روزرسانی ورود به سیستم موجود" }, @@ -1084,10 +1169,6 @@ "message": "روشن", "description": "Light color" }, - "solarizedDark": { - "message": "تاریک خورشیدی", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "صادرات از" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "خرید پرمیوم" }, - "premiumPurchaseAlert": { - "message": "شما می‌توانید عضویت پرمیوم را از گاوصندوق وب bitwarden.com خریداری کنید. مایلید اکنون از وب‌سایت بازید کنید؟" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "مرا به خاطر بسپار" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "ارسال دوباره ایمیل کد تأیید" }, "useAnotherTwoStepMethod": { "message": "استفاده از روش ورود دو مرحله‌ای دیگر" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "YubiKey خود را وارد پورت USB رایانه کنید، بعد دکمه آن را بفشارید." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "باز کردن زبانه جدید" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "تأیید اعتبار در WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "ورود به سیستم در دسترس نیست" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "گزینه‌های ورود دو مرحله‌ای" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "دسترسی به تمامی ارائه‌دهندگان دو مرحله‌ای را از دست داده‌اید؟ از کد بازیابی خود برای غیرفعال‌سازی ارائه‌دهندگان دو مرحله‌ای از حسابتان استفاده کنید." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "محیط خود میزبان" }, - "selfHostedEnvironmentFooter": { - "message": "نشانی اینترنتی پایه فرضی نصب Bitwarden میزبانی شده را مشخص کنید." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "محیط سفارشی" }, - "customEnvironmentFooter": { - "message": "برای کاربران پیشرفته. شما می‌توانید نشانی پایه هر سرویس را مستقلاً تعیین کنید." - }, "baseUrl": { "message": "نشانی اینترنتی سرور" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "برای مرتب‌سازی بکشید" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "متن" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Username generator" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "Use this password" }, @@ -2063,12 +2163,24 @@ "message": "to create a strong unique password", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "عمل متوقف شدن گاو‌صندوق" }, "vaultTimeoutAction1": { "message": "Timeout action" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "قفل", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "دامنه های مستثنی" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +3022,20 @@ "error": { "message": "خطا" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "ایجاد نام کاربری" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "تنظیمات ویرایش شده اند" - }, - "environmentEditedClick": { - "message": "اینجا کلیک کنید" - }, - "environmentEditedReset": { - "message": "برای بازنشانی به تنظیمات از پیش پیکربندی شده" - }, "serverVersion": { "message": "نسخه سرور" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "با کلمه عبور اصلی وارد شوید" }, - "loggingInAs": { - "message": "در حال ورود به عنوان" - }, - "notYou": { - "message": "شما نیستید؟" - }, "newAroundHere": { "message": "اینجا تازه واردی؟" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "ورود با دستگاه" }, - "loginWithDeviceEnabledInfo": { - "message": "ورود به سیستم با دستگاه باید در تنظیمات برنامه‌ی Bitwarden تنظیم شود. به گزینه دیگری نیاز دارید؟" - }, "fingerprintPhraseHeader": { "message": "عبارت اثر انگشت" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "View all log in options" }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" - }, "notificationSentDevice": { "message": "یک اعلان به دستگاه شما ارسال شده است." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "You will be notified once the request is approved" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "ورود به سیستم آغاز شد" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "کلمه عبور اصلی افشا شده" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "درخواست تأیید مدیر" }, - "approveWithMasterPassword": { - "message": "تأیید با کلمه عبور اصلی" - }, "ssoIdentifierRequired": { "message": "شناسه سازمان SSO مورد نیاز است." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "درخواست شما به مدیرتان فرستاده شد." }, - "youWillBeNotifiedOnceApproved": { - "message": "به محض تأیید مطلع خواهید شد." - }, "troubleLoggingIn": { "message": "در ورود مشکلی دارید؟" }, @@ -3396,38 +3649,6 @@ "message": "دکمه بستن", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "دامنه مستعار" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Active account" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Available accounts" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "No values to copy" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organization is deactivated" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "You have not selected anything." }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra wide" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index 718c0631156..a37bd0235c1 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Pääsalasanan vihje (valinnainen)" }, + "passwordStrengthScore": { + "message": "Salasanan vahvuusluokitus on $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Liity organisaatioon" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Kopioi merkinnät" }, + "copy": { + "message": "Kopioi", + "description": "Copy to clipboard" + }, "fill": { "message": "Täytä", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Syötä tilisi sähköpostiosoite, niin salasanavihjeesi lähetetään sinulle sähköpostitse" }, - "passwordHint": { - "message": "Salasanavihje" - }, - "enterEmailToGetHint": { - "message": "Syötä tilisi sähköpostiosoite saadaksesi pääsalasanan vihjeen." - }, "getMasterPasswordHint": { "message": "Pyydä pääsalasanan vihjettä" }, @@ -302,11 +309,11 @@ "message": "Voit vaihtaa pääsalasanasi Bitwardenin verkkosovelluksessa." }, "fingerprintPhrase": { - "message": "Tunnistelauseke", + "message": "Tunnistelause", "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": "Tilisi tunnistelauseke", + "message": "Tilisi tunnistelause", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "twoStepLogin": { @@ -372,6 +379,15 @@ "editFolder": { "message": "Muokkaa kansiota" }, + "editFolderWithName": { + "message": "Muokkaa kansiota: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "Uusi kansio" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Luo salalause" }, + "passwordGenerated": { + "message": "Salasana luotiin" + }, + "passphraseGenerated": { + "message": "Salalause luotiin" + }, + "usernameGenerated": { + "message": "Käyttäjätunnus luotiin" + }, + "emailGenerated": { + "message": "Sähköpostiosoite luotiin" + }, "regeneratePassword": { "message": "Luo uusi salasana" }, @@ -454,22 +482,6 @@ "length": { "message": "Pituus" }, - "uppercase": { - "message": "Isot kirjaimet (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Pienet kirjaimet (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Numerot (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Erikoismerkit (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Sisällytys", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Sisällytä erikoismerkkejä", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Sanojen määrä" }, @@ -530,7 +538,7 @@ "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { - "message": "Yrityskäytännön säännöt vaikuttavat generaattoriasetuksiisi.", + "message": "Yrityskäytännön säännöt vaikuttavat generaattorisi asetuksiin.", "description": "Indicates that a policy limits the credential generator screen." }, "searchVault": { @@ -558,7 +566,7 @@ "message": "Todennuksen salaisuus" }, "passphrase": { - "message": "Salauslauseke" + "message": "Salalause" }, "favorite": { "message": "Suosikki" @@ -644,9 +652,15 @@ "browserNotSupportClipboard": { "message": "Selaimesi ei tue helppoa leikepöydälle kopiointia. Kopioi kohde manuaalisesti." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Vahvista henkilöllisyytesi" }, + "weDontRecognizeThisDevice": { + "message": "Laitetta ei tunnistettu. Vahvista henkilöllisyytesi syöttämällä sähköpostitse saamasi koodi." + }, + "continueLoggingIn": { + "message": "Jatka kirjautumista" + }, "yourVaultIsLocked": { "message": "Holvisi on lukittu. Jatka vahvistamalla henkilöllisyytesi." }, @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Kirjaudu Bitwardeniin" }, + "enterTheCodeSentToYourEmail": { + "message": "Syötä sähköpostitse saamasi koodi" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Syötä todennussovelluksesi näyttämä koodi" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Tunnistaudu koskettamalla YubiKeytäsi" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo edellyttää tililtäsi kaksivaiheista tunnistautumista. Viimeistele kirjautuminen seuraamalla seuraavia vaiheita." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Viimeistele kirjautuminen seuraamalla seuraavia vaiheita." + }, "restartRegistration": { "message": "Aloita rekisteröityminen alusta" }, @@ -875,6 +904,9 @@ "no": { "message": "En" }, + "location": { + "message": "Sijainti" + }, "unexpectedError": { "message": "Tapahtui odottamaton virhe." }, @@ -986,26 +1018,29 @@ "addLoginNotificationDescAlt": { "message": "Ehdota kohteen tallennusta, jos holvistasi ei vielä löydy vastaavaa kohdetta. Koskee kaikkia kirjautuneita tilejä." }, - "showCardsInVaultView": { - "message": "Näytä kortit automaattitäytön ehdotuksina Holvi-näkymässä" + "showCardsInVaultViewV2": { + "message": "Näytä kortit aina automaattitäytön ehdotuksina Holvi-näkymässä" }, "showCardsCurrentTab": { "message": "Näytä kortit välilehtiosiossa" }, "showCardsCurrentTabDesc": { - "message": "Näytä kortit Välilehti-sivulla automaattitäytön helpottamiseksi." + "message": "Näytä kortit Välilehti-näkymässä automaattitäytön helpottamiseksi." }, - "showIdentitiesInVaultView": { - "message": "Näytä henkilöllisyydet automaattitäytön ehdotuksina Holvi-sivulla." + "showIdentitiesInVaultViewV2": { + "message": "Näytä henkilöllisyydet aina automaattitäytön ehdotuksina Holvi-näkymässä" }, "showIdentitiesCurrentTab": { "message": "Näytä henkilöllisyydet välilehtiosiossa" }, "showIdentitiesCurrentTabDesc": { - "message": "Näytä henkilöllisyydet Välilehti-sivulla automaattitäytön helpottamiseksi." + "message": "Näytä henkilöllisyydet Välilehti-näkymässä automaattitäytön helpottamiseksi." }, "clickToAutofillOnVault": { - "message": "Click items to autofill on Vault view" + "message": "Täytä kohteet Holvi-näkymästä klikkaamalla niitä" + }, + "clickToAutofill": { + "message": "Täytä automaattitäytön ehdotus napsauttamalla sitä" }, "clearClipboard": { "message": "Tyhjennä leikepöytä", @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Tallenna" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ tallennettiin Bitwardeniin.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ päivitettiin Bitwardeniin.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Tallenna uutena kirjautumistietona", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Päivitä kirjautumistieto", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Tallennetaanko kirjautumistieto?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Päivitetäänkö olemassaoleva kirjautumistieto?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Kirjautumistieto tallennettiin", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Kirjautumistieto päivitettiin", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Virhe tallennettaessa", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Voi ei! Emme voineet tallentaa tätä. Yritä syöttää tiedot manuaalisesti.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Kysy päivitetäänkö kirjautumistieto" }, @@ -1084,10 +1169,6 @@ "message": "Vaalea", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized, tumma", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Vie lähteestä" }, @@ -1245,7 +1326,7 @@ "message": "Varmuuskäyttö" }, "premiumSignUpTwoStepOptions": { - "message": "Omisteiset kaksivaiheisen kirjautumisen vaihtoehdot, kuten YubiKey ja Duo." + "message": "Kaksivaiheisen kirjautumisen erikoisvaihtoehdot, kuten YubiKey ja Duo." }, "ppremiumSignUpReports": { "message": "Salasanahygienian, tilin terveyden ja tietovuotojen raportointitoiminnot pitävät holvisi turvassa." @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Osta Premium" }, - "premiumPurchaseAlert": { - "message": "Voit ostaa Premium-jäsenyyden bitwarden.com-verkkoholvista. Haluatko avata sivuston nyt?" - }, "premiumPurchaseAlertV2": { "message": "Voit ostaa Premiumin tiliasetuksistasi Bitwardenin verkkosovelluksen kautta." }, @@ -1343,14 +1421,24 @@ "rememberMe": { "message": "Muista minut" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Älä kysy uudelleen tällä laitteella 30 päivään" + }, "sendVerificationCodeEmailAgain": { "message": "Lähetä todennuskoodi sähköpostitse uudelleen" }, "useAnotherTwoStepMethod": { "message": "Käytä vaihtoehtoista todennustapaa" }, + "selectAnotherMethod": { + "message": "Valitse vaihtoehtoinen tapa", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Käytä palautuskoodiasi" + }, "insertYubiKey": { - "message": "Kytke YubiKey-todennuslaitteesi tietokoneen USB-porttiin ja paina sen painiketta." + "message": "Kytke YubiKey-suojausavaimesi tietokoneen USB-porttiin ja kosketa sen painiketta." }, "insertU2f": { "message": "Kytke suojausavaimesi tietokoneen USB-porttiin. Jos laitteessa on painike, paina sitä." @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Avaa uusi välilehti" }, + "openInNewTab": { + "message": "Avaa uudessa välilehdessä" + }, "webAuthnAuthenticate": { "message": "WebAuthn-todennus" }, + "readSecurityKey": { + "message": "Lue suojausavain" + }, + "awaitingSecurityKeyInteraction": { + "message": "Odotetaan suojausavaimen aktivointia..." + }, "loginUnavailable": { "message": "Kirjautuminen ei ole käytettävissä" }, @@ -1376,8 +1473,11 @@ "twoStepOptions": { "message": "Kaksivaiheisen kirjautumisen asetukset" }, + "selectTwoStepLoginMethod": { + "message": "Valitse todennustapa" + }, "recoveryCodeDesc": { - "message": "Etkö pysty käyttämään kaksivaiheisen kirjautumisen todentajiasi? Poista kaikki tilillesi määritetyt todentajat käytöstä palautuskoodillasi." + "message": "Etkö voi käyttää kaksivaiheisen kirjautumisen todentajiasi? Poista kaikki tilillesi määritetyt todentajat käytöstä palautuskoodillasi." }, "recoveryCodeTitle": { "message": "Palautuskoodi" @@ -1393,7 +1493,7 @@ "message": "Yubico OTP -suojausavain" }, "yubiKeyDesc": { - "message": "Käytä YubiKey-todennuslaitetta tilisi avaukseen. Toimii YubiKey 4, 4 Nano, 4C sekä NEO -laitteiden kanssa." + "message": "Käytä YubiKey-suojausavainta tilisi avaukseen. Toimii YubiKey 4, 4 Nano, 4C ja NEO -laitteiden kanssa." }, "duoDescV2": { "message": "Syötä Duo Securityn luoma koodi.", @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Itse ylläpidetty palvelinympäristö" }, - "selfHostedEnvironmentFooter": { - "message": "Määritä omassa palvelinympäristössäsi suoritettavan Bitwarden-asennuksen perusosoite." - }, "selfHostedBaseUrlHint": { "message": "Määritä itse ylläpitämäsi Bitwarden-asennuksen perusosoite. Esimerkki: https://bitwarden.yritys.fi." }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Mukautettu palvelinympäristö" }, - "customEnvironmentFooter": { - "message": "Edistyneille käyttäjille. Voit määrittää jokaiselle palvelulle oman perusosoitteen." - }, "baseUrl": { "message": "Palvelimen URL" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Järjestä raahaamalla" }, + "dragToReorder": { + "message": "Järjestä vetämällä" + }, "cfTypeText": { "message": "Teksti" }, @@ -1597,7 +1694,7 @@ "message": "Painallus ponnahdusikkunan ulkopuolelle todennuskoodin sähköpostista noutoa varten sulkee ikkunan. Haluatko avata näkymän uuteen ikkunaan, jotta se pysyy avoinna?" }, "popupU2fCloseMessage": { - "message": "Tämä selain ei voi käsitellä U2F-pyyntöjä tässä ponnahdusikkunassa. Haluatko avata näkymän uuteen ikkunaan, jotta voit vahvistaa kirjautumisen U2F-todennuslaitteella?" + "message": "Tämä selain ei voi käsitellä U2F-pyyntöjä tässä ponnahdusikkunassa. Haluatko avata näkymän uuteen ikkunaan vahvistaaksesi kirjautumisen U2F-suojausavaimella?" }, "enableFavicon": { "message": "Näytä verkkosivustojen kuvakkeet" @@ -1983,7 +2080,7 @@ "message": "Heikko pääsalasana" }, "weakMasterPasswordDesc": { - "message": "Valitsemasi pääsalasana on heikko. Sinun tulisi käyttää vahvaa pääsalasanaa (tai salauslauseketta) suojataksesi Bitwarden-tilisi kunnolla. Haluatko varmasti käyttää tätä pääsalasanaa?" + "message": "Valitsemasi pääsalasana on heikko. Sinun tulisi käyttää vahvaa pääsalasanaa (tai salauslausetta) suojataksesi Bitwarden-tilisi kunnolla. Haluatko varmasti käyttää tätä pääsalasanaa?" }, "pin": { "message": "PIN", @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Käyttäjätunnusgeneraattori" }, + "useThisEmail": { + "message": "Käytä tätä sähköpostia" + }, "useThisPassword": { "message": "Käytä tätä salasanaa" }, @@ -2063,12 +2163,24 @@ "message": "luodaksesi vahvan ainutlaatuisen salasanan", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Holvin mukautus" + }, "vaultTimeoutAction": { "message": "Holvin aikakatkaisutoiminto" }, "vaultTimeoutAction1": { "message": "Aikakatkaisutoiminto" }, + "newCustomizationOptionsCalloutTitle": { + "message": "Uusia mukautusvaihtoehtoja" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Mukauta holvikokemustasi pikakopiointitoiminnoilla, kompaktilla tilalla ja muulla!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "Näytä kaikki ulkoasuasetukset" + }, "lock": { "message": "Lukitse", "description": "Verb form: to make secure or inaccessible by" @@ -2150,7 +2262,7 @@ "message": "Yksi tai useampi organisaatiokäytäntö edellyttää, että pääsalasanasi täyttää seuraavat vaatimukset:" }, "policyInEffectMinComplexity": { - "message": "Monimutkaisuuden vähimmäispistemäärä on $SCORE$", + "message": "Monimutkaisuuden vähimmäisluokitus on $SCORE$", "placeholders": { "score": { "content": "$1", @@ -2324,6 +2436,12 @@ "message": "Verkkotunnukset", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Estetyt verkkotunnukset" + }, + "learnMoreAboutBlockedDomains": { + "message": "Lisätietoja estetyistä verkkotunnuksista" + }, "excludedDomains": { "message": "Ohitettavat verkkotunnukset" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden ei pyydä kirjautumistietojen tallennusta näillä verkkotunnuksilla. Koskee kaikkia kirjautuneita tilejä. Ota muutokset käyttöön päivittämällä sivu." }, + "blockedDomainsDesc": { + "message": "Näille sivustoille ei tarjota automaattista täyttöä eikä muita siihen liittyviä ominaisuuksia. Sinun on päivitettävä sivu, jotta muutokset tulevat voimaan." + }, + "autofillBlockedNoticeV2": { + "message": "Automaattitäyttö on estetty tällä sivustolla." + }, + "autofillBlockedNoticeGuidance": { + "message": "Muuta tätä asetuksissa" + }, + "change": { + "message": "Vaihda" + }, + "changeButtonTitle": { + "message": "Vaihda salasana - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "Vaarantuneet salasanat" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ pyytää sinua vaihtamaan yhden salasanan, koska se on vaarassa.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ pyytää sinua vaihtamaan nämä $COUNT$ salasanaa, koska ne ovat vaarassa.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Organisaatiosi pyytävät sinua vaihtamaan nämä $COUNT$ salasanaa, koska ne ovat vaarassa.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Tarkasta ja vaihda yksi vaarantunut salasana" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Tarkasta ja vaihda $COUNT$ vaarantunutta salasanaa", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Vaihda vaarantuneet salasanat nopeammin" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Päivitä asetuksesi, jotta voit täyttää salasanasi ja luoda uusia nopeasti." + }, + "reviewAtRiskLogins": { + "message": "Tarkista vaarantuneet kirjautumistiedot" + }, + "reviewAtRiskPasswords": { + "message": "Tarkista vaarantuneet salasanat" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Organisaatiosalasanasi ovat vaarantuneet, koska ne ovat heikkoja, toistuvasti käytettyjä ja/tai paljastuneita.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Kuvitus vaarantuneiden kirjautumistietojen luettelosta" + }, + "generatePasswordSlideDesc": { + "message": "Luo vahva ja ainutlaatuinen salasana nopeasti Bitwardenin automaattitäytön valikosta vaarantuneella sivustolla.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Kuvitus Bitwardenin automaattitäytön valikosta, luodulla salasanalla" + }, + "updateInBitwarden": { + "message": "Päivitä Bitwardenissa" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden pyytää sinua sitten päivittämään salasanan salasanahallinnassa.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Kuvitus ilmoituksesta, jossa Bitwarden tarjoaa kirjautumistiedon päivitystä" + }, + "turnOnAutofill": { + "message": "Ota automaattitäyttö käyttöön" + }, + "turnedOnAutofill": { + "message": "Automaattitäyttö otettiin käyttöön" + }, + "dismiss": { + "message": "Sulje" + }, "websiteItemLabel": { "message": "Verkkotunnus $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Estetyn verkkotunnuksen muutokset tallennettu" + }, "excludedDomainsSavedSuccess": { "message": "Rajoitettujen verkkotunnusten muutokset tallennettiin" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Virhe" }, + "decryptionError": { + "message": "Salauksen purkuvirhe" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden ei pystynyt purkamaan alla lueteltuja holvin kohteita." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Ota yhteyttä asiakkaaseen", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "lisätietojen menettämisen välttämiseksi.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Luo käyttäjätunnus" }, @@ -2820,7 +3067,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " Käytä $RECOMMENDED$ tai useampaa sanaa vahvan salalauseen luomiseen.", + "message": " Käytä vahvaan salalauseeseen ainakin $RECOMMENDED$ sanaa.", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ hylkäsi pyyntösi. Ole yhteydessä palveluntarjoajaasi saadaksesi apua.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ hylkäsi pyyntösi: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "$SERVICENAME$ -palvelun peittämän sähköpostitilin tunnusta ei saatu.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Asetuksia on muokattu" - }, - "environmentEditedClick": { - "message": "Paina tästä" - }, - "environmentEditedReset": { - "message": "palauttaaksesi oletusasetukset" - }, "serverVersion": { "message": "Palvelimen versio" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Kirjaudu pääsalasanalla" }, - "loggingInAs": { - "message": "Kirjaudutaan tunnuksella" - }, - "notYou": { - "message": "Etkö se ollut sinä?" - }, "newAroundHere": { "message": "Oletko uusi täällä?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Laitteella kirjautuminen" }, - "loginWithDeviceEnabledInfo": { - "message": "Laitteella kirjautuminen on määritettävä Bitwarden-sovelluksen asetuksista. Tarvitsetko eri vaihtoehdon?" - }, "fingerprintPhraseHeader": { "message": "Tunnistelauseke" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "Näytä kaikki kirjautumisvaihtoehdot" }, - "viewAllLoginOptionsV1": { - "message": "Näytä kaikki kirjautumisvaihtoehdot" - }, "notificationSentDevice": { "message": "Laitteellesi on lähetetty ilmoitus." }, + "notificationSentDevicePart1": { + "message": "Avaa Bitwarden laitteellasi tai" + }, + "notificationSentDeviceAnchor": { + "message": "verkkosovelluksena" + }, + "notificationSentDevicePart2": { + "message": "Ennen hyväksyntää varmista, että tunnistelause vastaa alla olevaa lausetta." + }, "aNotificationWasSentToYourDevice": { "message": "Laitteeseesi lähetettiin ilmoitus" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Varmista, että vahvistavan laitteen holvi on avattu ja että se näyttää saman tunnistelausekkeen" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "Sinulle ilmoitetaan, kun pyyntö on hyväksytty" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Kirjautuminen aloitettu" }, + "logInRequestSent": { + "message": "Pyyntö lähetetty" + }, "exposedMasterPassword": { "message": "Paljastunut pääsalasana" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Pyydä hyväksyntää ylläpidolta" }, - "approveWithMasterPassword": { - "message": "Hyväksy pääsalasanalla" - }, "ssoIdentifierRequired": { "message": "Organisaation kertakirjautumistunniste tarvitaan." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Pyyntösi on välitetty ylläpidollesi." }, - "youWillBeNotifiedOnceApproved": { - "message": "Saat ilmoituksen kun se on hyväksytty." - }, "troubleLoggingIn": { "message": "Ongelmia kirjautumisessa?" }, @@ -3396,38 +3649,6 @@ "message": "Laajenna tai supista", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Haluatko tuoda tietosi Bitwardeniin?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Haluatko suojata LastPass-tietosi tuomalla ne Bitwardeniin?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Tallenna salaamattomana tiedostona", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Tuo Bitwardeniin", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Tuodaan...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Tietojen tuonti onnistui.", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Tuontivirhe. Näet isätietoja hallinnasta.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Verkkovirhe tuonnin aikana.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Aliaksen verkkotunnus" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Aktiivinen tili" }, + "bitwardenAccount": { + "message": "Bitwarden-tili" + }, "availableAccounts": { "message": "Käytettävissä olevat tilit" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Automaattitäytön ehdotukset" }, + "itemSuggestions": { + "message": "Ehdotetut kohteet" + }, "autofillSuggestionsTip": { "message": "Tallenna tälle sivustolle automaattisesti täytettävä kirjautumistieto." }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Kopioi $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "Ei kopioitavia arvoja" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Kohteen nimi" }, - "cannotRemoveViewOnlyCollections": { - "message": "Et voi poistaa kokoelmia, joihin sinulla on vain tarkasteluoikeus: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organisaatio on poistettu käytöstä" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Siirrä verkkosivuston URIa. Liikuta kohdetta ylös- tai alaspäin nuolinäppäimillä." + }, "reorderFieldUp": { "message": "$LABEL$ siirrettiin ylemmäs, sijainti: $INDEX$/$LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "Et ole valinnut mitään." }, - "movedItemsToOrg": { - "message": "Valitut kohteet siirrettiin organisaatiolle $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Kohteet siirrettiin organisaatiolle $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Teksti-Sendit" }, - "bitwardenNewLook": { - "message": "Bitwardenilla on uusi ulkoasu!" - }, - "bitwardenNewLookDesc": { - "message": "Automaattinen täyttö ja sisällön haku Holvi-välilehdeltä on nyt entistä helpompaa ja luontevampaa. Kokeile nyt!" - }, "accountActions": { "message": "Tilitoiminnot" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "Sinulla ei ole oikeutta muokata tätä kohdetta" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrinen avaus ei ole käytettävissä, koska PIN-koodi tai salasanan lukituksen avaus vaaditaan ensin." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrinen avaus ei tällä hetkellä ole käytettävissä." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrinen avaus ei ole käytettävissä, koska järjestelmätiedostoja ei ole määritetty." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrinen avaus ei ole käytettävissä, koska järjestelmätiedostoja ei ole määritetty." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometrinen avaus ei ole käytettävissä, koska Bitwardenin työpöytäsovellus on suljettu." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometrinen avaus ei ole käytettävissä, koska sitä ei ole otettu käyttöön osoitteelle $EMAIL$ Bitwardenin työpöytäsovelluksessa.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrinen avaus ei ole tällä hetkellä käytettävissä tuntemattomasta syystä." + }, "authenticating": { "message": "Todennetaan" }, @@ -4798,7 +5045,7 @@ "message": "Luotu salasana" }, "compactMode": { - "message": "Pienikokoinen tila" + "message": "Kompakti tila" }, "beta": { "message": "Beta" @@ -4813,13 +5060,13 @@ "message": "Bitwarden lähettää tilisi sähköpostiosoitteeseen koodin, jolla voit vahvistaa kirjautumiset uusista laitteista helmikuusta 2025 alkaen." }, "newDeviceVerificationNoticeContentPage2": { - "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + "message": "Voit määrittää kaksivaiheisen kirjautumisen tilisi vaihtoehtoiseksi suojaustavaksi tai vaihtaa sähköpostiosoitteesi sellaiseen, johon sinulla on pääsy." }, "remindMeLater": { "message": "Muistuta myöhemmin" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", + "message": "Onko sinulla luotettava pääsy sähköpostiisi, $EMAIL$?", "placeholders": { "email": { "content": "$1", @@ -4828,10 +5075,10 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "Ei ole" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" + "message": "Kyllä on" }, "turnOnTwoStepLogin": { "message": "Ota kaksivaiheinen kirjautuminen käyttöön" @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Erittäin leveä" + }, + "sshKeyWrongPassword": { + "message": "Syöttämäsi salasana on virheellinen." + }, + "importSshKey": { + "message": "Tuo" + }, + "confirmSshKeyPassword": { + "message": "Vahvista salasana" + }, + "enterSshKeyPasswordDesc": { + "message": "Syötä SSH-avaimen salasana." + }, + "enterSshKeyPassword": { + "message": "Syötä salasana" + }, + "invalidSshKey": { + "message": "SSH-avain on virheellinen" + }, + "sshKeyTypeUnsupported": { + "message": "SSH-avaintyyppiä ei ole tuettu" + }, + "importSshKeyFromClipboard": { + "message": "Tuo avain leikepöydältä" + }, + "sshKeyImported": { + "message": "SSH-avain on tuotu" + }, + "cannotRemoveViewOnlyCollections": { + "message": "Et voi poistaa kokoelmia, joihin sinulla on vain tarkasteluoikeus: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Päivitä työpöytäsovellus" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "Käyttääksesi biometristä avausta, päivitä työpöytäsovelluksesi tai poista tunnistelauseke käytöstä työpöydän asetuksista." + }, + "changeAtRiskPassword": { + "message": "Vaihda vaarantunut salasana" } } diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index a504cbd3174..9f991843f4f 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Mungkahi sa Master Password (opsyonal)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Copy notes" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { "message": "Fill", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Mungkahi sa Password" - }, - "enterEmailToGetHint": { - "message": "Ipasok ang iyong email address ng account para makatanggap ng hint ng iyong master password." - }, "getMasterPasswordHint": { "message": "Makuha ang Punong Password na Hint" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "I-edit ang folder" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "New folder" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Generate passphrase" }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "regeneratePassword": { "message": "Muling I-generate ang Password" }, @@ -454,22 +482,6 @@ "length": { "message": "Kahabaan" }, - "uppercase": { - "message": "Uppercase (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Lowercase (a-t) sa Filipino ay mababang-letra (a-t)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Numbers (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Mga espesyal na character (!@#$%^&*) sa Filipino ay tinatawag na mga simbolong pambihira", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Include", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Ang bilang ng mga salita\n\nNumero ng mga salita" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Hindi suportado ng iyong web browser ang madaling pag-copy ng clipboard. Kopya ito manually sa halip." }, - "verifyIdentity": { - "message": "I-verify ang pagkakakilanlan" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "Naka-lock ang iyong vault. Patunayan ang iyong pagkakakilanlan upang magpatuloy." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Restart registration" }, @@ -875,6 +904,9 @@ "no": { "message": "Hindi" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "Namana ang isang hindi inaasahang error." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Hilingin na magdagdag ng isang item kung ang isa ay hindi mahanap sa iyong vault. Nalalapat sa lahat ng naka-log in na account." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "Ipakita ang mga card sa Tab page" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Itala ang mga item ng card sa Tab page para sa madaling auto-fill." }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "Ipakita ang mga pagkatao sa Tab page" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "Linisin ang clipboard", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "I-save" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Tanungin ang update ng umiiral na login" }, @@ -1084,10 +1169,6 @@ "message": "Mabait", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized dark", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Export from" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Bilhin ang Premium" }, - "premiumPurchaseAlert": { - "message": "Maaari kang mamili ng membership sa Premium sa website ng bitwarden.com. Gusto mo bang bisitahin ang website ngayon?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Tandaan mo ako" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Ipadala muli ang email ng verification code" }, "useAnotherTwoStepMethod": { "message": "Gamitin ang isa pang two-step na paraan ng pag-login" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "I-insert ang iyong YubiKey sa USB port ng iyong computer, pagkatapos ay tindigin ang buton nito." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Buksan ang bagong tab" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "I-authenticate ang WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "Hindi magagamit ang pag-login" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Mga pagpipilian para sa two-step login" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Nawalan ka ng access sa lahat ng iyong mga two-factor provider? Gamitin ang iyong recovery code para i-off ang lahat ng two-factor providers mula sa iyong account." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Kapaligirang self-hosted" }, - "selfHostedEnvironmentFooter": { - "message": "Tukuyin ang base URL ng iyong Bitwarden installation na naka-host sa on-premises." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Kapaligirang Custom" }, - "customEnvironmentFooter": { - "message": "Para sa mga advanced na gumagamit. Maaari mong tukuyin ang base URL ng bawat serbisyo nang independiyente." - }, "baseUrl": { "message": "URL ng Server" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "I-drag upang i-sort" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Teksto" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Username generator" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "Use this password" }, @@ -2063,12 +2163,24 @@ "message": "to create a strong unique password", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "Aksyon sa Vault timeout" }, "vaultTimeoutAction1": { "message": "Timeout action" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "I-lock", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "Inilayo na Domain" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Mali" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Lumikha ng username" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Nabago ang mga setting" - }, - "environmentEditedClick": { - "message": "Mag-click dito" - }, - "environmentEditedReset": { - "message": "i-reset sa pre-configured na mga setting" - }, "serverVersion": { "message": "Bersyon ng server" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Mag-login gamit ang pangunahing password" }, - "loggingInAs": { - "message": "Naglolog-in bilang" - }, - "notYou": { - "message": "Hindi ikaw?" - }, "newAroundHere": { "message": "Mag-login gamit ang pangunahing password?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Mag log in gamit ang device" }, - "loginWithDeviceEnabledInfo": { - "message": "Ang pag log in gamit ang device ay dapat na naka set up sa mga setting ng Bitwarden app. Kailangan mo ng ibang opsyon?" - }, "fingerprintPhraseHeader": { "message": "Hulmabig ng Hilik ng Dako" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "View all log in options" }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" - }, "notificationSentDevice": { "message": "Naipadala na ang notification sa iyong device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "You will be notified once the request is approved" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Nakalantad na Master Password" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3396,38 +3649,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Active account" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Available accounts" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "No values to copy" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organization is deactivated" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "You have not selected anything." }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra wide" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index 9566f457861..32b6dd0296b 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -7,7 +7,7 @@ "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Chez vous, au travail, n'importe où, Bitwarden sécurise mots de passe, clés d'accès et informations sensibles", + "message": "Où que vous soyez, Bitwarden sécurise tous vos mots de passe, clés d'accès et informations sensibles", "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { @@ -20,7 +20,7 @@ "message": "Créer un compte" }, "newToBitwarden": { - "message": "Nouveau sur Bitwarden ?" + "message": "Nouveau sur Bitwarden ?" }, "logInWithPasskey": { "message": "Se connecter avec une clé d'accès" @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Indice du mot de passe principal (facultatif)" }, + "passwordStrengthScore": { + "message": "Score de force du mot de passe $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Rejoindre l'organisation" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Copier les notes" }, + "copy": { + "message": "Copier", + "description": "Copy to clipboard" + }, "fill": { "message": "Remplir", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -230,7 +243,7 @@ "message": "Connectez-vous à votre coffre" }, "autoFillInfo": { - "message": "Il n'y a aucun identifiant disponible pour le remplissage automatique de l'onglet actuel du navigateur." + "message": "Il n'y a aucun identifiant disponible pour la saisie automatique concernant l'onglet actuel du navigateur." }, "addLogin": { "message": "Ajouter un identifiant" @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Saisissez l'adresse courriel de votre compte et votre indice de mot de passe vous sera envoyé" }, - "passwordHint": { - "message": "Indice de mot de passe" - }, - "enterEmailToGetHint": { - "message": "Saisissez l'adresse électronique de votre compte pour recevoir l'indice de votre mot de passe principal." - }, "getMasterPasswordHint": { "message": "Obtenir l'indice du mot de passe principal" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Modifier le dossier" }, + "editFolderWithName": { + "message": "Éditer le dossier : $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "Nouveau dossier" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Générer une phrase de passe" }, + "passwordGenerated": { + "message": "Mot de passe généré" + }, + "passphraseGenerated": { + "message": "Phrase de passe générée" + }, + "usernameGenerated": { + "message": "Nom d'utilisateur généré" + }, + "emailGenerated": { + "message": "Courriel généré" + }, "regeneratePassword": { "message": "Régénérer un mot de passe" }, @@ -454,22 +482,6 @@ "length": { "message": "Longueur" }, - "uppercase": { - "message": "Majuscules (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Minuscules (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Chiffres (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Caractères spéciaux (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Inclure", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Inclure des caractères spéciaux", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Nombre de mots" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Votre navigateur web ne supporte pas la copie rapide depuis le presse-papier. Copiez-le manuellement à la place." }, - "verifyIdentity": { - "message": "Vérifier l'identité" + "verifyYourIdentity": { + "message": "Vérifiez votre identité" + }, + "weDontRecognizeThisDevice": { + "message": "Nous ne reconnaissons pas cet appareil. Saisissez le code envoyé à votre courriel pour vérifier votre identité." + }, + "continueLoggingIn": { + "message": "Continuer à se connecter" }, "yourVaultIsLocked": { "message": "Votre coffre est verrouillé. Vérifiez votre identité pour continuer." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Se connecter à Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Saisissez le code envoyé par courriel" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Saisissez le code de votre application d'authentification" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Appuyez sur votre YubiKey pour vous authentifier" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Les identifiants de l'authentification à deux facteurs Duo sont requis pour votre compte. Suivez les étapes ci-dessous afin de réussir à vous connecter." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Suivez les étapes ci-dessous afin de réussir à vous connecter." + }, "restartRegistration": { "message": "Redémarrer l'inscription" }, @@ -875,6 +904,9 @@ "no": { "message": "Non" }, + "location": { + "message": "Emplacement" + }, "unexpectedError": { "message": "Une erreur inattendue est survenue." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Demande l'ajout d'un élément si celui-ci n'est pas trouvé dans votre coffre. S'applique à tous les comptes connectés." }, - "showCardsInVaultView": { - "message": "Afficher les cartes de paiement en tant que suggestions de saisie automatique dans la vue du coffre" + "showCardsInVaultViewV2": { + "message": "Toujours afficher les cartes de paiement en tant que suggestions de saisie automatique dans l'affichage du coffre" }, "showCardsCurrentTab": { "message": "Afficher les cartes de paiement sur la Page d'onglet" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Liste les éléments des cartes de paiement sur la Page d'onglet pour faciliter la saisie automatique." }, - "showIdentitiesInVaultView": { - "message": "Afficher les identités en tant que suggestions de saisie automatique dans la vue du coffre" + "showIdentitiesInVaultViewV2": { + "message": "Toujours afficher les identités en tant que suggestions de saisie automatique dans l'affichage du coffre" }, "showIdentitiesCurrentTab": { "message": "Afficher les identités sur la Page d'onglet" @@ -1005,7 +1037,10 @@ "message": "Liste les éléments d'identité sur la Page d'onglet pour faciliter la saisie automatique." }, "clickToAutofillOnVault": { - "message": "Click items to autofill on Vault view" + "message": "Cliquez sur les éléments dans l'affichage du coffre pour la saisie automatique" + }, + "clickToAutofill": { + "message": "Cliquez sur les éléments de la suggestion de saisie automatique pour les remplir" }, "clearClipboard": { "message": "Effacer le presse-papiers", @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Enregistrer" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ enregistré dans Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ mis à jour dans Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Enregistrer en tant que nouvel identifiant", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Mettre à jour l'identifiant", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Enregistrer l'identifiant ?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Mettre à jour de l'identifiant existant ?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Identifiant enregistré", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Identifiant mis à jour", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Erreur lors de l'enregistrement", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh non ! Nous n'avons pas pu enregistrer cela. Essayez de saisir les détails manuellement.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Demander de mettre à jour un identifiant existant" }, @@ -1084,10 +1169,6 @@ "message": "Clair", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized Dark", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Exporter à partir de" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Acheter Premium" }, - "premiumPurchaseAlert": { - "message": "Vous pouvez acheter une adhésion Premium sur le coffre web de bitwarden.com. Voulez-vous visiter le site web maintenant ?" - }, "premiumPurchaseAlertV2": { "message": "Vous pouvez acheter la version Premium depuis les paramètres de votre compte dans l'application web Bitwarden." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Rester connecté" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Ne plus demander sur cet appareil pendant 30 jours" + }, "sendVerificationCodeEmailAgain": { "message": "Envoyer à nouveau le courriel de code de vérification" }, "useAnotherTwoStepMethod": { "message": "Utiliser une autre méthode d'identification en deux étapes" }, + "selectAnotherMethod": { + "message": "Sélectionnez une autre méthode", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Utilisez votre code de récupération" + }, "insertYubiKey": { "message": "Insérez votre YubiKey dans le port USB de votre ordinateur puis appuyez sur son bouton." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Ouvrir un nouvel onglet" }, + "openInNewTab": { + "message": "Ouvrir dans un nouvel onglet" + }, "webAuthnAuthenticate": { "message": "Authentifier WebAuthn" }, + "readSecurityKey": { + "message": "Lire la clé de sécurité" + }, + "awaitingSecurityKeyInteraction": { + "message": "En attente d'interaction de la clé de sécurité..." + }, "loginUnavailable": { "message": "Identifiant indisponible" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Options d'authentification à feux facteurs" }, + "selectTwoStepLoginMethod": { + "message": "Sélectionnez la méthode d'authentification à deux facteurs" + }, "recoveryCodeDesc": { "message": "Accès perdu à tous vos services d'authentification à double facteurs ? Utilisez votre code de récupération pour désactiver tous les services de double authentifications sur votre compte." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Environnement auto-hébergé" }, - "selfHostedEnvironmentFooter": { - "message": "Spécifiez l'URL de base de votre installation Bitwarden auto-hébergée." - }, "selfHostedBaseUrlHint": { "message": "Spécifiez l'URL de base de votre installation autohébergée par Bitwarden. Exemple : https://bitwarden.compagnie.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Environnement personnalisé" }, - "customEnvironmentFooter": { - "message": "Pour utilisateurs avancés. Vous pouvez spécifier une URL de base indépendante pour chaque service." - }, "baseUrl": { "message": "URL du serveur" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Glissez pour trier" }, + "dragToReorder": { + "message": "Faire glisser pour réorganiser" + }, "cfTypeText": { "message": "Texte" }, @@ -1771,7 +1868,7 @@ "message": "Clé SSH" }, "newItemHeader": { - "message": "Nouveau/nouvelle $TYPE$", + "message": "Créer un(e) $TYPE$", "placeholders": { "type": { "content": "$1", @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Générateur de nom d'utilisateur" }, + "useThisEmail": { + "message": "Utiliser ce courriel" + }, "useThisPassword": { "message": "Utiliser ce mot de passe" }, @@ -2063,12 +2163,24 @@ "message": "pour créer un mot de passe fort unique", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Personnalisation du coffre" + }, "vaultTimeoutAction": { "message": "Action après délai d'expiration du coffre" }, "vaultTimeoutAction1": { "message": "Expiration de l'action" }, + "newCustomizationOptionsCalloutTitle": { + "message": "Nouvelles options de personnalisation" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Personnalisez votre expérience utilisateur du coffre avec des actions de copie rapide, un mode compact et bien plus encore !" + }, + "newCustomizationOptionsCalloutLink": { + "message": "Afficher tous les paramètres d'apparence" + }, "lock": { "message": "Verrouiller", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domaines", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Domaines bloqués" + }, + "learnMoreAboutBlockedDomains": { + "message": "En savoir plus sur les domaines bloqués" + }, "excludedDomains": { "message": "Domaines exclus" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden ne demandera pas d'enregistrer les détails de connexion pour ces domaines pour tous les comptes connectés. Vous devez actualiser la page pour que les modifications prennent effet." }, + "blockedDomainsDesc": { + "message": "La saisie automatique et d'autres fonctionnalités connexes ne seront pas proposées pour ces sites web. Vous devez actualiser la page pour que les modifications soient prises en compte." + }, + "autofillBlockedNoticeV2": { + "message": "La saisie automatique est bloquée pour ce site web." + }, + "autofillBlockedNoticeGuidance": { + "message": "Modifier ceci dans les paramètres" + }, + "change": { + "message": "Modifier" + }, + "changeButtonTitle": { + "message": "Modifier le mot de passe - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "Mots de passe à risque" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ vous demande de modifier un mot de passe car il est à risque.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ vous demande de modifier les mots de passe de $COUNT$ parce qu’ils sont à risque.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Vos organisations vous demandent de modifier les mots de passe $COUNT$ car ils sont à risque.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Examiner et modifier un mot de passe à risque" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Examiner et modifier les mots de passe à risque $COUNT$", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Modifier plus rapidement les mots de passe à risque" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Mettez à jour vos paramètres afin de pouvoir rapidement saisir automatiquement vos mots de passe et en générer de nouveaux" + }, + "reviewAtRiskLogins": { + "message": "Examiner les identifiants à risque" + }, + "reviewAtRiskPasswords": { + "message": "Examiner les mots de passe à risque" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Les mots de passe de votre organisation sont à risque, car ils sont faibles, réutilisés et/ou exposés.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration d'une liste d'identifiants à risque" + }, + "generatePasswordSlideDesc": { + "message": "Générez rapidement un mot de passe fort et unique grâce au menu de saisie automatique de Bitwarden sur le site à risque.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration du menu de saisie automatique de Bitwarden affichant un mot de passe généré" + }, + "updateInBitwarden": { + "message": "Mettre à jour dans Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden vous demandera alors de mettre à jour le mot de passe dans le gestionnaire de mots de passe.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration d’une notification de Bitwarden invitant l’utilisateur à mettre à jour l'identifiant" + }, + "turnOnAutofill": { + "message": "Activer la saisie automatique" + }, + "turnedOnAutofill": { + "message": "Activer la saisie automatique" + }, + "dismiss": { + "message": "Rejeter" + }, "websiteItemLabel": { "message": "Site web $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Modifications apportées aux domaines bloqués enregistrées" + }, "excludedDomainsSavedSuccess": { "message": "Changements de domaines exclus enregistrés" }, @@ -2574,7 +2807,7 @@ "message": "Une erreur s'est produite lors de l'enregistrement de vos dates de suppression et d'expiration." }, "hideYourEmail": { - "message": "Hide your email address from viewers." + "message": "Masquer votre adresse courriel aux regards indiscrets." }, "passwordPrompt": { "message": "Ressaisir le mot de passe principal" @@ -2650,7 +2883,7 @@ "message": "Minutes" }, "vaultTimeoutPolicyAffectingOptions": { - "message": "Enterprise policy requirements have been applied to your timeout options" + "message": "Les exigences de la politique de sécurité d'Entreprise ont été appliquées à vos options de délai d'expiration" }, "vaultTimeoutPolicyInEffect": { "message": "Les politiques de sécurité de votre organisation ont défini le délai d'expiration de votre coffre à $HOURS$ heure(s) et $MINUTES$ minute(s) maximum.", @@ -2666,7 +2899,7 @@ } }, "vaultTimeoutPolicyInEffect1": { - "message": "$HOURS$ hour(s) and $MINUTES$ minute(s) maximum.", + "message": "$HOURS$ heure(s) et $MINUTES$ minute(s) maximum.", "placeholders": { "hours": { "content": "$1", @@ -2679,7 +2912,7 @@ } }, "vaultTimeoutPolicyMaximumError": { - "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "message": "Le délai d'expiration dépasse la restriction définie par votre organisation : $HOURS$ heure(s) et $MINUTES$ minute(s) maximum", "placeholders": { "hours": { "content": "$1", @@ -2789,6 +3022,20 @@ "error": { "message": "Erreur" }, + "decryptionError": { + "message": "Erreur de déchiffrement" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden n’a pas pu déchiffrer le(s) élément(s) du coffre listé(s) ci-dessous." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contacter le service clientèle", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "afin d'éviter toute perte de données supplémentaire.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Générer un nom d'utilisateur" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ a refusé votre demande. Veuillez contacter votre fournisseur de services pour obtenir de l'aide.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ a refusé votre demande : $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Impossible d'obtenir l'ID du compte de courriel masqué $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Les paramètres ont été modifiés" - }, - "environmentEditedClick": { - "message": "Cliquer ici" - }, - "environmentEditedReset": { - "message": "pour réinitialiser aux paramètres par défaut" - }, "serverVersion": { "message": "Version du serveur" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Se connecter avec le mot de passe principal" }, - "loggingInAs": { - "message": "Connexion en tant que" - }, - "notYou": { - "message": "Ce n'est pas vous ?" - }, "newAroundHere": { "message": "Nouveau par ici ?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Se connecter avec l'appareil" }, - "loginWithDeviceEnabledInfo": { - "message": "La connexion avec l'appareil doit être configurée dans les paramètres de l'application Bitwarden. Besoin d'une autre option ?" - }, "fingerprintPhraseHeader": { "message": "Phrase d'empreinte" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "Afficher toutes les options de connexion" }, - "viewAllLoginOptionsV1": { - "message": "Afficher toutes les options de connexion" - }, "notificationSentDevice": { "message": "Une notification a été envoyée à votre appareil." }, + "notificationSentDevicePart1": { + "message": "Déverrouillez Bitwarden sur votre appareil ou sur le" + }, + "notificationSentDeviceAnchor": { + "message": "application web" + }, + "notificationSentDevicePart2": { + "message": "Assurez-vous que la phrase d'empreinte correspond à celle ci-dessous avant de l'approuver." + }, "aNotificationWasSentToYourDevice": { "message": "Une notification a été envoyée à votre appareil" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Assurez-vous que votre compte est déverrouillé et que la phrase d'empreinte correspond à l'autre appareil" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "Vous serez notifié une fois que la demande sera approuvée" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Connexion initiée" }, + "logInRequestSent": { + "message": "Demande envoyée" + }, "exposedMasterPassword": { "message": "Mot de passe principal exposé" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Demander l'approbation de l'administrateur" }, - "approveWithMasterPassword": { - "message": "Approuver avec le mot de passe principal" - }, "ssoIdentifierRequired": { "message": "Identifiant SSO de l'organisation requis." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Demande transmise à votre administrateur." }, - "youWillBeNotifiedOnceApproved": { - "message": "Vous serez notifié une fois approuvé." - }, "troubleLoggingIn": { "message": "Problème pour vous connecter ?" }, @@ -3396,38 +3649,6 @@ "message": "Déplier/Replier", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Importer vos données dans Bitwarden ?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protéger vos données LastPass et importer dans Bitwarden ?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Enregistrer en tant que fichier non chiffré", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Importer vers Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importation en cours...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Données importées avec succès !", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Erreur lors de l'importation. Consultez la console pour plus de détails.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Une erreur réseau s'est produite lors de l'importation.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Domaine de l'alias" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Compte actif" }, + "bitwardenAccount": { + "message": "Compte Bitwarden" + }, "availableAccounts": { "message": "Comptes disponibles" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Suggestions de saisie automatique" }, + "itemSuggestions": { + "message": "Éléments suggérés" + }, "autofillSuggestionsTip": { "message": "Enregistrez un élément de connexion à remplir automatiquement pour ce site" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copier $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "Aucune valeur à copier" }, @@ -4107,7 +4348,7 @@ } }, "new": { - "message": "Nouveau" + "message": "Créer" }, "removeItem": { "message": "Retirer $NAME$", @@ -4128,15 +4369,6 @@ "itemName": { "message": "Nom de l’élément" }, - "cannotRemoveViewOnlyCollections": { - "message": "Vous ne pouvez pas supprimer des collections avec les autorisations d'affichage uniquement : $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "L'organisation est désactivée" }, @@ -4286,7 +4518,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "Afficher la détection de correspondance $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -4295,7 +4527,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "Masquer la détection de correspondance $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -4316,7 +4548,7 @@ "message": "Détails de la carte de paiement" }, "cardBrandDetails": { - "message": "$BRAND$ details", + "message": "Détails de la carte $BRAND$", "placeholders": { "brand": { "content": "$1", @@ -4438,8 +4670,11 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Réorganiser les URI des sites web. Utiliser la touche fléchée pour déplacer l'élément vers le haut ou vers le bas." + }, "reorderFieldUp": { - "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "message": "$LABEL$ déplacé vers le haut, position $INDEX$ de $LENGTH$", "placeholders": { "label": { "content": "$1", @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "Vous n'avez rien sélectionné." }, - "movedItemsToOrg": { - "message": "Les éléments sélectionnés ont été déplacés vers $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Éléments déplacés vers $ORGNAME$", "placeholders": { @@ -4526,7 +4752,7 @@ } }, "reorderFieldDown": { - "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "message": "$LABEL$ déplacé vers le bas, position $INDEX$ de $LENGTH$", "placeholders": { "label": { "content": "$1", @@ -4549,19 +4775,13 @@ "message": "Send en fichier" }, "fileSends": { - "message": "File Sends" + "message": "Envoi de fichiers" }, "textSend": { "message": "Send en texte" }, "textSends": { - "message": "Text Sends" - }, - "bitwardenNewLook": { - "message": "Bitwarden a un nouveau look !" - }, - "bitwardenNewLookDesc": { - "message": "Il est plus facile et plus intuitif que jamais de remplir automatiquement les champs et d'effectuer des recherches à partir de l'onglet \"Coffre\". Jetez un coup d'œil !" + "message": "Envoi de textes" }, "accountActions": { "message": "Actions du compte" @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "Vous n'êtes pas autorisé à modifier cet élément" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Le déverrouillage par biométrie n’est pas disponible parce qu'il faut au préalable déverrouiller avec le code PIN ou le mot de passe." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Le déverrouillage par biométrie n'est pas disponible actuellement." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Le déverrouillage par biométrie n'est pas disponible en raison de fichiers système mal configurés." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Le déverrouillage par biométrie n'est pas disponible en raison de fichiers système mal configurés." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Le déverrouillage par biométrie n'est pas disponible car l'application de bureau Bitwarden est fermée." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Le déverrouillage par biométrie n'est pas disponible car elle n'est pas activée pour $EMAIL$ dans l'application de bureau Bitwarden.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Le déverrouillage par biométrie n'est pas disponible actuellement pour une raison inconnue." + }, "authenticating": { "message": "Authentification" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Très large" + }, + "sshKeyWrongPassword": { + "message": "Le mot de passe saisi est incorrect." + }, + "importSshKey": { + "message": "Importer" + }, + "confirmSshKeyPassword": { + "message": "Confirmer le mot de passe" + }, + "enterSshKeyPasswordDesc": { + "message": "Saisir le mot de passe de la clé SSH." + }, + "enterSshKeyPassword": { + "message": "Saisir le mot de passe" + }, + "invalidSshKey": { + "message": "La clé SSH n'est pas valide" + }, + "sshKeyTypeUnsupported": { + "message": "Le type de clé SSH n'est pas pris en charge" + }, + "importSshKeyFromClipboard": { + "message": "Importer une clé à partir du presse-papiers" + }, + "sshKeyImported": { + "message": "Clé SSH importée avec succès" + }, + "cannotRemoveViewOnlyCollections": { + "message": "Vous ne pouvez pas supprimer des collections avec les autorisations d'affichage uniquement : $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Veuillez mettre à jour votre application de bureau" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "Pour utiliser le déverrouillage par biométrie, veuillez mettre à jour votre application de bureau ou désactiver le déverrouillage par empreinte digitale dans les paramètres de l'application de bureau." + }, + "changeAtRiskPassword": { + "message": "Changer le mot de passe à risque" } } diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index b3eaae64486..6c0b9cce87b 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Pista do contrasinal mestre (opcional)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Unirse a esta organización" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Copiar notas" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { "message": "Encher", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Introduce o teu correo electrónico e enviarémosche a túa pista do contrasinal mestre" }, - "passwordHint": { - "message": "Pista do contrasinal" - }, - "enterEmailToGetHint": { - "message": "Introduce a dirección de correo da túa conta para recibir a pista do contrasinal mestre." - }, "getMasterPasswordHint": { "message": "Obter pista do contrasinal mestre" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Editar cartafol" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "Novo cartafol" }, @@ -382,7 +398,7 @@ "message": "Aniñar un cartafol engadindo o nome do cartafol pai seguido dun \"/\". Exemplo: Social/Foros" }, "noFoldersAdded": { - "message": "Sen cartafois" + "message": "Sen cartafoles" }, "createFoldersToOrganize": { "message": "Crea cartafoles para organizar as entradas da túa caixa forte" @@ -394,10 +410,10 @@ "message": "Eliminar cartafol" }, "folders": { - "message": "Cartafois" + "message": "Cartafoles" }, "noFolders": { - "message": "Non hai cartafois que listar." + "message": "Non hai cartafoles que listar." }, "helpFeedback": { "message": "Axuda e comentarios" @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Xerar frase de contrasinal" }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "regeneratePassword": { "message": "Volver xerar contrasinal" }, @@ -454,22 +482,6 @@ "length": { "message": "Lonxitude" }, - "uppercase": { - "message": "Maiúsculas (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Minúsculas (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Números (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Caracteres especiais (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Incluír", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Incluír caracteres especiais", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Número de palabras" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "O navegador non permite copia doada ó portapapeis. Debes copialo manualmente." }, - "verifyIdentity": { - "message": "Verificar identidade" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "A túa caixa forte está bloqueada. Verifica a túa identidade para continuar." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Iniciar sesión en Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Reiniciar rexistro" }, @@ -875,6 +904,9 @@ "no": { "message": "Non" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "Produciuse un erro inesperado." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Ofrecer gardar un elemento se non se atopa na caixa forte. Aplica a tódalas sesións iniciadas." }, - "showCardsInVaultView": { - "message": "Na caixa forte, amosar tarxetas como suxestións de Autoenchido" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "Amosar tarxetas na pestana" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Lista na pestana actual as tarxetas gardadas para autoenchido." }, - "showIdentitiesInVaultView": { - "message": "Na caixa forte, amosar identidades como suxestións de Autoenchido" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "Amosar identidades na pestana" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Clicar nas entradas da caixa forte para autoencher" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "Baleirar portapapeis", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Gardar" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Ofrecer actualizar as credenciais xa gardadas" }, @@ -1084,10 +1169,6 @@ "message": "Claro", "description": "Light color" }, - "solarizedDark": { - "message": "Solarizado escuro", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Exportar dende" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Adquirir Prémium" }, - "premiumPurchaseAlert": { - "message": "Podes adquirir o plan Prémium na aplicación web de bitwarden.com. Queres visitala agora mesmo?" - }, "premiumPurchaseAlertV2": { "message": "Podes adquirir o plan Prémium dende os axustes de conta da aplicación web de Bitwarden." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Lémbrame" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Enviar un novo correo de verificación" }, "useAnotherTwoStepMethod": { "message": "Empregar outro método de verificación en 2 pasos" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Conecta a túa YubiKey no porto USB, despois preme o seu botón." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Abrir nova pestana" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "Autenticar con WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "Inicio de sesión non dispoñible" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Opcións de verificación en dous pasos" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Perdiche o acceso ós provedores de verificación en dous pasos (2FA)? Emprega o teu código de recuperación para desactivalos." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Entorno de aloxamento propio" }, - "selfHostedEnvironmentFooter": { - "message": "Especifica a URL base do teu servidor Bitwarden de aloxamento propio." - }, "selfHostedBaseUrlHint": { "message": "Especifica a URL base do teu servidor Bitwarden de aloxamento propio. Ex.: https://bitwarden.compañia.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Entorno personalizado" }, - "customEnvironmentFooter": { - "message": "Para usuarios avanzados. Podes especificar o URL base de cada servizo de xeito independente." - }, "baseUrl": { "message": "URL do servidor" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Arrastra para ordenar" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Texto" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Xerador de nomes de usuario" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "Usar este contrasinal" }, @@ -2063,12 +2163,24 @@ "message": "para crear un contrasinal forte", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "Acción do temporizador da caixa forte" }, "vaultTimeoutAction1": { "message": "Acción do temporizador" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "Bloquear", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Dominios", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Dominios bloqueados" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "Dominios excluídos" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden non ofrecerá gardar contas para estes dominios en ningunha das sesións iniciadas. Recarga a páxina para que os cambios fornezan efecto." }, + "blockedDomainsDesc": { + "message": "O autoenchido e outras funcións relacionadas non estarán dispoñibles para estas webs. Debes recargar a páxina para que os cambios teñan efecto." + }, + "autofillBlockedNoticeV2": { + "message": "O autoenchido está bloqueado para esta web." + }, + "autofillBlockedNoticeGuidance": { + "message": "Cambia isto en axustes" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "Web $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Dominios bloqueados gardados" + }, "excludedDomainsSavedSuccess": { "message": "Dominios excluídos gardados" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Erro" }, + "decryptionError": { + "message": "Erro de descifrado" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden non puido descifrar os seguintes elementos." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contacto co cliente exitoso", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "para evitar a perda de datos.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Xerar nome de usuario" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Non foi posible obter o ID de correo enmascarado de $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Os axustes foron modificados" - }, - "environmentEditedClick": { - "message": "Preme aquí" - }, - "environmentEditedReset": { - "message": "para volver á configuración por defecto" - }, "serverVersion": { "message": "Versión do Servidor" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Iniciar sesión co contrasinal mestre" }, - "loggingInAs": { - "message": "Iniciando sesión como" - }, - "notYou": { - "message": "Non es ti?" - }, "newAroundHere": { "message": "Novo por aquí?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Iniciar sesión cun dispositivo" }, - "loginWithDeviceEnabledInfo": { - "message": "O inicio de sesión con dispositivos debe estar activado nos axustes da app de Bitwarden. Precisas doutro método?" - }, "fingerprintPhraseHeader": { "message": "Frase de pegada dixital" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "Ver todas as opcións de inicio de sesión" }, - "viewAllLoginOptionsV1": { - "message": "Ver todas as opcións de inicio de sesión" - }, "notificationSentDevice": { "message": "Enviouse unha notificación ó teu dispositivo." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "Enviouse unha notificación ó teu dispositivo" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Por favor asegúrate de que a sesión está aberta e a frase de pegada dixital coincide ca do outro dispositivo" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "Serás notificado unha vez se aprobe a solicitude" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Inicio de sesión comezado" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Contrasinal mestre filtrado" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Solicitar aprobación do administrador" }, - "approveWithMasterPassword": { - "message": "Aprobar con contrasinal mestre" - }, "ssoIdentifierRequired": { "message": "Identificador SSO da organización requirido." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "A solicitude foi enviada ó teu administrador." }, - "youWillBeNotifiedOnceApproved": { - "message": "Serás notificado cando se aprobe." - }, "troubleLoggingIn": { "message": "Problemas ao iniciar sesión?" }, @@ -3396,38 +3649,6 @@ "message": "Colapsar/Expandir", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Importar os teus datos a Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protexer os teus datos de LastPass e importar a Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Gardar como arquivo sen cifrar", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Importar a Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importando...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Datos importados con éxito!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Erro importando. Comproba a consola para máis detalle.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Aconteceu un erro de rede durante a importación.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias do dominio" }, @@ -3795,7 +4016,7 @@ "message": "Autenticación multifactor fallida" }, "includeSharedFolders": { - "message": "Incluír cartafois compartidos" + "message": "Incluír cartafoles compartidos" }, "lastPassEmail": { "message": "Correo de LastPass" @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Activar conta" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Contas dispoñibles" }, @@ -3979,7 +4203,10 @@ "message": "Clave de acceso eliminada" }, "autofillSuggestions": { - "message": "Suxestións de autoenchido" + "message": "Autofill suggestions" + }, + "itemSuggestions": { + "message": "Entradas suxeridas" }, "autofillSuggestionsTip": { "message": "Gardar unha credencial como suxestión para este sitio" @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "Non hai valores que copiar" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Nome da entrada" }, - "cannotRemoveViewOnlyCollections": { - "message": "Non podes eliminar coleccións con permisos de Só lectura: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "A organización está desactivada" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "Subiuse $LABEL$ á posición $INDEX$ de $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "Non tes nada seleccionado." }, - "movedItemsToOrg": { - "message": "Entradas seleccionadas transferidas a $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Entradas transferidas a $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Textos Send" }, - "bitwardenNewLook": { - "message": "Bitwarden ten un novo look!" - }, - "bitwardenNewLookDesc": { - "message": "É máis fácil e intuitivo que nunca autoencher e buscar dende a caixa forte. Bota un ollo!" - }, "accountActions": { "message": "Accións da conta" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "Non tes permiso para modificar esta entrada" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "O desbloqueo biométrico non está dispoñible porque se require o PIN ou contrasinal primeiro." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "O desbloqueo biométrico non está dispoñible." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "O desbloqueo biométrico non está dispoñible por arquivos do sistema desconfigurados." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "O desbloqueo biométrico non está dispoñible por arquivos do sistema desconfigurados." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "O desbloqueo biométrico non está dispoñible porque a aplicación de escritorio está pechada." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "O desbloqueo biométrico non está dispoñible porque non está activada para $EMAIL$ na aplicación de escritorio.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "O desbloqueo biométrico non está dispoñible por algunha razón non prevista." + }, "authenticating": { "message": "Autenticando" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Moi ancho" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "Non podes eliminar coleccións con permisos de Só lectura: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index 5101c11a653..035bf9da48e 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -3,42 +3,42 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden Password Manager", + "message": "מנהל הסיסמאות Bitwarden", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "message": "בבית, בעבודה, או בדרך, Bitwarden מאבטח בקלות את כל הסיסמאות, מפתחות הגישה, והמידע הרגיש שלך", "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "צור חשבון חדש או התחבר כדי לגשת לכספת המאובטחת שלך." }, "inviteAccepted": { - "message": "Invitation accepted" + "message": "ההזמנה התקבלה" }, "createAccount": { "message": "צור חשבון" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "חדש ב־Bitwarden?" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "כניסה עם מפתח גישה" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "השתמש בכניסה יחידה" }, "welcomeBack": { - "message": "Welcome back" + "message": "ברוך שובך" }, "setAStrongPassword": { - "message": "Set a strong password" + "message": "הגדר סיסמה חזקה" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "Finish creating your account by setting a password" + "message": "סיים ליצור את החשבון שלך על ידי הגדרת סיסמה" }, "enterpriseSingleSignOn": { - "message": "כניסה ארגונית אחידה" + "message": "כניסה יחידה ארגונית" }, "cancel": { "message": "בטל" @@ -62,7 +62,7 @@ "message": "ניתן להשתמש ברמז לסיסמה הראשית אם שכחת אותה." }, "masterPassHintText": { - "message": "If you forget your password, the password hint can be sent to your email. $CURRENT$/$MAXIMUM$ character maximum.", + "message": "אם תשכח את הסיסמה שלך, הרמז לסיסמה יכול להישלח לדוא\"ל שלך. $CURRENT$/$MAXIMUM$ תווים לכל היותר.", "placeholders": { "current": { "content": "$1", @@ -78,13 +78,22 @@ "message": "הקלד שוב סיסמה ראשית" }, "masterPassHint": { - "message": "רמז לסיסמה ראשית (אופציונאלי)" + "message": "רמז לסיסמה הראשית (אופציונלי)" + }, + "passwordStrengthScore": { + "message": "ציון חוזק סיסמה $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } }, "joinOrganization": { - "message": "Join organization" + "message": "הצטרף לארגון" }, "joinOrganizationName": { - "message": "הצטרפות אל $ORGANIZATIONNAME$", + "message": "הצטרף אל $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -93,7 +102,7 @@ } }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Finish joining this organization by setting a master password." + "message": "סיים להצטרף לארגון זה על ידי הגדרת סיסמה ראשית." }, "tab": { "message": "לשונית" @@ -114,16 +123,16 @@ "message": "הגדרות" }, "currentTab": { - "message": "לשונית נוכחית" + "message": "כרטיסיה נוכחית" }, "copyPassword": { "message": "העתק סיסמה" }, "copyPassphrase": { - "message": "Copy passphrase" + "message": "העתק ביטוי סיסמה" }, "copyNote": { - "message": "העתק פתק" + "message": "העתק הערה" }, "copyUri": { "message": "העתק שורת כתובת" @@ -138,31 +147,31 @@ "message": "העתק קוד אבטחה" }, "copyName": { - "message": "Copy name" + "message": "העתק שם" }, "copyCompany": { - "message": "Copy company" + "message": "העתק חברה" }, "copySSN": { - "message": "Copy Social Security number" + "message": "העתק מספר תעודת זהות" }, "copyPassportNumber": { - "message": "Copy passport number" + "message": "העתק מספר דרכון" }, "copyLicenseNumber": { - "message": "Copy license number" + "message": "העתק מספר רישיון" }, "copyPrivateKey": { - "message": "Copy private key" + "message": "העתק מפתח פרטי" }, "copyPublicKey": { - "message": "Copy public key" + "message": "העתק מפתח ציבורי" }, "copyFingerprint": { - "message": "Copy fingerprint" + "message": "העתק טביעת אצבע" }, "copyCustomField": { - "message": "Copy $FIELD$", + "message": "העתק $FIELD$", "placeholders": { "field": { "content": "$1", @@ -171,42 +180,46 @@ } }, "copyWebsite": { - "message": "Copy website" + "message": "העתק אתר אינטרנט" }, "copyNotes": { - "message": "Copy notes" + "message": "העתק הערות" + }, + "copy": { + "message": "העתק", + "description": "Copy to clipboard" }, "fill": { - "message": "Fill", + "message": "מילוי", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." }, "autoFill": { - "message": "השלמה אוטומטית" + "message": "מילוי אוטומטי" }, "autoFillLogin": { - "message": "מילוי פרטי כניסה אוטומטית" + "message": "מילוי כניסה אוטומטי" }, "autoFillCard": { - "message": "מילוי פרטי כרטיס אוטומטית" + "message": "מילוי כרטיס אוטומטי" }, "autoFillIdentity": { - "message": "מילוי פרטי זיהוי אוטומטית" + "message": "מילוי זהות אוטומטי" }, "fillVerificationCode": { - "message": "Fill verification code" + "message": "מילוי קוד אימות" }, "fillVerificationCodeAria": { - "message": "Fill Verification Code", + "message": "מילוי קוד אימות", "description": "Aria label for the heading displayed the inline menu for totp code autofill" }, "generatePasswordCopied": { - "message": "צור סיסמה (העתק)" + "message": "צור סיסמה (והעתק)" }, "copyElementIdentifier": { - "message": "העתקת שם שדה מותאם אישית" + "message": "העתק שם שדה מותאם אישית" }, "noMatchingLogins": { - "message": "לא נמצאו פרטי כניסה תואמים." + "message": "אין כניסות תואמות" }, "noCards": { "message": "אין כרטיסים" @@ -215,46 +228,40 @@ "message": "אין זהויות" }, "addLoginMenu": { - "message": "הוספת פרטי כניסה" + "message": "הוסף כניסה" }, "addCardMenu": { - "message": "הוספת כרטיס" + "message": "הוסף כרטיס" }, "addIdentityMenu": { - "message": "הוספת זהות" + "message": "הוסף זהות" }, "unlockVaultMenu": { - "message": "שחרור הכספת שלך" + "message": "פתח את הכספת שלך" }, "loginToVaultMenu": { "message": "כניסה לכספת שלך" }, "autoFillInfo": { - "message": "לא נמצאו פרטי כניסה להשלמה אוטומטית בלשונית הנוכחית בדפדפן." + "message": "לא נמצאו כניסות למילוי אוטומטי בכרטיסיית הדפדפן הנוכחית." }, "addLogin": { - "message": "הוסף פרטי כניסה" + "message": "הוסף כניסה" }, "addItem": { "message": "הוסף פריט" }, "accountEmail": { - "message": "Account email" + "message": "דוא\"ל חשבון" }, "requestHint": { - "message": "Request hint" + "message": "בקש רמז" }, "requestPasswordHint": { - "message": "Request password hint" + "message": "בקש רמז לסיסמה" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { - "message": "Enter your account email address and your password hint will be sent to you" - }, - "passwordHint": { - "message": "רמז לסיסמה" - }, - "enterEmailToGetHint": { - "message": "הכנס את פרטי האימייל שלך לקבלת רמז עבור הסיסמה הראשית." + "message": "הזן את כתובת דוא\"ל החשבון שלך והרמז לסיסמה שלך יישלח אליך" }, "getMasterPasswordHint": { "message": "הצג את הרמז לסיסמה הראשית" @@ -263,13 +270,13 @@ "message": "המשך" }, "sendVerificationCode": { - "message": "שליחת קוד אימות לדוא״ל שלך" + "message": "שלח קוד אימות לדוא\"ל שלך" }, "sendCode": { - "message": "שליחת קוד" + "message": "שלח קוד" }, "codeSent": { - "message": "קוד נשלח" + "message": "הקוד נשלח" }, "verificationCode": { "message": "קוד אימות" @@ -281,28 +288,28 @@ "message": "החלף סיסמה ראשית" }, "continueToWebApp": { - "message": "Continue to web app?" + "message": "להמשיך אל יישום רשת?" }, "continueToWebAppDesc": { - "message": "Explore more features of your Bitwarden account on the web app." + "message": "גלה עוד תכונות של חשבון ה־Bitwarden שלך ביישום הרשת." }, "continueToHelpCenter": { - "message": "Continue to Help Center?" + "message": "להמשיך אל מרכז העזרה?" }, "continueToHelpCenterDesc": { - "message": "Learn more about how to use Bitwarden on the Help Center." + "message": "למד עוד אודות אופן השימוש ב־Bitwarden במרכז העזרה." }, "continueToBrowserExtensionStore": { - "message": "Continue to browser extension store?" + "message": "להמשיך אל חנות הרחבות דפדפן?" }, "continueToBrowserExtensionStoreDesc": { - "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + "message": "עזור לאחרים לגלות אם Bitwarden מתאים להם. בקר בחנות הרחבות הדפדפן שלך והשאר דירוג עכשיו." }, "changeMasterPasswordOnWebConfirmation": { - "message": "You can change your master password on the Bitwarden web app." + "message": "אתה יכול לשנות את הסיסמה הראשית שלך ביישום הרשת של Bitwarden." }, "fingerprintPhrase": { - "message": "סיסמת טביעת אצבע", + "message": "ביטוי טביעת אצבע", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "yourAccountsFingerprint": { @@ -310,49 +317,49 @@ "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "twoStepLogin": { - "message": "התחברות בשני-שלבים" + "message": "כניסה דו־שלבית" }, "logOut": { - "message": "התנתק" + "message": "צא" }, "aboutBitwarden": { - "message": "About Bitwarden" + "message": "אודות Bitwarden" }, "about": { "message": "אודות" }, "moreFromBitwarden": { - "message": "More from Bitwarden" + "message": "עוד מאת Bitwarden" }, "continueToBitwardenDotCom": { - "message": "Continue to bitwarden.com?" + "message": "להמשיך אל bitwarden.com?" }, "bitwardenForBusiness": { - "message": "Bitwarden for Business" + "message": "Bitwarden לעסקים" }, "bitwardenAuthenticator": { - "message": "Bitwarden Authenticator" + "message": "מאמת Bitwarden" }, "continueToAuthenticatorPageDesc": { - "message": "Bitwarden Authenticator allows you to store authenticator keys and generate TOTP codes for 2-step verification flows. Learn more on the bitwarden.com website" + "message": "מאמת Bitwarden מאפשר לך לאחסן מפתחות מאמת וליצור קודי TOTP עבור זרימת אימות דו־שלבית. למד עוד באתר האינטרנט bitwarden.com" }, "bitwardenSecretsManager": { - "message": "Bitwarden Secrets Manager" + "message": "מנהל הסודות של Bitwarden" }, "continueToSecretsManagerPageDesc": { - "message": "Securely store, manage, and share developer secrets with Bitwarden Secrets Manager. Learn more on the bitwarden.com website." + "message": "אחסן, נהל, ושתף בבטחה סודות מפתח עם מנהל הסודות של Bitwarden. למד עוד באתר האינטרנט bitwarden.com." }, "passwordlessDotDev": { "message": "Passwordless.dev" }, "continueToPasswordlessDotDevPageDesc": { - "message": "Create smooth and secure login experiences free from traditional passwords with Passwordless.dev. Learn more on the bitwarden.com website." + "message": "צור חוויית כניסה חלקה ובטוחה חופשית מסיסמאות מסורתיות עם Passwordless.dev. למד עוד באתר האינטרנט bitwarden.com." }, "freeBitwardenFamilies": { - "message": "Free Bitwarden Families" + "message": "Bitwarden למשפחות בחינם" }, "freeBitwardenFamiliesPageDesc": { - "message": "You are eligible for Free Bitwarden Families. Redeem this offer today in the web app." + "message": "אתה זכאי ל־Bitwarden למשפחות בחינם. ממש הצעה זו היום ביישום הרשת." }, "version": { "message": "גירסה" @@ -361,7 +368,7 @@ "message": "שמור" }, "move": { - "message": "העברה" + "message": "העבר" }, "addFolder": { "message": "הוסף תיקייה" @@ -372,23 +379,32 @@ "editFolder": { "message": "ערוך תיקייה" }, + "editFolderWithName": { + "message": "ערוך תיקייה: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { - "message": "New folder" + "message": "תיקייה חדשה" }, "folderName": { - "message": "Folder name" + "message": "שם תיקייה" }, "folderHintText": { - "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + "message": "צור תיקייה מקוננת על ידי הוספת שם תיקיית האב ואחריו “/”. דוגמה: חברתי/פורומים" }, "noFoldersAdded": { - "message": "No folders added" + "message": "לא נוספו תיקיות" }, "createFoldersToOrganize": { - "message": "Create folders to organize your vault items" + "message": "צור תיקיות כדי לארגן את פריטי הכספת שלך" }, "deleteFolderPermanently": { - "message": "Are you sure you want to permanently delete this folder?" + "message": "האם אתה בטוח שברצונך למחוק תיקייה זו לצמיתות?" }, "deleteFolder": { "message": "מחק תיקייה" @@ -400,16 +416,16 @@ "message": "אין תיקיות להצגה." }, "helpFeedback": { - "message": "עזרה ומשוב" + "message": "עזרה & משוב" }, "helpCenter": { "message": "מרכז העזרה של Bitwarden" }, "communityForums": { - "message": "Explore Bitwarden community forums" + "message": "גלה את פורומי קהילת Bitwarden" }, "contactSupport": { - "message": "Contact Bitwarden support" + "message": "פנה לתמיכת Bitwarden" }, "sync": { "message": "סנכרן" @@ -421,7 +437,7 @@ "message": "סנכרון אחרון:" }, "passGen": { - "message": "יוצר הסיסמאות" + "message": "מחולל הסיסמאות" }, "generator": { "message": "מייצר", @@ -431,10 +447,10 @@ "message": "צור אוטומטית סיסמאות חזקות ויחודיות עבור פרטי הכניסה שלך." }, "bitWebVaultApp": { - "message": "Bitwarden web app" + "message": "יישום הרשת של Bitwarden" }, "importItems": { - "message": "יבא פריטים" + "message": "ייבא פריטים" }, "select": { "message": "בחר" @@ -443,10 +459,22 @@ "message": "צור סיסמה" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "צור ביטוי סיסמה" + }, + "passwordGenerated": { + "message": "נוצרה סיסמה" + }, + "passphraseGenerated": { + "message": "נוצר ביטוי סיסמה" + }, + "usernameGenerated": { + "message": "נוצר שם משתמש" + }, + "emailGenerated": { + "message": "נוצר דוא\"ל" }, "regeneratePassword": { - "message": "צור סיסמה חדשה" + "message": "צור סיסמה מחדש" }, "options": { "message": "אפשרויות" @@ -454,28 +482,12 @@ "length": { "message": "אורך" }, - "uppercase": { - "message": "Uppercase (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Lowercase (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Numbers (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Special characters (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { - "message": "Include", + "message": "כלול", "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "כלול תווי אות גדולה", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -483,7 +495,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "כלול תווי אות קטנה", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -491,7 +503,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "כלול מספרים", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -499,15 +511,11 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "כלול תווים מיוחדים", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { - "message": "מספר מילים" + "message": "מספר המילים" }, "wordSeparator": { "message": "מפריד מילים" @@ -517,20 +525,20 @@ "description": "Make the first letter of a work uppercase." }, "includeNumber": { - "message": "כלול מספרים" + "message": "כלול מספר" }, "minNumbers": { - "message": "מינימום ספרות" + "message": "מינימום מספרים" }, "minSpecial": { - "message": "מינימום תוים מיוחדים" + "message": "מינימום מיוחדים" }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "הימנע מתווים דו־משמעיים", "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { - "message": "Enterprise policy requirements have been applied to your generator options.", + "message": "דרישות מדיניות ארגונית הוחלו על אפשרויות המחולל שלך.", "description": "Indicates that a policy limits the credential generator screen." }, "searchVault": { @@ -555,7 +563,7 @@ "message": "סיסמה" }, "totp": { - "message": "Authenticator secret" + "message": "סוד מאמת" }, "passphrase": { "message": "משפט סיסמה" @@ -564,7 +572,7 @@ "message": "מועדף" }, "unfavorite": { - "message": "Unfavorite" + "message": "הסר ממועדפים" }, "itemAddedToFavorites": { "message": "פריט נוסף למועדפים" @@ -576,7 +584,7 @@ "message": "הערות" }, "privateNote": { - "message": "Private note" + "message": "הערה פרטית" }, "note": { "message": "הערה" @@ -597,10 +605,10 @@ "message": "הפעל" }, "launchWebsite": { - "message": "Launch website" + "message": "פתח אתר" }, "launchWebsiteName": { - "message": "Launch website $ITEMNAME$", + "message": "פתח אתר $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -612,7 +620,7 @@ "message": "אתר" }, "toggleVisibility": { - "message": "הצג או הסתר" + "message": "שנה מצב נראות" }, "manage": { "message": "נהל" @@ -621,43 +629,49 @@ "message": "אחר" }, "unlockMethods": { - "message": "Unlock options" + "message": "אפשרויות ביטול נעילה" }, "unlockMethodNeededToChangeTimeoutActionDesc": { - "message": "Set up an unlock method to change your vault timeout action." + "message": "הגדר שיטת ביטול נעילה כדי לשנות את פעולת פסק הזמן לכספת שלך." }, "unlockMethodNeeded": { - "message": "Set up an unlock method in Settings" + "message": "הגדר שיטת ביטול נעילה בהגדרות" }, "sessionTimeoutHeader": { - "message": "Session timeout" + "message": "פסק זמן להפעלה" }, "vaultTimeoutHeader": { - "message": "Vault timeout" + "message": "פסק זמן לכספת" }, "otherOptions": { - "message": "Other options" + "message": "אפשרויות אחרות" }, "rateExtension": { - "message": "דירוג הרחבה" + "message": "דרג את ההרחבה" }, "browserNotSupportClipboard": { "message": "הדפדפן שלך לא תומך בהעתקה ללוח. אנא העתק בצורה ידנית." }, - "verifyIdentity": { - "message": "אימות זהות" + "verifyYourIdentity": { + "message": "אמת את זהותך" + }, + "weDontRecognizeThisDevice": { + "message": "אנחנו לא מזהים את המכשיר הזה. הזן את הקוד שנשלח לדוא\"ל שלך כדי לאמת את זהותך." + }, + "continueLoggingIn": { + "message": "המשך להיכנס" }, "yourVaultIsLocked": { - "message": "הכספת שלך נעולה. הזן את הסיסמה הראשית שלך כדי להמשיך." + "message": "הכספת שלך נעולה. אמת את זהותך כדי להמשיך." }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "הכספת שלך נעולה" }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "החשבון שלך נעול" }, "or": { - "message": "or" + "message": "או" }, "unlock": { "message": "בטל נעילה" @@ -679,16 +693,16 @@ "message": "סיסמה ראשית שגויה" }, "vaultTimeout": { - "message": "משך זמן מירבי עבור חיבור לכספת" + "message": "פסק זמן לכספת" }, "vaultTimeout1": { - "message": "Timeout" + "message": "פסק זמן" }, "lockNow": { "message": "נעל עכשיו" }, "lockAll": { - "message": "Lock all" + "message": "נעל הכל" }, "immediately": { "message": "באופן מיידי" @@ -724,7 +738,7 @@ "message": "4 שעות" }, "onLocked": { - "message": "בזמן נעילת המערכת" + "message": "בנעילת המערכת" }, "onRestart": { "message": "בהפעלת הדפדפן מחדש" @@ -736,16 +750,16 @@ "message": "אבטחה" }, "confirmMasterPassword": { - "message": "Confirm master password" + "message": "אמת סיסמה ראשית" }, "masterPassword": { - "message": "Master password" + "message": "סיסמה ראשית" }, "masterPassImportant": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "הסיסמה הראשית שלך לא ניתנת לשחזור אם אתה שוכח אותה!" }, "masterPassHintLabel": { - "message": "Master password hint" + "message": "רמז לסיסמה הראשית" }, "errorOccurred": { "message": "אירעה שגיאה" @@ -757,13 +771,13 @@ "message": "כתובת אימייל לא תקינה." }, "masterPasswordRequired": { - "message": "Master password is required." + "message": "נדרשת סיסמה ראשית." }, "confirmMasterPasswordRequired": { - "message": "Master password retype is required." + "message": "נדרשת הזנה מחדש של הסיסמה הראשית." }, "masterPasswordMinlength": { - "message": "Master password must be at least $VALUE$ characters long.", + "message": "סיסמה ראשית חייבת להיות לפחות באורך $VALUE$ תווים.", "description": "The Master Password must be at least a specific number of characters long.", "placeholders": { "value": { @@ -779,16 +793,16 @@ "message": "החשבון שלך נוצר בהצלחה! כעת ניתן להכנס למערכת." }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "החשבון החדש שלך נוצר!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "אתה נכנסת!" }, "youSuccessfullyLoggedIn": { - "message": "You successfully logged in" + "message": "נכנסת בהצלחה" }, "youMayCloseThisWindow": { - "message": "You may close this window" + "message": "אתה רשאי לסגור חלון זה" }, "masterPassSent": { "message": "שלחנו לך אימייל עם רמז לסיסמה הראשית." @@ -797,7 +811,7 @@ "message": "נדרש קוד אימות." }, "webauthnCancelOrTimeout": { - "message": "The authentication was cancelled or took too long. Please try again." + "message": "האימות בוטל או לקח זמן רב מדי. נא לנסות שוב." }, "invalidVerificationCode": { "message": "קוד אימות שגוי" @@ -813,58 +827,73 @@ } }, "autofillError": { - "message": "לא הצלחנו לבצע פעולת השלמה האוטומטית בעמוד זה. אנא העתק והדבק את המידע הנחוץ בצורה ידנית." + "message": "לא ניתן למלא אוטומטית את הפריט שנבחר בעמוד זה. העתק והדבק את המידע במקום זאת." }, "totpCaptureError": { - "message": "Unable to scan QR code from the current webpage" + "message": "אי אפשר לסרוק קוד QR מהעמוד הנוכחי" }, "totpCaptureSuccess": { - "message": "Authenticator key added" + "message": "מפתח המאמת נוסף" }, "totpCapture": { - "message": "Scan authenticator QR code from current webpage" + "message": "סרוק קוד QR מאמת מהעמוד הנוכחי" }, "totpHelperTitle": { - "message": "Make 2-step verification seamless" + "message": "הפוך את האימות הדו־שלבי לחלק" }, "totpHelper": { - "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + "message": "Bitwarden יכול לאחסון ולמלא קודים של אימות דו־שלבי. העתק והדבק את המפתח לשדה זה." }, "totpHelperWithCapture": { - "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + "message": "Bitwarden יכול לאחסון ולמלא קודים של אימות דו־שלבי. בחר את סמל המצלמה כדי לצלם את הקוד QR המאמת של אתר זה, או העתק והדבק את המפתח לתוך שדה זה." }, "learnMoreAboutAuthenticators": { - "message": "Learn more about authenticators" + "message": "למד עוד על מאמתים" }, "copyTOTP": { - "message": "Copy Authenticator key (TOTP)" + "message": "העתק קוד מאמת (TOTP)" }, "loggedOut": { "message": "בוצעה יציאה" }, "loggedOutDesc": { - "message": "You have been logged out of your account." + "message": "יצאת מהחשבון שלך." }, "loginExpired": { "message": "תוקף החיבור שלך הסתיים." }, "logIn": { - "message": "Log in" + "message": "היכנס" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "היכנס אל Bitwarden" + }, + "enterTheCodeSentToYourEmail": { + "message": "הזן את הקוד שנשלח לדוא\"ל שלך" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "הזן את הקוד מיישום המאמת שלך" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "לחץ על ה־YubiKey שלך כדי לאמת" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "נדרשת כניסה דו־שלבית של Duo עבור החשבון שלך. עקוב אחר השלבים למטה כדי לסיים להיכנס." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "עקוב אחר השלבים למטה כדי לסיים להיכנס." }, "restartRegistration": { - "message": "Restart registration" + "message": "התחל הרשמה מחדש" }, "expiredLink": { - "message": "Expired link" + "message": "קישור פג תוקף" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "נא להתחיל הרשמה מחדש או לנסות להיכנס." }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "ייתכן שכבר יש לך חשבון" }, "logOutConfirmation": { "message": "האם אתה בטוח שברצונך להתנתק?" @@ -875,6 +904,9 @@ "no": { "message": "לא" }, + "location": { + "message": "מיקום" + }, "unexpectedError": { "message": "אירעה שגיאה לא צפויה." }, @@ -882,28 +914,28 @@ "message": "דרוש שם." }, "addedFolder": { - "message": "נוספה תיקייה" + "message": "תיקייה נוספה" }, "twoStepLoginConfirmation": { - "message": "התחברות בשני-שלבים הופכת את החשבון שלך למאובטח יותר בכך שאתה נדרש לוודא בכל כניסה בעזרת מכשיר אחר כדוגמת מפתח אבטחה, תוכנת אימות, SMS, שיחת טלפון, או אימייל. ניתן להפעיל את \"התחברות בשני-שלבים\" בכספת שבאתר bitwarden.com. האם ברצונך לפתוח את האתר כעת?" + "message": "כניסה דו־שלבית הופכת את החשבון שלך למאובטח יותר בכך שאתה נדרש לאמת את הכניסה שלך עם מכשיר אחר כמו מפתח אבטחה, יישום מאמת, מסרון, שיחת טלפון, או דוא\"ל. ניתן להגדיר כניסה דו־שלבית בכספת הרשת bitwarden.com. האם ברצונך לבקר באתר כעת?" }, "twoStepLoginConfirmationContent": { - "message": "Make your account more secure by setting up two-step login in the Bitwarden web app." + "message": "הפוך את החשבון שלך למאובטח יותר על ידי הגדרת כניסה דו־שלבית ביישום הרשת של Bitwarden." }, "twoStepLoginConfirmationTitle": { - "message": "Continue to web app?" + "message": "להמשיך אל יישום הרשת?" }, "editedFolder": { - "message": "תיקייה שנערכה" + "message": "תיקייה נשמרה" }, "deleteFolderConfirmation": { "message": "האם אתה בטוח שברצונך למחוק את התיקייה?" }, "deletedFolder": { - "message": "תיקייה שנמחקה" + "message": "תיקייה נמחקה" }, "gettingStartedTutorial": { - "message": "מדריך שימוש ראשוני" + "message": "מדריך תחילת עבודה" }, "gettingStartedTutorialVideo": { "message": "צפה במדריך השימוש הראשוני כדי ללמוד איך לנצל את המקסימום שהתוסף לדפדפן יכול להציע." @@ -934,20 +966,20 @@ "message": "כתובת חדשה" }, "addDomain": { - "message": "Add domain", + "message": "הוסף דומיין", "description": "'Domain' here refers to an internet domain name (e.g. 'bitwarden.com') and the message in whole described the act of putting a domain value into the context." }, "addedItem": { - "message": "פריט שהתווסף" + "message": "הפריט נוסף" }, "editedItem": { - "message": "פריט שנערך" + "message": "הפריט נשמר" }, "deleteItemConfirmation": { "message": "האם אתה בטוח שברצונך למחוק פריט זה?" }, "deletedItem": { - "message": "פריט נשלח לסל המחזור" + "message": "פריט נשלח לאשפה" }, "overwritePassword": { "message": "דרוס סיסמה" @@ -956,10 +988,10 @@ "message": "האם אתה בטוח שברצונך לדרוס את הסיסמה הנוכחית?" }, "overwriteUsername": { - "message": "Overwrite username" + "message": "דרוס שם משתמש" }, "overwriteUsernameConfirmation": { - "message": "Are you sure you want to overwrite the current username?" + "message": "האם אתה בטוח שברצונך לדרוס את שם המשתמש הנוכחי?" }, "searchFolder": { "message": "חפש תיקייה" @@ -975,40 +1007,43 @@ "description": "This is the folder for uncategorized items" }, "enableAddLoginNotification": { - "message": "Ask to add login" + "message": "בקש לשמור כניסה" }, "vaultSaveOptionsTitle": { - "message": "Save to vault options" + "message": "שמור בהגדרות כספת" }, "addLoginNotificationDesc": { - "message": "ההודעה \"שמור פרטי כניסה\" מופיעה בכל פעם שתכנס לאתר חדש בפעם הראשונה." + "message": "בקש להוסיף פריט אם לא נמצא פריט בכספת שלך." }, "addLoginNotificationDescAlt": { - "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." + "message": "בקש להוסיף פריט אם לא נמצא פריט בכספת שלך. חל על כל החשבונות המחוברים." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "הצג תמיד כרטיסים כהצעות מילוי אוטומטי בתצוגת כספת" }, "showCardsCurrentTab": { - "message": "Show cards on Tab page" + "message": "הצג כרטיסים בעמוד הכרטיסיות" }, "showCardsCurrentTabDesc": { - "message": "List card items on the Tab page for easy autofill." + "message": "רשום פריטי כרטיס בעמוד הכרטיסיות עבור מילוי אוטומטי קל." }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "הצג תמיד זהויות כהצעות למילוי אוטומטי בתצוגת כספת" }, "showIdentitiesCurrentTab": { - "message": "Show identities on Tab page" + "message": "הצג זהויות בעמוד הכרטיסיות" }, "showIdentitiesCurrentTabDesc": { - "message": "List identity items on the Tab page for easy autofill." + "message": "הצג פריטי זהות בעמוד הכרטיסיות עבור מילוי אוטומטי קל." }, "clickToAutofillOnVault": { - "message": "Click items to autofill on Vault view" + "message": "לחץ על פריטים כדי למלא אוטומטית בתצוגת כספת" + }, + "clickToAutofill": { + "message": "לחץ על פריטים בהצעות למילוי אוטומטי כדי למלא" }, "clearClipboard": { - "message": "נקה לוח העתקות", + "message": "נקה לוח העתקה", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." }, "clearClipboardDesc": { @@ -1019,53 +1054,103 @@ "message": "האם ברצונך שתוכנת Bitwarden תזכור סיסמה זו עבורך?" }, "notificationAddSave": { - "message": "כן, שמור עכשיו" + "message": "שמור" + }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ נשמר אל Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ עודכן ב־Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "שמור ככניסה חדשה", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "עדכן כניסה", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "לשמור כניסה?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "לעדכן כניסה קיימת?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "כניסה נשמרה", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "כניסה עודכנה", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "שגיאה בשמירה", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "או לא! לא יכלנו לשמור זאת. נסה להזין את הפרטים באופן ידני.", + "description": "Detailed error message shown when saving login details fails." }, "enableChangedPasswordNotification": { - "message": "Ask to update existing login" + "message": "שאל אם לעדכן פרטי כניסה קיימת" }, "changedPasswordNotificationDesc": { - "message": "Ask to update a login's password when a change is detected on a website." + "message": "שאל אם לעדכן סיסמת כניסה כאשר זוהה שינוי באתר אינטרנט." }, "changedPasswordNotificationDescAlt": { - "message": "Ask to update a login's password when a change is detected on a website. Applies to all logged in accounts." + "message": "שאל אם לעדכן סיסמת כניסה כאשר זוהה שינוי באתר אינטרנט. חל על כל החשבונות המחוברים." }, "enableUsePasskeys": { - "message": "Ask to save and use passkeys" + "message": "שאל אם לשמור ולהשתמש במפתחות גישה" }, "usePasskeysDesc": { - "message": "Ask to save new passkeys or log in with passkeys stored in your vault. Applies to all logged in accounts." + "message": "שאל אם לשמור מפתחות גישה חדשים או להיכנס עם מפתחות גישה השמורים בכספת שלך. חל על כל המשתמשים המחוברים." }, "notificationChangeDesc": { "message": "האם ברצונך לעדכן את הסיסמה הזו בתוכנת Bitwarden?" }, "notificationChangeSave": { - "message": "כן, עדכן עכשיו" + "message": "עדכן" }, "notificationUnlockDesc": { - "message": "Unlock your Bitwarden vault to complete the autofill request." + "message": "בטל נעילת כספת ה־Bitwarden שלך כדי להשלים את בקשת המילוי האוטומטי." }, "notificationUnlock": { - "message": "Unlock" + "message": "בטל נעילה" }, "additionalOptions": { - "message": "Additional options" + "message": "אפשרויות נוספות" }, "enableContextMenuItem": { - "message": "Show context menu options" + "message": "הצג אפשרויות תפריט הקשר" }, "contextMenuItemDesc": { - "message": "Use a secondary click to access password generation and matching logins for the website." + "message": "השתמש בלחיצה משנית כדי לגשת לחילול סיסמה וכניסות תואמות עבור אתר האינטרנט." }, "contextMenuItemDescAlt": { - "message": "Use a secondary click to access password generation and matching logins for the website. Applies to all logged in accounts." + "message": "השתמש בלחיצה משנית כדי לגשת לחילול סיסמה וכניסות תואמות עבור אתר האינטרנט. חל על כל החשבונות המחוברים." }, "defaultUriMatchDetection": { - "message": "ברירת מחדל לזיהוי התאמת כתובות", + "message": "ברירת מחדל לזיהוי התאמת URI", "description": "Default URI match detection for autofill." }, "defaultUriMatchDetectionDesc": { - "message": "בחר את שיטת ברירת המחדל עבור זיהוי התאמת כתובות כשמבצעים פעולות השלמה אוטומטית." + "message": "בחר את דרך ברירת המחדל לטיפול בזיהוי התאמת URI עבור כניסות כשמבצעים פעולות כגון מילוי אוטומטי." }, "theme": { "message": "ערכת נושא" @@ -1074,7 +1159,7 @@ "message": "שנה את ערכת הצבע של האפליקציה." }, "themeDescAlt": { - "message": "Change the application's color theme. Applies to all logged in accounts." + "message": "שנה את צבעי ערכת הנושא של היישום. חל על כל החשבונות המחוברים." }, "dark": { "message": "כהה", @@ -1084,62 +1169,58 @@ "message": "בהיר", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized dark", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { - "message": "Export from" + "message": "ייצא מ־" }, "exportVault": { - "message": "יצוא כספת" + "message": "ייצא כספת" }, "fileFormat": { - "message": "פורמט קובץ" + "message": "פורמט הקובץ" }, "fileEncryptedExportWarningDesc": { - "message": "This file export will be password protected and require the file password to decrypt." + "message": "קובץ ייצוא זה יהיה מוגן סיסמה ודורש את סיסמת הקובץ כדי לפענח." }, "filePassword": { - "message": "File password" + "message": "סיסמת קובץ" }, "exportPasswordDescription": { - "message": "This password will be used to export and import this file" + "message": "סיסמה זו תשמש כדי לייצא ולייבא קובץ זה" }, "accountRestrictedOptionDescription": { - "message": "Use your account encryption key, derived from your account's username and Master Password, to encrypt the export and restrict import to only the current Bitwarden account." + "message": "השתמש במפתח הצפנת החשבון שלך, הנגזר משם המשתמש והסיסמה הראשית של חשבונך, כדי להצפין את הייצוא ולהגביל את הייבוא רק לחשבון Bitwarden הנוכחי." }, "passwordProtectedOptionDescription": { - "message": "Set a file password to encrypt the export and import it to any Bitwarden account using the password for decryption." + "message": "הגדר סיסמת קובץ כדי להצפין את הייצוא ולייבא אותו לכל חשבון Bitwarden באמצעות הסיסמה לפענוח." }, "exportTypeHeading": { - "message": "Export type" + "message": "סוג ייצוא" }, "accountRestricted": { - "message": "Account restricted" + "message": "מוגבל חשבון" }, "filePasswordAndConfirmFilePasswordDoNotMatch": { - "message": "“File password” and “Confirm file password“ do not match." + "message": "\"סיסמת קובץ\" ו\"אשר סיסמת קובץ\" אינם תואמים." }, "warning": { "message": "אזהרה", "description": "WARNING (should stay in capitalized letters if the language permits)" }, "warningCapitalized": { - "message": "Warning", + "message": "אזהרה", "description": "Warning (should maintain locale-relevant capitalization)" }, "confirmVaultExport": { - "message": "אישור ייצוא כספת" + "message": "אשר ייצוא כספת" }, "exportWarningDesc": { "message": "הקובץ מכיל את פרטי הכספת שלך בפורמט לא מוצפן. מומלץ להעביר את הקובץ רק בדרכים מוצפנות, ומאוד לא מומלץ לשמור או לשלוח את הקובץ הזה בדרכים לא מוצפנות (כדוגמת סתם אימייל). מחק את הקובץ מיד לאחר שסיימת את השימוש בו." }, "encExportKeyWarningDesc": { - "message": "ייצוא זה מצפין את המידע שלך באמצעות שימוש במפתח ההצפנה של חשבונך. אם אי-פעם תבצע החלפה (רוטציה) למפתח ההצפנה של חשבונך, עליך לבצע ייצוא זה שוב אחרת לא תוכל לפענח קובץ ייצוא זה." + "message": "ייצוא זה מצפין את הנתונים שלך באמצעות מפתח ההצפנה של חשבונך. אם אי פעם תבצע סיבוב למפתח ההצפנה של חשבונך, תצטרך לייצא שוב משום שלא תוכל לפענח קובץ ייצוא זה." }, "encExportAccountWarningDesc": { - "message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." + "message": "מפתחות הצפנת חשבון הם ייחודים לכל חשבון משתמש של Bitwarden, לכן אינך יכול לייבא ייצוא מוצפן אל תוך חשבון אחר." }, "exportMasterPassword": { "message": "הזן את הסיסמה הראשית שלך עבור יצוא המידע מהכספת." @@ -1148,13 +1229,13 @@ "message": "משותף" }, "bitwardenForBusinessPageDesc": { - "message": "Bitwarden for Business allows you to share your vault items with others by using an organization. Learn more on the bitwarden.com website." + "message": "Bitwarden לעסקים מאפשר לך לשתף את פריטי הכספת שלך עם אחרים על ידי שימוש בארגון. למד עוד באתר האינטרנט bitwarden.com." }, "moveToOrganization": { - "message": "Move to organization" + "message": "העבר לארגון" }, "movedItemToOrg": { - "message": "$ITEMNAME$ הועבר ל- $ORGNAME$", + "message": "$ITEMNAME$ הועבר אל $ORGNAME$", "placeholders": { "itemname": { "content": "$1", @@ -1167,13 +1248,13 @@ } }, "moveToOrgDesc": { - "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." + "message": "בחר ארגון שאליו ברצונך להעביר פריט זה. העברה אל ארגון מעבירה בעלות של הפריט אל אותו ארגון. לא תוכל להיות הבעלים הישיר של פריט זה ברגע שהוא הועבר." }, "learnMore": { "message": "למידע נוסף" }, "authenticatorKeyTotp": { - "message": "מפתח אימות (TOTP)" + "message": "מפתח מאמת (TOTP)" }, "verificationCodeTotp": { "message": "קוד אימות (TOTP)" @@ -1191,61 +1272,61 @@ "message": "האם אתה בטוח שברצונך למחוק קובץ מצורף זה?" }, "deletedAttachment": { - "message": "קובץ מצורף שנמחק" + "message": "הצרופה נמחקה" }, "newAttachment": { - "message": "צרף קובץ חדש" + "message": "הוסף צרופה חדשה" }, "noAttachments": { "message": "אין קבצים מצורפים." }, "attachmentSaved": { - "message": "הקובץ המצורף נשמר." + "message": "הצרופה נשמרה" }, "file": { "message": "קובץ" }, "fileToShare": { - "message": "File to share" + "message": "קובץ לשיתוף" }, "selectFile": { - "message": "בחר קובץ." + "message": "בחר קובץ" }, "maxFileSize": { - "message": "גודל הקובץ המירבי הוא 500 מגה." + "message": "גודל הקובץ המרבי הוא 500MB." }, "featureUnavailable": { - "message": "יכולת זו לא זמינה" + "message": "התכונה אינה זמינה" }, "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "message": "נדרשת הגירת מפתח הצפנה. נא להיכנס דרך כספת הרשת כדי לעדכן את מפתח ההצפנה שלך." }, "premiumMembership": { - "message": "חשבון פרימיום" + "message": "חברות פרימיום" }, "premiumManage": { - "message": "נהל חשבון" + "message": "נהל חברות" }, "premiumManageAlert": { "message": "באפשרותך לנהל את החשבון שלך דרך הכספת באתר bitwarden.com. האם ברצונך לפתוח את האתר כעת?" }, "premiumRefresh": { - "message": "רענן פרטי חשבון" + "message": "רענן חברות" }, "premiumNotCurrentMember": { - "message": "חשבונך אינו חשבון פרמיום כרגע." + "message": "אתה לא חבר פרימיום כרגע." }, "premiumSignUpAndGet": { - "message": "צור חשבון פרמיום לשנה, וקבל:" + "message": "הירשם לחברות פרימיום וקבל:" }, "ppremiumSignUpStorage": { "message": "1 ג'יגה של מקום אחסון עבור קבצים מצורפים." }, "premiumSignUpEmergency": { - "message": "Emergency access." + "message": "גישת חירום." }, "premiumSignUpTwoStepOptions": { - "message": "Proprietary two-step login options such as YubiKey and Duo." + "message": "אפשרויות כניסה דו־שלבית קנייניות כגון YubiKey ו־Duo." }, "ppremiumSignUpReports": { "message": "היגיינת סיסמאות, מצב בריאות החשבון, ודיווחים מעודכנים על פרצות חדשות בכדי לשמור על הכספת שלך בטוחה." @@ -1257,25 +1338,22 @@ "message": "קדימות בתמיכה הטכנית." }, "ppremiumSignUpFuture": { - "message": "כל יכולות הפרימיום העתידיות שנפתח. עוד יכולות מגיעות בקרוב!" + "message": "כל תכונות הפרימיום העתידיות. עוד מגיעות בקרוב!" }, "premiumPurchase": { "message": "רכוש פרימיום" }, - "premiumPurchaseAlert": { - "message": "באפשרותך לרכוש מנוי פרימיום בכספת באתר bitwarden.com. האם ברצונך לפתוח את האתר כעת?" - }, "premiumPurchaseAlertV2": { - "message": "You can purchase Premium from your account settings on the Bitwarden web app." + "message": "אתה יכול לרכוש פרימיום מהגדרות החשבון שלך ביישום הרשת של Bitwarden." }, "premiumCurrentMember": { - "message": "אתה מנוי פרימיום!" + "message": "אתה חבר פרימיום!" }, "premiumCurrentMemberThanks": { "message": "תודה על תמיכתך בBitwarden." }, "premiumFeatures": { - "message": "Upgrade to Premium and receive:" + "message": "שדרג לפרימיום וקבל:" }, "premiumPrice": { "message": "הכל רק ב$PRICE$ לשנה!", @@ -1287,7 +1365,7 @@ } }, "premiumPriceV2": { - "message": "All for just $PRICE$ per year!", + "message": "הכל רק ב־$PRICE$ לשנה!", "placeholders": { "price": { "content": "$1", @@ -1299,28 +1377,28 @@ "message": "הרענון הושלם" }, "enableAutoTotpCopy": { - "message": "Copy TOTP automatically" + "message": "העתק TOTP באופן אוטומטי" }, "disableAutoTotpCopyDesc": { - "message": "אם פרטי הכניסה שלך מקושרים לאפליקציית אימות, קוד האימות TOTP מועתק אוטומטית ללוח שלך ברגע שמתבצעת ההשלמה האוטומטית לטופס הכניסה." + "message": "אם לכניסה שלך יש מפתח מאמת, העתק את קוד האימות ה־TOTP ללוח ההעתקה שלך כאשר אתה ממלא אוטומטית את הכניסה." }, "enableAutoBiometricsPrompt": { - "message": "Ask for biometrics on launch" + "message": "בקש זיהוי ביומטרי בפתיחה" }, "premiumRequired": { - "message": "נדרש חשבון פרימיום" + "message": "נדרש פרימיום" }, "premiumRequiredDesc": { - "message": "בכדי להשתמש ביכולת זו יש צורך בחשבון פרימיום." + "message": "נדרשת חברות פרימיום כדי להשתמש בתכונה זו." }, "enterVerificationCodeApp": { "message": "הכנס את קוד האימות בן 6 הספרות מאפליקציית האימות שלך." }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "פסק זמן לאימות" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "זמן אימות ההפעלה תם. נא להתחיל מחדש את תהליך הכניסה." }, "enterVerificationCodeEmail": { "message": "הכנס את קוד האימות בן 6 הספרות שנשלח ל-$EMAIL$.", @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "זכור אותי" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "אל תשאל אותי שוב במכשיר זה למשך 30 יום" + }, "sendVerificationCodeEmailAgain": { "message": "שלח שוב קוד אימות לאימייל" }, "useAnotherTwoStepMethod": { "message": "השתמש בשיטה אחרת עבור כניסה דו שלבית" }, + "selectAnotherMethod": { + "message": "בחר שיטה אחרת", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "השתמש בקוד השחזור שלך" + }, "insertYubiKey": { "message": "הכנס את ה-YubiKey אל כניסת ה-USB במחשבך, ואז גע בכפתור שלו." }, @@ -1356,47 +1444,59 @@ "message": "הכנס את מפתח האבטחה שלך אל כניסת ה-USB במחשבך. אם יש לו כפתור, לחץ עליו." }, "webAuthnNewTab": { - "message": "To start the WebAuthn 2FA verification. Click the button below to open a new tab and follow the instructions provided in the new tab." + "message": "על מנת להתחיל אימות WebAuthn דו־שלבי. לחץ על הלחצן למטה כדי לפתוח כרטיסיה חדשה ועקוב אחר ההוראות המסופקת בכרטיסיה החדשה." }, "webAuthnNewTabOpen": { - "message": "פתיחת לשונית חדשה" + "message": "פתח כרטיסיה חדשה" + }, + "openInNewTab": { + "message": "פתח בכרטיסיה חדשה" }, "webAuthnAuthenticate": { - "message": "Authenticate WebAuthn" + "message": "אמת WebAuthn" + }, + "readSecurityKey": { + "message": "קרא מפתח אבטחה" + }, + "awaitingSecurityKeyInteraction": { + "message": "ממתין לאינטראקציה עם מפתח אבטחה..." }, "loginUnavailable": { - "message": "פרטי כניסה לא זמינים" + "message": "כניסה לא זמינה" }, "noTwoStepProviders": { - "message": "כניסה דו-שלבית פעילה בחשבון זה, אך אף אחד מספקי הכניסה הדו-שלבית לא נתמכים בדפדפן זה." + "message": "לחשבון זה מוגדרת כניסה דו־שלבית, עם זאת, אף אחד מהספקים הדו־שלביים שהוגדרו אינו נתמך על ידי דפדפן זה." }, "noTwoStepProviders2": { "message": "אנא השתמש בדפדפן נתמך (כמו לדוגמא Chrome) ו\\או הוסף ספק כניסה דו-שלבית הנתמך בדפדפן זה (כמו לדוגמא אפליקצית אימות)." }, "twoStepOptions": { - "message": "אפשרויות כניסה דו שלבית" + "message": "אפשרויות כניסה דו־שלבית" + }, + "selectTwoStepLoginMethod": { + "message": "בחר שיטת כניסה דו־שלבית" }, "recoveryCodeDesc": { - "message": "איבדת גישה לכל ספקי האימות הדו-שלבי שלך? השתמש בקוד השחזור בכדי לבטל את כל ספקי האימות הדו-שלבי דרך החשבון שלך." + "message": "איבדת גישה לכל הספקים הדו־גורמיים שלך? השתמש בקוד השחזור שלך כדי לכבות את כל הספקים הדו־גורמיים מהחשבון שלך." }, "recoveryCodeTitle": { "message": "קוד שחזור" }, "authenticatorAppTitle": { - "message": "אפליקציית אימות" + "message": "יישום מאמת" }, "authenticatorAppDescV2": { - "message": "Enter a code generated by an authenticator app like Bitwarden Authenticator.", + "message": "הזן קוד שנוצר על ידי יישום מאמת כמו מאמת Bitwarden.", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP Security Key" + "message": "מפתח אבטחה OTP של Yubico" }, "yubiKeyDesc": { "message": "השתמש בYubiKey עבור גישה לחשבון שלך. עובד עם YubiKey בגירסאות 4, 4C, 4Nano, ומכשירי NEO." }, "duoDescV2": { - "message": "Enter a code generated by Duo Security.", + "message": "הזן קוד שנוצר על ידי Duo Security.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { @@ -1407,135 +1507,129 @@ "message": "FIDO2 WebAuthn" }, "webAuthnDesc": { - "message": "Use any WebAuthn compatible security key to access your account." + "message": "השתמש בכל מפתח אבטחה תואם WebAuthn כדי לגשת לחשבונך." }, "emailTitle": { "message": "אימייל" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "הזן קוד שנשלח לדוא\"ל שלך." }, "selfHostedEnvironment": { - "message": "סביבה על שרתים מקומיים" - }, - "selfHostedEnvironmentFooter": { - "message": "הזן את כתובת השרת המקומי של Bitwarden." + "message": "סביבה באירוח עצמי" }, "selfHostedBaseUrlHint": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" + "message": "ציין את בסיס ה־URL של התקנת Bitwarden באירוח מקומי שלך. דוגמה: https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { - "message": "For advanced configuration, you can specify the base URL of each service independently." + "message": "עבור תצורה מתקדמת, באפשרותך לציין את בסיס ה־URL של כל שירות בנפרד." }, "selfHostedEnvFormInvalid": { - "message": "You must add either the base Server URL or at least one custom environment." + "message": "אתה מוכרח להוסיף או את בסיס ה־URL של השרת או לפחות סביבה מותאמת אישית אחת." }, "customEnvironment": { "message": "סביבה מותאמת אישית" }, - "customEnvironmentFooter": { - "message": "למשתמשים מתקדמים. באפשרותך לציין את כתובת השרת עבור כל שירות בנפרד." - }, "baseUrl": { "message": "כתובת שרת" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "URL של שרת אירוח עצמי", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { - "message": "כתובת שרת הAPI" + "message": "URL של שרת ה־API" }, "webVaultUrl": { - "message": "כתובת שרת הכספת" + "message": "URL של שרת כספת הרשת" }, "identityUrl": { - "message": "כתובת שרת הזהות" + "message": "URL של שרת הזהות" }, "notificationsUrl": { - "message": "כתובת שרת הודעות" + "message": "URL של שרת ההודעות" }, "iconsUrl": { - "message": "כתובת שרת אייקונים" + "message": "URL של שרת הסמלים" }, "environmentSaved": { - "message": "כתובות הסביבה נשמרו." + "message": "כתובות URL של הסביבה נשמרו" }, "showAutoFillMenuOnFormFields": { - "message": "Show autofill menu on form fields", + "message": "הצג תפריט מילוי אוטומטי על שדות טופס", "description": "Represents the message for allowing the user to enable the autofill overlay" }, "autofillSuggestionsSectionTitle": { - "message": "Autofill suggestions" + "message": "הצעות למילוי אוטומטי" }, "showInlineMenuLabel": { - "message": "Show autofill suggestions on form fields" + "message": "הצג הצעות למילוי אוטומטי על שדות טופס" }, "showInlineMenuIdentitiesLabel": { - "message": "Display identities as suggestions" + "message": "הצג זהויות כהצעות" }, "showInlineMenuCardsLabel": { - "message": "Display cards as suggestions" + "message": "הצג כרטיסים כהצעות" }, "showInlineMenuOnIconSelectionLabel": { - "message": "Display suggestions when icon is selected" + "message": "הצג הצעות כאשר נבחר סמל" }, "showInlineMenuOnFormFieldsDescAlt": { - "message": "Applies to all logged in accounts." + "message": "חל על כל החשבונות המחוברים." }, "turnOffBrowserBuiltInPasswordManagerSettings": { - "message": "Turn off your browser's built in password manager settings to avoid conflicts." + "message": "כבה את הגדרות מנהל הסיסמאות המובנה של הדפדפן שלך כדי להימנע מקונפליקטים." }, "turnOffBrowserBuiltInPasswordManagerSettingsLink": { - "message": "Edit browser settings." + "message": "ערוך הגדרות דפדפן." }, "autofillOverlayVisibilityOff": { - "message": "Off", + "message": "כבוי", "description": "Overlay setting select option for disabling autofill overlay" }, "autofillOverlayVisibilityOnFieldFocus": { - "message": "When field is selected (on focus)", + "message": "כאשר נבחר שדה (בפוקוס)", "description": "Overlay appearance select option for showing the field on focus of the input element" }, "autofillOverlayVisibilityOnButtonClick": { - "message": "When autofill icon is selected", + "message": "כאשר נבחר סמל מילוי אוטומטי", "description": "Overlay appearance select option for showing the field on click of the overlay icon" }, "enableAutoFillOnPageLoadSectionTitle": { - "message": "Autofill on page load" + "message": "מילוי אוטומטי בעת טעינת עמוד" }, "enableAutoFillOnPageLoad": { - "message": "הפעל השלמה אוטומטית בזמן טעינת העמוד" + "message": "מילוי אוטומטי בעת טעינת עמוד" }, "enableAutoFillOnPageLoadDesc": { - "message": "אם זוהה טופס כניסה, בצע אוטומטית מילוי-אוטומטי כשהעמוד נטען." + "message": "אם זוהה טופס כניסה, בצע מילוי אוטומטי כשהעמוד נטען." }, "experimentalFeature": { - "message": "Compromised or untrusted websites can exploit autofill on page load." + "message": "אתרים פרוצים או לא מהימנים יכולים לנצל מילוי אוטומטי בעת טעינת עמוד." }, "learnMoreAboutAutofillOnPageLoadLinkText": { - "message": "Learn more about risks" + "message": "למד עוד על סיכונים" }, "learnMoreAboutAutofill": { - "message": "Learn more about autofill" + "message": "למד עוד על מילוי אוטומטי" }, "defaultAutoFillOnPageLoad": { - "message": "הגדרת ברירת מחדל למילוי אוטומטי של פרטי התחברות" + "message": "הגדרת ברירת המחדל של מילוי אוטומטי לפריטי כניסה" }, "defaultAutoFillOnPageLoadDesc": { - "message": "לאחר הפעלת מילוי אוטומטי של פרטים בעת טעינת דפים, אפשר להפעיל או לכבות את האפשרות לפרטי התחברות ספציפיים. זו הגדרת ברירת המחדל לפרטי התחברות שלא הוגדרו בנפרד." + "message": "אתה יכול לכבות מילוי אוטומטי בעת טעינת עמוד עבור פריטי כניסה בודדים מתצוגת העריכה של הפריט." }, "itemAutoFillOnPageLoad": { - "message": "מילוי אוטומטי בעת טעינת דפים (אם מופעל בהגדרות)" + "message": "מילוי אוטומטי בעת טעינת עמוד (אם מוגדר באפשרויות)" }, "autoFillOnPageLoadUseDefault": { - "message": "שימוש בהגדרות ברירת המחדל" + "message": "השתמש בהגדרת ברירת המחדל" }, "autoFillOnPageLoadYes": { - "message": "מילוי אוטומטי אחרי טעינת דפים" + "message": "מילוי אוטומטי בעת טעינת עמוד" }, "autoFillOnPageLoadNo": { - "message": "Do not autofill on page load" + "message": "אל תמלא אוטומטית בעת טעינת עמוד" }, "commandOpenPopup": { "message": "פתיחת כספת בחלונית צפה" @@ -1544,13 +1638,13 @@ "message": "פתיחת כספת בסרגל צד" }, "commandAutofillLoginDesc": { - "message": "Autofill the last used login for the current website" + "message": "מלא אוטומטית את הכניסה האחרונה בה השתמשת עבור האתר הנוכחי" }, "commandAutofillCardDesc": { - "message": "Autofill the last used card for the current website" + "message": "מלא אוטומטית את הכרטיס האחרון בו השתמשת עבור האתר הנוכחי" }, "commandAutofillIdentityDesc": { - "message": "Autofill the last used identity for the current website" + "message": "מלא אוטומטית את הזהות האחרונה בה השתמשת עבור האתר הנוכחי" }, "commandGeneratePasswordDesc": { "message": "צור והעתק סיסמה רנדומלית חדשה." @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "גרור כדי למיין" }, + "dragToReorder": { + "message": "גרור כדי לסדר מחדש" + }, "cfTypeText": { "message": "טקסט" }, @@ -1583,7 +1680,7 @@ "message": "אמת או שקר" }, "cfTypeCheckbox": { - "message": "Checkbox" + "message": "תיבת סימון" }, "cfTypeLinked": { "message": "מקושר", @@ -1600,19 +1697,19 @@ "message": "דפדפן זה לא יכול לעבד בקשות U2F בחלון צף זה. האם ברצונך לפתוח את החלון הצף כחלון חדש רגיל כדי שתוכל להכנס באמצעות U2F?" }, "enableFavicon": { - "message": "Show website icons" + "message": "הצג סמלי אתר אינטרנט" }, "faviconDesc": { - "message": "Show a recognizable image next to each login." + "message": "הצג תמונה מוכרת ליד כל כניסה." }, "faviconDescAlt": { - "message": "Show a recognizable image next to each login. Applies to all logged in accounts." + "message": "הצג תמונה מוכרת ליד כל כניסה. חל על כל החשבונות המחוברים." }, "enableBadgeCounter": { - "message": "Show badge counter" + "message": "הצג מונה סמל" }, "badgeCounterDesc": { - "message": "Indicate how many logins you have for the current web page." + "message": "מציין כמה כניסות יש לך עבור עמוד הרשת הנוכחי." }, "cardholderName": { "message": "שם בעל הכרטיס" @@ -1624,10 +1721,10 @@ "message": "מותג" }, "expirationMonth": { - "message": "תוקף אשראי - חודש" + "message": "חודש תפוגה" }, "expirationYear": { - "message": "תוקף אשראי - שנה" + "message": "שנת תפוגה" }, "expiration": { "message": "תוקף" @@ -1690,7 +1787,7 @@ "message": "דוקטור" }, "mx": { - "message": "Mx" + "message": "מיקס" }, "firstName": { "message": "שם פרטי" @@ -1711,13 +1808,13 @@ "message": "חברה" }, "ssn": { - "message": "מספר ביטוח לאומי" + "message": "מספר תעודת זהות" }, "passportNumber": { "message": "מספר דרכון" }, "licenseNumber": { - "message": "מספר רשיון" + "message": "מספר רישיון" }, "email": { "message": "אימייל" @@ -1759,7 +1856,7 @@ "message": "פרטי התחברות" }, "typeSecureNote": { - "message": "פתק מאובטח" + "message": "הערה מאובטחת" }, "typeCard": { "message": "כרטיס" @@ -1768,10 +1865,10 @@ "message": "זהות" }, "typeSshKey": { - "message": "SSH key" + "message": "מפתח SSH" }, "newItemHeader": { - "message": "New $TYPE$", + "message": "$TYPE$ חדש", "placeholders": { "type": { "content": "$1", @@ -1780,7 +1877,7 @@ } }, "editItemHeader": { - "message": "Edit $TYPE$", + "message": "ערוך $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1789,7 +1886,7 @@ } }, "viewItemHeader": { - "message": "View $TYPE$", + "message": "הצג $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1801,13 +1898,13 @@ "message": "היסטוריית סיסמאות" }, "generatorHistory": { - "message": "Generator history" + "message": "היסטוריית מחולל" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "נקה היסטוריית מחולל" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "אם תמשיך, כל הרשומות יימחקו לצמיתות מהיסטוריית המחולל. האם אתה בטוח שברצונך להמשיך?" }, "back": { "message": "הקודם" @@ -1816,7 +1913,7 @@ "message": "אוספים" }, "nCollections": { - "message": "$COUNT$ collections", + "message": "$COUNT$ אוספים", "placeholders": { "count": { "content": "$1", @@ -1843,10 +1940,10 @@ "message": "פרטי התחברות" }, "secureNotes": { - "message": "פתקים מאובטחים" + "message": "הערות מאובטחות" }, "sshKeys": { - "message": "SSH Keys" + "message": "מפתחות SSH" }, "clear": { "message": "נקה", @@ -1872,11 +1969,11 @@ "description": "Domain name. Ex. website.com" }, "baseDomainOptionRecommended": { - "message": "Base domain (recommended)", + "message": "בסיס דומיין (מומלץ)", "description": "Domain name. Ex. website.com" }, "domainName": { - "message": "שם תחום", + "message": "שם דומיין", "description": "Domain name. Ex. website.com" }, "host": { @@ -1902,7 +1999,7 @@ "description": "Default URI match detection for autofill." }, "toggleOptions": { - "message": "הצגה\\הסתרה של אפשרויות" + "message": "החלף מצב אפשרויות" }, "toggleCurrentUris": { "message": "שנה מצב הצגת כתובות URI", @@ -1926,13 +2023,13 @@ "message": "אין סיסמאות להצגה ברשימה." }, "clearHistory": { - "message": "Clear history" + "message": "נקה היסטוריה" }, "nothingToShow": { - "message": "Nothing to show" + "message": "אין מה להראות" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "לא יצרת שום דבר לאחרונה" }, "remove": { "message": "הסר" @@ -1945,7 +2042,7 @@ "description": "ex. Date this item was updated" }, "dateCreated": { - "message": "Created", + "message": "נוצר", "description": "ex. Date this item was created" }, "datePasswordUpdated": { @@ -1993,16 +2090,16 @@ "message": "בטל נעילה עם קוד PIN" }, "setYourPinTitle": { - "message": "Set PIN" + "message": "הגדר PIN" }, "setYourPinButton": { - "message": "Set PIN" + "message": "הגדר PIN" }, "setYourPinCode": { "message": "קבע קוד PIN לביטול נעילת Bitwarden. הגדרות הPIN יאופסו אם תבצע יציאה מהתוכנה." }, "setYourPinCode1": { - "message": "Your PIN will be used to unlock Bitwarden instead of your master password. Your PIN will reset if you ever fully log out of Bitwarden." + "message": "ה־PIN שלך ישמש לביטול נעילת Bitwarden במקום הסיסמה הראשית שלך. ה־PIN שלך יאופס אם אי פעם תצא באופן מלא מ־Bitwarden." }, "pinRequired": { "message": "נדרש קוד PIN." @@ -2011,25 +2108,25 @@ "message": "קוד PIN לא תקין." }, "tooManyInvalidPinEntryAttemptsLoggingOut": { - "message": "Too many invalid PIN entry attempts. Logging out." + "message": "יותר מדי ניסיונות פסולים להזנת PIN. יוצא." }, "unlockWithBiometrics": { "message": "פתח נעילה עם זיהוי ביומטרי" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "בטל נעילה עם סיסמה ראשית" }, "awaitDesktop": { "message": "ממתין לאישור משולחן העבודה" }, "awaitDesktopDesc": { - "message": "אנא אשר בעזרת אמצעים ביומטרים באפליקציית Bitwarden של שולחן העבודה בכדי לאפשר אמצעים ביומטריים בדפדפן." + "message": "אנא אשר באמצעות זיהוי ביומטרי ביישום שולחן העבודה של Bitwarden בכדי להגדיר זיהוי ביומטרי עבור דפדפן." }, "lockWithMasterPassOnRestart": { "message": "נעל בעזרת הסיסמה הראשית בהפעלת הדפדפן מחדש" }, "lockWithMasterPassOnRestart1": { - "message": "Require master password on browser restart" + "message": "דרוש סיסמה ראשית בעת הפעלה מחדש של הדפדפן" }, "selectOneCollection": { "message": "עליך לבחור לפחות אוסף אחד." @@ -2041,33 +2138,48 @@ "message": "שכפול" }, "passwordGenerator": { - "message": "Password generator" + "message": "מחולל הסיסמאות" }, "usernameGenerator": { - "message": "Username generator" + "message": "מחולל שם משתמש" + }, + "useThisEmail": { + "message": "השתמש בדוא\"ל זה" }, "useThisPassword": { - "message": "Use this password" + "message": "השתמש בסיסמה זו" }, "useThisUsername": { - "message": "Use this username" + "message": "השתמש בשם משתמש זה" }, "securePasswordGenerated": { - "message": "Secure password generated! Don't forget to also update your password on the website." + "message": "סיסמה מאובטחת נוצרה! אל תשכח גם לעדכן את הסיסמה שלך באתר האינטרנט." }, "useGeneratorHelpTextPartOne": { - "message": "Use the generator", + "message": "השתמש במחולל", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "useGeneratorHelpTextPartTwo": { - "message": "to create a strong unique password", + "message": "כדי ליצור סיסמה חזקה וייחודית", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "התאמה אישית של הכספת" + }, "vaultTimeoutAction": { - "message": "פעולה לביצוע בכספת בתום זמן החיבור" + "message": "פעולת פסק זמן לכספת" }, "vaultTimeoutAction1": { - "message": "Timeout action" + "message": "פעולת פסק זמן" + }, + "newCustomizationOptionsCalloutTitle": { + "message": "אפשרויות התאמה אישית חדשות" + }, + "newCustomizationOptionsCalloutContent": { + "message": "התאם אישית את חווית הכספת שלך עם פעולות העתקה מהירות, מצב קומפקטי, ועוד!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "הצג את כל הגדרות המראה" }, "lock": { "message": "נעילה", @@ -2081,52 +2193,52 @@ "message": "חפש בסל המחזור" }, "permanentlyDeleteItem": { - "message": "מחק לצמיתות פריט שנבחר" + "message": "מחק פריט לצמיתות" }, "permanentlyDeleteItemConfirmation": { "message": "האם אתה בטוח שברצונך למחוק את הפריט הזה?" }, "permanentlyDeletedItem": { - "message": "פריט שנמחק לצמיתות" + "message": "הפריט נמחק לצמיתות" }, "restoreItem": { "message": "שחזר פריט" }, "restoredItem": { - "message": "פריט ששוחזר" + "message": "הפריט שוחזר" }, "alreadyHaveAccount": { - "message": "Already have an account?" + "message": "כבר יש לך חשבון?" }, "vaultTimeoutLogOutConfirmation": { "message": "יציאה מהחשבון תסיר את כל הגישה לכספת ויידרש אימות מקוון לאחר משך הזמן שהוקצב. האם אתה בטוח שברצונך להשתמש בהגדרה זו?" }, "vaultTimeoutLogOutConfirmationTitle": { - "message": "אישור פעולת אימות לאחר חוסר פעילות" + "message": "אישור פעולת פסק זמן" }, "autoFillAndSave": { - "message": "בצע השלמה אוטומטית ושמור" + "message": "מלא אוטומטית ושמור" }, "fillAndSave": { - "message": "Fill and save" + "message": "מלא ושמור" }, "autoFillSuccessAndSavedUri": { - "message": "בוצעה השלמה אוטומטית והכתובת נשמרה" + "message": "הפריט מולא אוטומטית וה־URI נשמר" }, "autoFillSuccess": { - "message": "בוצעה השלמה אוטומטית" + "message": "הפריט מולא אוטומטית " }, "insecurePageWarning": { - "message": "Warning: This is an unsecured HTTP page, and any information you submit can potentially be seen and changed by others. This Login was originally saved on a secure (HTTPS) page." + "message": "אזהרה: זהו עמוד HTTP לא מאובטח, וכל מידע שאתה שולח יכול באופן פוטנציאלי להיראות ולהשתנות על ידי אחרים. הכניסה הזאת נשמרה במקור בעמוד מאובטח (HTTPS)." }, "insecurePageWarningFillPrompt": { - "message": "Do you still wish to fill this login?" + "message": "האם עדיין ברצונך להשלים כניסה זו?" }, "autofillIframeWarning": { - "message": "The form is hosted by a different domain than the URI of your saved login. Choose OK to autofill anyway, or Cancel to stop." + "message": "הטופס מאורח על ידי דומיין שונה מה־URI של הכניסה השמורה שלך. בחר \"בסדר\" כדי למלא אוטומטית בכל זאת, או \"ביטול\" כדי לעצור." }, "autofillIframeWarningTip": { - "message": "To prevent this warning in the future, save this URI, $HOSTNAME$, to your Bitwarden login item for this site.", + "message": "כדי למנוע אזהרה זו בעתיד, שמור את URI זה, $HOSTNAME$, בפריט כניסת Bitwarden שלך עבור אתר זה.", "placeholders": { "hostname": { "content": "$1", @@ -2138,13 +2250,13 @@ "message": "הגדר סיסמה ראשית" }, "currentMasterPass": { - "message": "Current master password" + "message": "סיסמה ראשית נוכחית" }, "newMasterPass": { - "message": "New master password" + "message": "סיסמה ראשית חדשה" }, "confirmNewMasterPass": { - "message": "Confirm new master password" + "message": "אמת סיסמה ראשית חדשה" }, "masterPasswordPolicyInEffect": { "message": "אחד או יותר מאילוצי המדיניות של הארגון דורשים שהסיסמה הראשית שלך תעמוד בדרישות הבאות:" @@ -2189,25 +2301,25 @@ "message": "הסיסמה הראשית החדשה השלך לא עומדת בדרישות המדיניות." }, "receiveMarketingEmailsV2": { - "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." + "message": "קבל עצות, הכרזות, והזדמנויות מחקר מאת Bitwarden בדואר הנכנס שלך." }, "unsubscribe": { - "message": "Unsubscribe" + "message": "בטל הרשמה" }, "atAnyTime": { - "message": "at any time." + "message": "בכל זמן." }, "byContinuingYouAgreeToThe": { - "message": "By continuing, you agree to the" + "message": "על ידי המשך, אתה מסכים ל" }, "and": { - "message": "and" + "message": "ו" }, "acceptPolicies": { "message": "סימון תיבה זו מהווה את הסכמתך לתנאים הבאים:" }, "acceptPoliciesRequired": { - "message": "Terms of Service and Privacy Policy have not been acknowledged." + "message": "תנאי השימוש ומדיניות הפרטיות לא הוכרו." }, "termsOfService": { "message": "תנאי השירות" @@ -2222,10 +2334,10 @@ "message": "אישור" }, "errorRefreshingAccessToken": { - "message": "Access Token Refresh Error" + "message": "שגיאת רענון אסימון גישה" }, "errorRefreshingAccessTokenDesc": { - "message": "No refresh token or API keys found. Please try logging out and logging back in." + "message": "לא נמצאו אסימון רענון או מפתחות API. נא לנסות לצאת ולהיכנס חזרה." }, "desktopSyncVerificationTitle": { "message": "אימות סנכרון מול שולחן העבודה" @@ -2234,19 +2346,19 @@ "message": "אנא ודא כי אפליקציית שולחן העבודה שלך מציגה את טביעת האצבע הזו: " }, "desktopIntegrationDisabledTitle": { - "message": "אינטגרציית הדפדפן לא מופעלת" + "message": "שילוב הדפדפן אינו מוגדר" }, "desktopIntegrationDisabledDesc": { - "message": "אינטגרציית הדפדפן לא מופעלת באפליקציית Bitwarden בשולחן העבודה. אנא אפשר זאת בהגדרות האפליקציה." + "message": "שילוב הדפדפן אינו מוגדר ביישום שולחן העבודה של Bitwarden. אנא הגדר אותו בהגדרות שבתוך יישום שולחן העבודה." }, "startDesktopTitle": { - "message": "הפעל את אפליקציית Bitwarden בשולחן העבודה" + "message": "הפעל את יישום שולחן העבודה של Bitwarden" }, "startDesktopDesc": { - "message": "יש להפעיל את אפליקציית Bitwarden בשולחן העבודה בכדי להשתמש בפונקציה זו." + "message": "יישום שולחן העבודה של Bitwarden צריך להיות מופעל לפני שניתן לבטל נעילה עם זיהוי ביומטרי." }, "errorEnableBiometricTitle": { - "message": "לא ניתן להפעיל זיהוי ביומטרי" + "message": "לא ניתן להגדיר זיהוי ביומטרי" }, "errorEnableBiometricDesc": { "message": "הפעולה בוטלה על ידי אפליקציית שולחן העבודה" @@ -2264,16 +2376,16 @@ "message": "חוסר התאמה בין חשבונות" }, "nativeMessagingWrongUserKeyTitle": { - "message": "Biometric key missmatch" + "message": "אי־התאמה של מפתח זיהוי ביומטרי" }, "nativeMessagingWrongUserKeyDesc": { - "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + "message": "ביטול נעילה ביומטרי נכשל. מפתח הסוד הביומטרי נכשל בביטול נעילת הכספת. נא לנסות להגדיר זיהוי ביומטרי שוב." }, "biometricsNotEnabledTitle": { - "message": "אמצעי זיהוי ביומטרים לא מאופשרים" + "message": "זיהוי ביומטרי אינו מוגדר" }, "biometricsNotEnabledDesc": { - "message": "בכדי להשתמש באמצעים ביומטרים בדפדפן יש לאפשר תכונה זו באפליקציה בשולחן העבודה." + "message": "זיהוי ביומטרי בדפדפן דורש שזיהוי ביומטרי בשולחן העבודה יהיה מוגדר בהגדרות קודם." }, "biometricsNotSupportedTitle": { "message": "אמצעי זיהוי ביומטרים לא נתמכים" @@ -2282,22 +2394,22 @@ "message": "מכשיר זה לא תומך בזיהוי ביומטרי בדפדפן." }, "biometricsNotUnlockedTitle": { - "message": "User locked or logged out" + "message": "המשתמש נעל או יצא" }, "biometricsNotUnlockedDesc": { - "message": "Please unlock this user in the desktop application and try again." + "message": "נא לבטל נעילת משתמש זה ביישום שולחן העבודה ולנסות שוב." }, "biometricsNotAvailableTitle": { - "message": "Biometric unlock unavailable" + "message": "ביטול נעילה ביומטרי לא זמין" }, "biometricsNotAvailableDesc": { - "message": "Biometric unlock is currently unavailable. Please try again later." + "message": "ביטול נעילה ביומטרי לא זמין כרגע. נא לנסות שוב מאוחר יותר." }, "biometricsFailedTitle": { - "message": "Biometrics failed" + "message": "זיהוי ביומטרי נכשל" }, "biometricsFailedDesc": { - "message": "Biometrics cannot be completed, consider using a master password or logging out. If this persists, please contact Bitwarden support." + "message": "לא ניתן להשלים זיהוי ביומטרי, שקול להשתמש במפתח ראשי או לצאת. אם הדבר נמשך, אנא צור קשר עם תמיכת Bitwarden." }, "nativeMessaginPermissionErrorTitle": { "message": "הרשאה לא סופקה" @@ -2306,35 +2418,153 @@ "message": "ללא הרשאות לתקשר עם אפליקציית שולחן העבודה אין באפשרותנו לספק תמיכה באמצעים ביומטריים בדפדפן. אנא נסה שוב." }, "nativeMessaginPermissionSidebarTitle": { - "message": "Permission request error" + "message": "שגיאת בקשת הרשאה" }, "nativeMessaginPermissionSidebarDesc": { - "message": "This action cannot be done in the sidebar, please retry the action in the popup or popout." + "message": "לא ניתן לבצע את הפעולה בסרגל הצד, נא לנסות שוב את הפעולה בחלון הצץ או המוקפץ." }, "personalOwnershipSubmitError": { - "message": "מדיניות הארגון מונעת ממך לשמור פריטים בכספת האישית. שנה את אפשרות הבעלות לארגוניות ובחר מתוך האוספים הזמינים." + "message": "בשל מדיניות ארגונית, אתה מוגבל מלשמור פריטים לכספת האישית שלך. שנה את אפשרות הבעלות לארגון ובחר מאוספים זמינים." }, "personalOwnershipPolicyInEffect": { "message": "מדיניות ארגונית משפיעה על אפשרויות הבעלות שלך." }, "personalOwnershipPolicyInEffectImports": { - "message": "An organization policy has blocked importing items into your individual vault." + "message": "מדיניות ארגון חסמה ייבוא פריטים אל תוך הכספת האישית שלך." }, "domainsTitle": { - "message": "Domains", + "message": "דומיינים", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "דומיינים חסומים" + }, + "learnMoreAboutBlockedDomains": { + "message": "למד עוד על דומיינים חסומים" + }, "excludedDomains": { - "message": "Excluded domains" + "message": "דומיינים מוחרגים" }, "excludedDomainsDesc": { - "message": "Bitwarden will not ask to save login details for these domains. You must refresh the page for changes to take effect." + "message": "Bitwarden לא יבקש לשמור פרטי כניסה עבור הדומיינים האלה. אתה מוכרח לרענן את העמוד כדי שהשינויים ייכנסו לתוקף." }, "excludedDomainsDescAlt": { - "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." + "message": "Bitwarden לא יבקש לשמור פרטי כניסה עבור הדומיינים האלה עבור כל החשבונות המחוברים. אתה מוכרח לרענן את העמוד כדי שהשינויים ייכנסו לתוקף." + }, + "blockedDomainsDesc": { + "message": "לא יוצעו מילוי אוטומטי ותכונות קשורות אחרות עבור האתרים האלה. אתה מוכרח לרענן את העמוד כדי שהשינויים ייכנסו לתוקף." + }, + "autofillBlockedNoticeV2": { + "message": "מילוי אוטומטי חסום עבור אתר זה." + }, + "autofillBlockedNoticeGuidance": { + "message": "שנה זאת בהגדרות" + }, + "change": { + "message": "שינוי" + }, + "changeButtonTitle": { + "message": "שנה סיסמה - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "סיסמאות בסיכון" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ מבקש/ת ממך לשנות סיסמה אחת בגלל שהיא בסיכון.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ מבקש/ת ממך לשנות $COUNT$ סיסמאות בגלל שהן בסיכון.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "הארגונים שלך מבקשים שתשנה $COUNT$ סיסמאות בגלל שהן בסיכון.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "סקור ושנה סיסמה אחת בסיכון" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "סקור ושנה $COUNT$ סיסמאות בסיכון", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "שנה סיסמאות בסיכון מהר יותר" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "עדכן את ההגדרות שלך כך שתוכל למלא במהירות את הסיסמאות שלך וליצור חדשות" + }, + "reviewAtRiskLogins": { + "message": "סקור כניסות בסיכון" + }, + "reviewAtRiskPasswords": { + "message": "סקור סיסמאות בסיכון" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "סיסמאות הארגון שלך הן בסכנה בגלל שהן חלשות, משומשות, ו/או חשופות.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "איור של רשימת כניסות בסיכון" + }, + "generatePasswordSlideDesc": { + "message": "צור במהירות סיסמה חזקה וייחודית עם תפריט המילוי האוטומטי של Bitwarden באתר שבסיכון.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "איור של תפריט המילוי האוטומטי של Bitwarden המציג סיסמה שנוצרה" + }, + "updateInBitwarden": { + "message": "עדכן ב־Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden אז ינחה אותך לעדכן את הסיסמה במנהל הסיסמאות.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "איור של התראת Bitwarden המנחה את המשתמש לעדכן את הכניסה" + }, + "turnOnAutofill": { + "message": "הפעל מילוי אוטומטי" + }, + "turnedOnAutofill": { + "message": "מילוי אוטומטי הופעל" + }, + "dismiss": { + "message": "התעלם" }, "websiteItemLabel": { - "message": "Website $number$ (URI)", + "message": "אתר אינטרנט $number$ (URI)", "placeholders": { "number": { "content": "$1", @@ -2343,7 +2573,7 @@ } }, "excludedDomainsInvalidDomain": { - "message": "$DOMAIN$ is not a valid domain", + "message": "$DOMAIN$ אינו דומיין חוקי", "placeholders": { "domain": { "content": "$1", @@ -2351,18 +2581,21 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "שינויי דומיין חסום נשמרו" + }, "excludedDomainsSavedSuccess": { - "message": "Excluded domain changes saved" + "message": "שינויי דומיין מוחרג נשמרו" }, "limitSendViews": { - "message": "Limit views" + "message": "הגבל צפיות" }, "limitSendViewsHint": { - "message": "No one can view this Send after the limit is reached.", + "message": "אף אחד לא יכול לצפות בסֵנְד זה לאחר ההגעה למגבלה.", "description": "Displayed under the limit views field on Send" }, "limitSendViewsCount": { - "message": "$ACCESSCOUNT$ views left", + "message": "$ACCESSCOUNT$ צפיות נותרו", "description": "Displayed under the limit views field on Send", "placeholders": { "accessCount": { @@ -2372,93 +2605,93 @@ } }, "send": { - "message": "Send", + "message": "סֵנְד", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDetails": { - "message": "Send details", + "message": "פרטי סֵנְד", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTypeText": { - "message": "Text" + "message": "טקסט" }, "sendTypeTextToShare": { - "message": "Text to share" + "message": "טקסט לשיתוף" }, "sendTypeFile": { - "message": "File" + "message": "קובץ" }, "allSends": { - "message": "All Sends", + "message": "כל הסֵנְדים", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "hideTextByDefault": { - "message": "Hide text by default" + "message": "הסתר טקסט כברירת מחדל" }, "expired": { - "message": "Expired" + "message": "פג תוקף" }, "passwordProtected": { - "message": "Password protected" + "message": "מוגן סיסמה" }, "copyLink": { - "message": "Copy link" + "message": "העתק קישור" }, "copySendLink": { - "message": "Copy Send link", + "message": "העתק קישור סֵנְד", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "removePassword": { - "message": "Remove Password" + "message": "הסר סיסמה" }, "delete": { - "message": "Delete" + "message": "מחק" }, "removedPassword": { - "message": "Password removed" + "message": "הסיסמה הוסרה" }, "deletedSend": { - "message": "Send deleted", + "message": "סֵנְד נמחק", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLink": { - "message": "Send link", + "message": "קישור סֵנְד", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "disabled": { - "message": "Disabled" + "message": "מושבת" }, "removePasswordConfirmation": { - "message": "Are you sure you want to remove the password?" + "message": "האם אתה בטוח שברצונך להסיר את הסיסמה?" }, "deleteSend": { - "message": "Delete Send", + "message": "מחק סֵנְד", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deleteSendConfirmation": { - "message": "Are you sure you want to delete this Send?", + "message": "האם אתה בטוח שברצונך למחוק סֵנְד זה?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deleteSendPermanentConfirmation": { - "message": "Are you sure you want to permanently delete this Send?", + "message": "האם אתה בטוח שברצונך למחוק לצמיתות סֵנְד זה?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editSend": { - "message": "Edit Send", + "message": "ערוך סֵנְד", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "תאריך מחיקה" }, "deletionDateDescV2": { - "message": "The Send will be permanently deleted on this date.", + "message": "סֵנְד זה יימחק לצמיתות בתאריך זה.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { "message": "תאריך תפוגה" }, "oneDay": { - "message": "יום אחד" + "message": "יום 1" }, "days": { "message": "$DAYS$ ימים", @@ -2473,38 +2706,38 @@ "message": "מותאם אישית" }, "sendPasswordDescV3": { - "message": "Add an optional password for recipients to access this Send.", + "message": "הוסף סיסמה אופציונלית עבור נמענים כדי לגשת לסֵנְד זה.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createSend": { - "message": "New Send", + "message": "סֵנְד חדש", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "newPassword": { - "message": "New password" + "message": "סיסמה חדשה" }, "sendDisabled": { - "message": "Send removed", + "message": "סֵנְד הוסר", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { - "message": "Due to an enterprise policy, you are only able to delete an existing Send.", + "message": "בשל מדיניות ארגונית, אתה יכול למחוק רק סֵנְד קיים.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSend": { - "message": "Send created", + "message": "סֵנְד נוצר", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSendSuccessfully": { - "message": "Send created successfully!", + "message": "סֵנְד נוצר בהצלחה!", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendExpiresInHoursSingle": { - "message": "The Send will be available to anyone with the link for the next 1 hour.", + "message": "הסֵנְד יהיה זמין לכל אחד עם הקישור במשך השעה הבאה.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendExpiresInHours": { - "message": "The Send will be available to anyone with the link for the next $HOURS$ hours.", + "message": "הסֵנְד יהיה זמין לכל אחד עם הקישור במשך $HOURS$ השעות הבאות.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "hours": { @@ -2514,11 +2747,11 @@ } }, "sendExpiresInDaysSingle": { - "message": "The Send will be available to anyone with the link for the next 1 day.", + "message": "הסֵנְד יהיה זמין לכל אחד עם הקישור במשך היום הבא.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendExpiresInDays": { - "message": "The Send will be available to anyone with the link for the next $DAYS$ days.", + "message": "הסֵנְד יהיה זמין לכל אחד עם הקישור במשך $DAYS$ הימים הבאים.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "days": { @@ -2528,98 +2761,98 @@ } }, "sendLinkCopied": { - "message": "Send link copied", + "message": "קישור סֵנְד הועתק", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { - "message": "Send saved", + "message": "סֵנְד נשמר", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendFilePopoutDialogText": { - "message": "Pop out extension?", + "message": "להקפיץ הרחבה?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendFilePopoutDialogDesc": { - "message": "To create a file Send, you need to pop out the extension to a new window.", + "message": "כדי ליצור קובץ סֵנְד, אתה צריך להקפיץ את ההרחבה לחלון חדש.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLinuxChromiumFileWarning": { - "message": "In order to choose a file, open the extension in the sidebar (if possible) or pop out to a new window by clicking this banner." + "message": "כדי לבחור קובץ, פתח את ההרחבה בסרגל הצד (אם ניתן) או הקפץ לחלון חדש על ידי לחיצת באנר זה." }, "sendFirefoxFileWarning": { - "message": "In order to choose a file using Firefox, open the extension in the sidebar or pop out to a new window by clicking this banner." + "message": "כדי לבחור קובץ באמצעות Firefox, פתח את ההרחבה בסרגל הצד או הקפץ לחלון חדש על ידי לחיצת באנר זה." }, "sendSafariFileWarning": { - "message": "In order to choose a file using Safari, pop out to a new window by clicking this banner." + "message": "כדי לבחור קובץ באמצעות Safari, הקפץ לחלון חדש על ידי לחיצת באנר זה." }, "popOut": { - "message": "Pop out" + "message": "הקפץ" }, "sendFileCalloutHeader": { - "message": "Before you start" + "message": "לפני שאתה מתחיל" }, "expirationDateIsInvalid": { - "message": "The expiration date provided is not valid." + "message": "תאריך התפוגה שסופק אינו חוקי." }, "deletionDateIsInvalid": { - "message": "The deletion date provided is not valid." + "message": "תאריך המחיקה שסופק אינו חוקי." }, "expirationDateAndTimeRequired": { - "message": "An expiration date and time are required." + "message": "נדרשים תאריך וזמן תפוגה." }, "deletionDateAndTimeRequired": { - "message": "A deletion date and time are required." + "message": "נדרשים תאריך וזמן מחיקה." }, "dateParsingError": { - "message": "There was an error saving your deletion and expiration dates." + "message": "הייתה שגיאה בשמירת תאריכי המחיקה והתפוגה שלך." }, "hideYourEmail": { - "message": "Hide your email address from viewers." + "message": "הסתר את כתובת הדוא\"ל שלך מצופים." }, "passwordPrompt": { - "message": "Master password re-prompt" + "message": "בקשה חוזרת של סיסמה ראשית" }, "passwordConfirmation": { - "message": "Master password confirmation" + "message": "אישור סיסמה ראשית" }, "passwordConfirmationDesc": { - "message": "This action is protected. To continue, please re-enter your master password to verify your identity." + "message": "פעולה זו מוגנת. כדי להמשיך, נא להזין שוב את הסיסמה הראשית שלך כדי לאמת את זהותך." }, "emailVerificationRequired": { - "message": "Email verification required" + "message": "נדרש אימות דוא\"ל" }, "emailVerifiedV2": { - "message": "Email verified" + "message": "דוא\"ל אומת" }, "emailVerificationRequiredDesc": { - "message": "You must verify your email to use this feature. You can verify your email in the web vault." + "message": "עליך לאמת את הדוא\"ל שלך כדי להשתמש בתכונה זו. ניתן לאמת את הדוא\"ל שלך בכספת הרשת." }, "updatedMasterPassword": { - "message": "Updated master password" + "message": "סיסמה ראשית עודכנה" }, "updateMasterPassword": { - "message": "Update master password" + "message": "עדכן סיסמה ראשית" }, "updateMasterPasswordWarning": { - "message": "Your master password was recently changed by an administrator in your organization. In order to access the vault, you must update it now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." + "message": "הסיסמה הראשית שלך שונתה לאחרונה על ידי מנהל הארגון שלך. כדי לגשת לכספת, עליך לעדכן אותה כעת. המשך התהליך יוציא אותך מההפעלה הנוכחית שלך, מה שידרוש ממך להיכנס חזרה. הפעלות פעילות במכשירים אחרים עלולות להישאר פעילות למשך עד שעה אחת." }, "updateWeakMasterPasswordWarning": { - "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." + "message": "הסיסמה הראשית שלך אינה עומדת באחת או יותר מפוליסות הארגון שלך. כדי לגשת לכספת, אתה מוכרח לעדכן את הסיסמה הראשית שלך עכשיו. בהמשך תנותק מההפעלה הנוכחית שלך, מה שידרוש ממך להיכנס חזרה. הפעלות פעילות במכשירים אחרים עלולות להישאר פעילות למשך עד שעה אחת." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "הארגון שלך השבית הצפנת מכשיר מהימן. נא להגדיר סיסמה ראשית כדי לגשת לכספת שלך." }, "resetPasswordPolicyAutoEnroll": { - "message": "Automatic enrollment" + "message": "רישום אוטומטי" }, "resetPasswordAutoEnrollInviteWarning": { - "message": "This organization has an enterprise policy that will automatically enroll you in password reset. Enrollment will allow organization administrators to change your master password." + "message": "לארגון זה יש מדיניות ארגונית שתרשום אותך באופן אוטומטי לאיפוס סיסמה. הרישום יאפשר למנהלי הארגון לשנות את הסיסמה הראשית שלך." }, "selectFolder": { - "message": "Select folder..." + "message": "בחר תיקייה..." }, "noFoldersFound": { - "message": "No folders found", + "message": "לא נמצאו תיקיות", "description": "Used as a message within the notification bar when no folders are found" }, "orgPermissionsUpdatedMustSetPassword": { @@ -2631,7 +2864,7 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "מתוך $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -2640,20 +2873,20 @@ } }, "verificationRequired": { - "message": "Verification required", + "message": "נדרש אימות", "description": "Default title for the user verification dialog." }, "hours": { - "message": "Hours" + "message": "שעות" }, "minutes": { - "message": "Minutes" + "message": "דקות" }, "vaultTimeoutPolicyAffectingOptions": { - "message": "Enterprise policy requirements have been applied to your timeout options" + "message": "דרישות מדיניות ארגונית הוחלו על אפשרויות פסק הזמן שלך" }, "vaultTimeoutPolicyInEffect": { - "message": "Your organization policies have set your maximum allowed vault timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "message": "פוליסות הארגון שלך הגדירו את פסק הזמן לכספת המרבי שלך ל־$HOURS$ שעות ו־$MINUTES$ דקות.", "placeholders": { "hours": { "content": "$1", @@ -2666,7 +2899,7 @@ } }, "vaultTimeoutPolicyInEffect1": { - "message": "$HOURS$ hour(s) and $MINUTES$ minute(s) maximum.", + "message": "$HOURS$ שעות ו־$MINUTES$ דקות לכל היותר.", "placeholders": { "hours": { "content": "$1", @@ -2679,7 +2912,7 @@ } }, "vaultTimeoutPolicyMaximumError": { - "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "message": "פסק זמן חורג את ההגבלה שהוגדרה על ידי הארגון שלך: $HOURS$ שעות ו־$MINUTES$ דקות לכל היותר", "placeholders": { "hours": { "content": "$1", @@ -2692,7 +2925,7 @@ } }, "vaultTimeoutPolicyWithActionInEffect": { - "message": "Your organization policies are affecting your vault timeout. Maximum allowed vault timeout is $HOURS$ hour(s) and $MINUTES$ minute(s). Your vault timeout action is set to $ACTION$.", + "message": "פוליסות הארגון שלך משפיעות על פסק הזמן לכספת שלך. פסק זמן מרבי המותר הוא $HOURS$ שעות ו־$MINUTES$ דקות. פעולת פסק הזמן לכספת שלך מוגדרת ל$ACTION$.", "placeholders": { "hours": { "content": "$1", @@ -2709,7 +2942,7 @@ } }, "vaultTimeoutActionPolicyInEffect": { - "message": "Your organization policies have set your vault timeout action to $ACTION$.", + "message": "פוליסות הארגון שלך הגדירו את פעולת פסק הזמן לכספת שלך ל$ACTION$.", "placeholders": { "action": { "content": "$1", @@ -2718,22 +2951,22 @@ } }, "vaultTimeoutTooLarge": { - "message": "הזמן הקצוב לכספת שלך חורג מהמגבלות שנקבעו על ידי הארגון שלך." + "message": "פסק הזמן לכספת שלך חורג מהמגבלות שנקבעו על ידי הארגון שלך." }, "vaultExportDisabled": { - "message": "Vault export unavailable" + "message": "ייצוא כספת לא זמין" }, "personalVaultExportPolicyInEffect": { - "message": "One or more organization policies prevents you from exporting your individual vault." + "message": "מדיניות ארגון אחת או יותר מונעת ממך מלייצא את הכספת האישית שלך." }, "copyCustomFieldNameInvalidElement": { - "message": "Unable to identify a valid form element. Try inspecting the HTML instead." + "message": "לא ניתן לזהות רכיב טופס חוקי. נסה לבדוק את ה־HTML במקום זאת." }, "copyCustomFieldNameNotUnique": { - "message": "No unique identifier found." + "message": "לא נמצא מזהה ייחודי." }, "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ משתמשים ב־SSO עם שרת מפתחות באירוח עצמי. סיסמה ראשית לא נחוצה יותר לטובת כניסה לחברי הארגון.", + "message": "$ORGANIZATION$ משתמש/ת ב־SSO עם שרת מפתחות באירוח עצמי. סיסמה ראשית כבר לא נדרשת כדי להיכנס עבור חברים של ארגון זה.", "placeholders": { "organization": { "content": "$1", @@ -2742,31 +2975,31 @@ } }, "leaveOrganization": { - "message": "לעזוב את הארגון" + "message": "עזוב ארגון" }, "removeMasterPassword": { - "message": "הסרת סיסמה ראשית" + "message": "הסר סיסמה ראשית" }, "removedMasterPassword": { - "message": "הסיסמה הראשית הוסרה." + "message": "הסיסמה הראשית הוסרה" }, "leaveOrganizationConfirmation": { - "message": "לעזוב את הארגון?" + "message": "האם אתה בטוח שברצונך לעזוב את הארגון הזה?" }, "leftOrganization": { "message": "עזבת את הארגון." }, "toggleCharacterCount": { - "message": "החלפת מצב ספירת תווים" + "message": "החלף מצב מונה תווים" }, "sessionTimeout": { - "message": "Your session has timed out. Please go back and try logging in again." + "message": "זמן ההפעלה שלך תם. נא לחזור ולנסות להיכנס שוב." }, "exportingPersonalVaultTitle": { - "message": "הכספת האישית מיוצאת" + "message": "מייצא כספת אישית" }, "exportingIndividualVaultDescription": { - "message": "Only the individual vault items associated with $EMAIL$ will be exported. Organization vault items will not be included. Only vault item information will be exported and will not include associated attachments.", + "message": "רק פריטי הכספת האישית המשויכת עם $EMAIL$ ייוצאו. פריטי כספת ארגון לא יכללו. רק פרטי פריט כספת ייוצאו ולא יכללו צרופות משויכות.", "placeholders": { "email": { "content": "$1", @@ -2775,10 +3008,10 @@ } }, "exportingOrganizationVaultTitle": { - "message": "Exporting organization vault" + "message": "מייצא כספת ארגון" }, "exportingOrganizationVaultDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "message": "רק כספת הארגון המשויכת עם $ORGANIZATION$ תיוצא. פריטים בכספת אישית או ארגונים אחרים לא יכללו.", "placeholders": { "organization": { "content": "$1", @@ -2789,14 +3022,28 @@ "error": { "message": "שגיאה" }, + "decryptionError": { + "message": "שגיאת פענוח" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden לא יכל לפענח את פריט(י) הכספת המפורט(ים) להלן." + }, + "contactCSToAvoidDataLossPart1": { + "message": "צור קשר עם הצלחת לקוחות", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "כדי למנוע אובדן נתונים נוסף.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { - "message": "Generate username" + "message": "צור שם משתמש" }, "generateEmail": { - "message": "Generate email" + "message": "צור דוא\"ל" }, "spinboxBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$.", + "message": "הערך חייב להיות בין $MIN$ ל־$MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2810,7 +3057,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "message": " השתמש ב־$RECOMMENDED$ תווים או יותר כדי ליצור סיסמה חזקה.", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2820,7 +3067,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "message": " השתמש ב־$RECOMMENDED$ מילים או יותר כדי ליצור ביטוי סיסמה חזק.", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2830,46 +3077,46 @@ } }, "plusAddressedEmail": { - "message": "Plus addressed email", + "message": "דוא\"ל ממוען בפלוס", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" }, "plusAddressedEmailDesc": { - "message": "Use your email provider's sub-addressing capabilities." + "message": "השתמש ביכולות מיעון משנה של ספק הדוא\"ל שלך." }, "catchallEmail": { - "message": "Catch-all email" + "message": "דוא\"ל תופס־כל" }, "catchallEmailDesc": { - "message": "Use your domain's configured catch-all inbox." + "message": "השתמש בתיבת דואר תפוס־כל המוגדרת בדומיין שלך." }, "random": { - "message": "Random" + "message": "אקראי" }, "randomWord": { - "message": "Random word" + "message": "מילה אקראית" }, "websiteName": { - "message": "Website name" + "message": "שם אתר" }, "service": { - "message": "Service" + "message": "שירות" }, "forwardedEmail": { - "message": "כתובת דוא״ל להעברה" + "message": "כינוי דוא\"ל מועבר" }, "forwardedEmailDesc": { - "message": "יצירת כינוי דוא״ל עם שירות העברה חיצוני." + "message": "צור כינוי דוא\"ל עם שירות העברה חיצוני." }, "forwarderDomainName": { - "message": "Email domain", + "message": "דומיין דוא\"ל", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Choose a domain that is supported by the selected service", + "message": "בחר דומיין שנתמך על ידי השירות הנבחר", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { - "message": "$SERVICENAME$ error: $ERRORMESSAGE$", + "message": "$SERVICENAME$ שגיאה: $ERRORMESSAGE$", "description": "Reports an error returned by a forwarding service to the user.", "placeholders": { "servicename": { @@ -2883,11 +3130,11 @@ } }, "forwarderGeneratedBy": { - "message": "Generated by Bitwarden.", + "message": "נוצר על ידי Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "אתר: $WEBSITE$. נוצר על ידי Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -2897,7 +3144,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "אסימון API של $SERVICENAME$ לא חוקי", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -2907,7 +3154,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "אסימון API של $SERVICENAME$ לא חוקי: $ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -2920,8 +3167,32 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ סירב לבקשה שלך. נא ליצור קשר עם נותן השירות שלך עבור סיוע.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ סירב לבקשה שלך: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { - "message": "Unable to obtain $SERVICENAME$ masked email account ID.", + "message": "לא ניתן להשיג מזהה חשבון דוא\"ל מסווה של $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -2931,7 +3202,7 @@ } }, "forwarderNoDomain": { - "message": "Invalid $SERVICENAME$ domain.", + "message": "דומיין $SERVICENAME$ לא חוקי.", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -2941,7 +3212,7 @@ } }, "forwarderNoUrl": { - "message": "Invalid $SERVICENAME$ url.", + "message": "כתובת url של $SERVICENAME$ לא חוקית.", "description": "Displayed when the url of the forwarding service wasn't supplied.", "placeholders": { "servicename": { @@ -2951,7 +3222,7 @@ } }, "forwarderUnknownError": { - "message": "Unknown $SERVICENAME$ error occurred.", + "message": "התרחשה שגיאת $SERVICENAME$ לא ידועה.", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -2961,7 +3232,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Unknown forwarder: '$SERVICENAME$'.", + "message": "משלח לא ידוע: '$SERVICENAME$'.", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -2975,25 +3246,25 @@ "description": "Part of a URL." }, "apiAccessToken": { - "message": "אסימון גישה ל־API" + "message": "אסימון גישת API" }, "apiKey": { "message": "מפתח API" }, "ssoKeyConnectorError": { - "message": "Key connector error: make sure key connector is available and working correctly." + "message": "שגיאת Key Connector: וודא שה־Key Connector זמין ופועל כראוי." }, "premiumSubcriptionRequired": { - "message": "Premium subscription required" + "message": "נדרש מנוי פרימיום" }, "organizationIsDisabled": { - "message": "Organization suspended." + "message": "ארגון מושעה." }, "disabledOrganizationFilterError": { - "message": "Items in suspended Organizations cannot be accessed. Contact your Organization owner for assistance." + "message": "לא ניתן לגשת לפריטים בארגון מושעה. פנה אל בעל הארגון שלך עבור סיוע." }, "loggingInTo": { - "message": "Logging in to $DOMAIN$", + "message": "נכנס אל $DOMAIN$", "placeholders": { "domain": { "content": "$1", @@ -3001,26 +3272,17 @@ } } }, - "settingsEdited": { - "message": "Settings have been edited" - }, - "environmentEditedClick": { - "message": "Click here" - }, - "environmentEditedReset": { - "message": "to reset to pre-configured settings" - }, "serverVersion": { - "message": "Server version" + "message": "גרסת שרת" }, "selfHostedServer": { - "message": "self-hosted" + "message": "אירוח עצמי" }, "thirdParty": { - "message": "Third-party" + "message": "צד שלישי" }, "thirdPartyServerMessage": { - "message": "Connected to third-party server implementation, $SERVERNAME$. Please verify bugs using the official server, or report them to the third-party server.", + "message": "מחובר ליישום שרת צד שלישי, $SERVERNAME$. בבקשה אמת באגים בעזרת השרת הרשמי, או דווח אותם לשרת הצד שלישי.", "placeholders": { "servername": { "content": "$1", @@ -3029,7 +3291,7 @@ } }, "lastSeenOn": { - "message": "last seen on: $DATE$", + "message": "נראה לאחרונה ב: $DATE$", "placeholders": { "date": { "content": "$1", @@ -3038,82 +3300,79 @@ } }, "loginWithMasterPassword": { - "message": "Log in with master password" - }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" + "message": "כניסה עם סיסמה ראשית" }, "newAroundHere": { - "message": "New around here?" + "message": "חדש כאן?" }, "rememberEmail": { - "message": "Remember email" + "message": "זכור דוא\"ל" }, "loginWithDevice": { - "message": "Log in with device" - }, - "loginWithDeviceEnabledInfo": { - "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" + "message": "כניסה עם מכשיר" }, "fingerprintPhraseHeader": { - "message": "Fingerprint phrase" + "message": "ביטוי טביעת אצבע" }, "fingerprintMatchInfo": { - "message": "Please make sure your vault is unlocked and the Fingerprint phrase matches on the other device." + "message": "נא לוודא שהכספת שלך פתוחה ושביטוי טביעת האצבע תואם את המכשיר האחר." }, "resendNotification": { - "message": "Resend notification" + "message": "שלח מחדש התראה" }, "viewAllLogInOptions": { - "message": "View all log in options" - }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" + "message": "הצג את כל אפשרויות הכניסה" }, "notificationSentDevice": { - "message": "A notification has been sent to your device." + "message": "התראה נשלחה למכשיר שלך." + }, + "notificationSentDevicePart1": { + "message": "בטל נעילת Bitwarden במכשיר שלך או ב" + }, + "notificationSentDeviceAnchor": { + "message": "יישום רשת" + }, + "notificationSentDevicePart2": { + "message": "וודא שביטוי טביעת האצבע תואם את זה שלמטה לפני שתאשר." }, "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" - }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "message": "התראה נשלחה למכשיר שלך" }, "youWillBeNotifiedOnceTheRequestIsApproved": { - "message": "You will be notified once the request is approved" + "message": "תקבל התראה כאשר הבקשה תאושר" }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "צריך אפשרות אחרת?" }, "loginInitiated": { - "message": "Login initiated" + "message": "הכניסה החלה" + }, + "logInRequestSent": { + "message": "בקשה נשלחה" }, "exposedMasterPassword": { - "message": "Exposed Master Password" + "message": "סיסמה ראשית חשופה" }, "exposedMasterPasswordDesc": { - "message": "Password found in a data breach. Use a unique password to protect your account. Are you sure you want to use an exposed password?" + "message": "הסיסמה נמצאה בפרצת נתונים. השתמש בסיסמה ייחודית כדי להגן על חשבונך. האם אתה בטוח שברצונך להשתמש בסיסמה חשופה?" }, "weakAndExposedMasterPassword": { - "message": "Weak and Exposed Master Password" + "message": "סיסמה ראשית חלשה וחשופה" }, "weakAndBreachedMasterPasswordDesc": { - "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" + "message": "סיסמה חלשה זוהתה ונמצאה בפרצת נתונים. השתמש בסיסמה חזקה וייחודית כדי להגן על חשבונך. האם אתה בטוח שאתה רוצה להשתמש בסיסמה זו?" }, "checkForBreaches": { - "message": "Check known data breaches for this password" + "message": "בדוק פרצות נתונים ידועות עבור סיסמה זו" }, "important": { - "message": "Important:" + "message": "חשוב:" }, "masterPasswordHint": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "הסיסמה הראשית שלך לא ניתנת לשחזור אם אתה שוכח אותה!" }, "characterMinimum": { - "message": "$LENGTH$ character minimum", + "message": "$LENGTH$ תווים לכל הפחות", "placeholders": { "length": { "content": "$1", @@ -3122,13 +3381,13 @@ } }, "autofillPageLoadPolicyActivated": { - "message": "Your organization policies have turned on autofill on page load." + "message": "פוליסות הארגון שלך הפעילו מילוי אוטומטי בעת טעינת עמוד." }, "howToAutofill": { - "message": "How to autofill" + "message": "כיצד למלא אוטומטית" }, "autofillSelectInfoWithCommand": { - "message": "Select an item from this screen, use the shortcut $COMMAND$, or explore other options in settings.", + "message": "בחר פריט ממסך זה, בחר את קיצור הדרך $COMMAND$, או חקור אפשרויות אחרות בהגדרות.", "placeholders": { "command": { "content": "$1", @@ -3137,31 +3396,31 @@ } }, "autofillSelectInfoWithoutCommand": { - "message": "Select an item from this screen, or explore other options in settings." + "message": "בחר פריט ממסך זה, או חקור אפשרויות אחרות בהגדרות." }, "gotIt": { - "message": "Got it" + "message": "הבנתי" }, "autofillSettings": { - "message": "Autofill settings" + "message": "הגדרות מילוי אוטומטי" }, "autofillKeyboardShortcutSectionTitle": { - "message": "Autofill shortcut" + "message": "קיצור מילוי אוטומטי" }, "autofillKeyboardShortcutUpdateLabel": { - "message": "Change shortcut" + "message": "שנה קיצור דרך" }, "autofillKeyboardManagerShortcutsLabel": { - "message": "Manage shortcuts" + "message": "נהל קיצורי דרך" }, "autofillShortcut": { - "message": "Autofill keyboard shortcut" + "message": "מקשי קיצור למילוי אוטומטי" }, "autofillLoginShortcutNotSet": { - "message": "The autofill login shortcut is not set. Change this in the browser's settings." + "message": "קיצור הדרך למילוי כניסה אוטומטי לא הוגדר. שנה זאת בהגדרות הדפדפן." }, "autofillLoginShortcutText": { - "message": "The autofill login shortcut is $COMMAND$. Manage all shortcuts in the browser's settings.", + "message": "קיצור הדרך למילוי כניסה אוטומטי הוא $COMMAND$. נהל את כל קיצורי הדרך בהדגרות הדפדפן.", "placeholders": { "command": { "content": "$1", @@ -3170,7 +3429,7 @@ } }, "autofillShortcutTextSafari": { - "message": "Default autofill shortcut: $COMMAND$.", + "message": "קיצור דרך למילוי אוטומטי ברירת מחדל: $COMMAND$.", "placeholders": { "command": { "content": "$1", @@ -3179,65 +3438,62 @@ } }, "opensInANewWindow": { - "message": "Opens in a new window" + "message": "נפתח בחלון חדש" }, "rememberThisDeviceToMakeFutureLoginsSeamless": { - "message": "Remember this device to make future logins seamless" + "message": "זכור מכשיר זה כדי להפוך כניסות עתידיות לחלקות" }, "deviceApprovalRequired": { - "message": "Device approval required. Select an approval option below:" + "message": "נדרש אישור מכשיר. בחר אפשרות אישור למטה:" }, "deviceApprovalRequiredV2": { - "message": "Device approval required" + "message": "נדרש אישור מכשיר" }, "selectAnApprovalOptionBelow": { - "message": "Select an approval option below" + "message": "בחר אפשרות אישור למטה" }, "rememberThisDevice": { - "message": "Remember this device" + "message": "זכור מכשיר זה" }, "uncheckIfPublicDevice": { - "message": "Uncheck if using a public device" + "message": "בטל את הסימון אם אתה משתמש במכשיר ציבורי" }, "approveFromYourOtherDevice": { - "message": "Approve from your other device" + "message": "אשר מהמכשיר האחר שלך" }, "requestAdminApproval": { - "message": "Request admin approval" - }, - "approveWithMasterPassword": { - "message": "Approve with master password" + "message": "בקש אישור מנהל" }, "ssoIdentifierRequired": { - "message": "Organization SSO identifier is required." + "message": "נדרש מזהה SSO של הארגון." }, "creatingAccountOn": { - "message": "Creating account on" + "message": "יוצר חשבון ב־" }, "checkYourEmail": { - "message": "Check your email" + "message": "בדוק את הדוא\"ל שלך" }, "followTheLinkInTheEmailSentTo": { - "message": "Follow the link in the email sent to" + "message": "עקוב אחר הקישור בדוא\"ל הנשלח אל" }, "andContinueCreatingYourAccount": { - "message": "and continue creating your account." + "message": "והמשך ליצור את החשבון שלך." }, "noEmail": { - "message": "No email?" + "message": "אין דוא\"ל?" }, "goBack": { - "message": "Go back" + "message": "חזור" }, "toEditYourEmailAddress": { - "message": "to edit your email address." + "message": "כדי לערוך את כתובת הדוא\"ל שלך." }, "eu": { - "message": "EU", + "message": "האיחוד האירופי", "description": "European Union" }, "accessDenied": { - "message": "Access denied. You do not have permission to view this page." + "message": "גישה נדחתה. אין לך הרשאות כדי לצפות בעמוד זה." }, "general": { "message": "כללי" @@ -3246,51 +3502,48 @@ "message": "תצוגה" }, "accountSuccessfullyCreated": { - "message": "Account successfully created!" + "message": "החשבון נוצר בהצלחה!" }, "adminApprovalRequested": { - "message": "Admin approval requested" + "message": "התבקש אישור מנהל" }, "adminApprovalRequestSentToAdmins": { - "message": "Your request has been sent to your admin." - }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." + "message": "הבקשה שלך נשלחה למנהל שלך." }, "troubleLoggingIn": { - "message": "Trouble logging in?" + "message": "בעיות בכניסה?" }, "loginApproved": { - "message": "Login approved" + "message": "כניסה אושרה" }, "userEmailMissing": { - "message": "User email missing" + "message": "חסר דוא\"ל משתמש" }, "activeUserEmailNotFoundLoggingYouOut": { - "message": "Active user email not found. Logging you out." + "message": "דוא\"ל משתמש פעיל לא נמצא. מוציא אותך." }, "deviceTrusted": { - "message": "Device trusted" + "message": "מכשיר מהימן" }, "sendsNoItemsTitle": { - "message": "No active Sends", + "message": "אין סֵנְדים פעילים", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "message": "השתמש בסֵנְד כדי לשתף באופן מאובטח מידע מוצפן עם כל אחד.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { - "message": "Input is required." + "message": "נדרש קלט." }, "required": { - "message": "required" + "message": "נדרש" }, "search": { - "message": "Search" + "message": "חיפוש" }, "inputMinLength": { - "message": "Input must be at least $COUNT$ characters long.", + "message": "אורך הקלט חייב להיות לפחות $COUNT$ תווים.", "placeholders": { "count": { "content": "$1", @@ -3299,7 +3552,7 @@ } }, "inputMaxLength": { - "message": "Input must not exceed $COUNT$ characters in length.", + "message": "אורך הקלט לא יעלה על $COUNT$ תווים.", "placeholders": { "count": { "content": "$1", @@ -3308,7 +3561,7 @@ } }, "inputForbiddenCharacters": { - "message": "The following characters are not allowed: $CHARACTERS$", + "message": "התווים הבאים אינם מותרים: $CHARACTERS$", "placeholders": { "characters": { "content": "$1", @@ -3317,7 +3570,7 @@ } }, "inputMinValue": { - "message": "Input value must be at least $MIN$.", + "message": "ערך הקלט חייב להיות לפחות $MIN$.", "placeholders": { "min": { "content": "$1", @@ -3326,7 +3579,7 @@ } }, "inputMaxValue": { - "message": "Input value must not exceed $MAX$.", + "message": "ערך הקלט לא יעלה על $MAX$.", "placeholders": { "max": { "content": "$1", @@ -3335,17 +3588,17 @@ } }, "multipleInputEmails": { - "message": "1 or more emails are invalid" + "message": "כתובת דוא\"ל 1 או יותר אינה חוקית" }, "inputTrimValidator": { - "message": "Input must not contain only whitespace.", + "message": "אסור שקלט יכיל רק רווח לבן.", "description": "Notification to inform the user that a form's input can't contain only whitespace." }, "inputEmail": { - "message": "Input is not an email address." + "message": "קלט הוא לא כתובת דוא\"ל." }, "fieldsNeedAttention": { - "message": "$COUNT$ field(s) above need your attention.", + "message": "$COUNT$ שדות למעלה צריכים את תשומת לבך.", "placeholders": { "count": { "content": "$1", @@ -3354,10 +3607,10 @@ } }, "singleFieldNeedsAttention": { - "message": "1 field needs your attention." + "message": "שדה 1 צריך את תשומת לבך." }, "multipleFieldsNeedAttention": { - "message": "$COUNT$ fields need your attention.", + "message": "$COUNT$ שדות צריכים את תשומת לבך.", "placeholders": { "count": { "content": "$1", @@ -3366,22 +3619,22 @@ } }, "selectPlaceholder": { - "message": "-- Select --" + "message": "-- בחר --" }, "multiSelectPlaceholder": { - "message": "-- Type to filter --" + "message": "-- הקלד כדי לסנן --" }, "multiSelectLoading": { - "message": "Retrieving options..." + "message": "מאחזר אפשרויות..." }, "multiSelectNotFound": { - "message": "No items found" + "message": "לא נמצאו פריטים" }, "multiSelectClearAll": { - "message": "Clear all" + "message": "נקה הכל" }, "plusNMore": { - "message": "+ $QUANTITY$ more", + "message": "+ עוד $QUANTITY$", "placeholders": { "quantity": { "content": "$1", @@ -3390,167 +3643,135 @@ } }, "submenu": { - "message": "Submenu" + "message": "תפריט משנה" }, "toggleCollapse": { - "message": "Toggle collapse", + "message": "שנה מצב כיווץ", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { - "message": "Alias domain" + "message": "דומיין כינוי" }, "passwordRepromptDisabledAutofillOnPageLoad": { - "message": "Items with master password re-prompt cannot be autofilled on page load. Autofill on page load turned off.", + "message": "פריטים עם בקשת סיסמה ראשית חוזרת לא ניתנים למילוי אוטומטי בעת טעינת עמוד. מילוי אוטומטי בעת טעינת נכבה.", "description": "Toast message for describing that master password re-prompt cannot be autofilled on page load." }, "autofillOnPageLoadSetToDefault": { - "message": "Autofill on page load set to use default setting.", + "message": "מילוי אוטומטי בעת טעינת הוגדר להשתמש בהגדרת ברירת מחדל.", "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "turnOffMasterPasswordPromptToEditField": { - "message": "Turn off master password re-prompt to edit this field", + "message": "כבה בקשת סיסמה ראשית חוזרת כדי לערוך שדה זה", "description": "Message appearing below the autofill on load message when master password reprompt is set for a vault item." }, "toggleSideNavigation": { - "message": "Toggle side navigation" + "message": "החלף מצב ניווט צדדי" }, "skipToContent": { - "message": "Skip to content" + "message": "דלג לתוכן" }, "bitwardenOverlayButton": { - "message": "Bitwarden autofill menu button", + "message": "לחצן תפריט מילוי אוטומטי של Bitwarden", "description": "Page title for the iframe containing the overlay button" }, "toggleBitwardenVaultOverlay": { - "message": "Toggle Bitwarden autofill menu", + "message": "החלף מצב תפריט מילוי אוטומטי של Bitwaden", "description": "Screen reader and tool tip label for the overlay button" }, "bitwardenVault": { - "message": "Bitwarden autofill menu", + "message": "תפריט מילוי אוטומטי של Bitwaden", "description": "Page title in overlay" }, "unlockYourAccountToViewMatchingLogins": { - "message": "Unlock your account to view matching logins", + "message": "בטל את נעילת החשבון שלך כדי להציג כניסות תואמות", "description": "Text to display in overlay when the account is locked." }, "unlockYourAccountToViewAutofillSuggestions": { - "message": "Unlock your account to view autofill suggestions", + "message": "בטל את נעילת החשבון שלך כדי להציג הצעות למילוי אוטומטי", "description": "Text to display in overlay when the account is locked." }, "unlockAccount": { - "message": "Unlock account", + "message": "ביטול נעילת חשבון", "description": "Button text to display in overlay when the account is locked." }, "unlockAccountAria": { - "message": "Unlock your account, opens in a new window", + "message": "בטל את נעילת החשבון שלך, נפתח בחלון חדש", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, "totpCodeAria": { - "message": "Time-based One-Time Password Verification Code", + "message": "קוד אימות סיסמה חד פעמית מבוססת זמן", "description": "Aria label for the totp code displayed in the inline menu for autofill" }, "totpSecondsSpanAria": { - "message": "Time remaining before current TOTP expires", + "message": "זמן נותר לפני שה־TOTP הנוכחי פג", "description": "Aria label for the totp seconds displayed in the inline menu for autofill" }, "fillCredentialsFor": { - "message": "Fill credentials for", + "message": "מלא אישורים עבור", "description": "Screen reader text for when overlay item is in focused" }, "partialUsername": { - "message": "Partial username", + "message": "שם משתמש חלקי", "description": "Screen reader text for when a login item is focused where a partial username is displayed. SR will announce this phrase before reading the text of the partial username" }, "noItemsToShow": { - "message": "No items to show", + "message": "אין פריטים להצגה", "description": "Text to show in overlay if there are no matching items" }, "newItem": { - "message": "New item", + "message": "פריט חדש", "description": "Button text to display in overlay when there are no matching items" }, "addNewVaultItem": { - "message": "Add new vault item", + "message": "הוסף פריט כספת חדש", "description": "Screen reader text (aria-label) for new item button in overlay" }, "newLogin": { - "message": "New login", + "message": "כניסה חדשה", "description": "Button text to display within inline menu when there are no matching items on a login field" }, "addNewLoginItemAria": { - "message": "Add new vault login item, opens in a new window", + "message": "הוסף לכספת פריט כניסה חדש, נפתח בחלון חדש", "description": "Screen reader text (aria-label) for new login button within inline menu" }, "newCard": { - "message": "New card", + "message": "כרטיס חדש", "description": "Button text to display within inline menu when there are no matching items on a credit card field" }, "addNewCardItemAria": { - "message": "Add new vault card item, opens in a new window", + "message": "הוסף לכספת פריט כרטיס חדש, נפתח בחלון חדש", "description": "Screen reader text (aria-label) for new card button within inline menu" }, "newIdentity": { - "message": "New identity", + "message": "זהות חדשה", "description": "Button text to display within inline menu when there are no matching items on an identity field" }, "addNewIdentityItemAria": { - "message": "Add new vault identity item, opens in a new window", + "message": "הוסף לכספת פריט זהות חדש, נפתח בחלון חדש", "description": "Screen reader text (aria-label) for new identity button within inline menu" }, "bitwardenOverlayMenuAvailable": { - "message": "Bitwarden autofill menu available. Press the down arrow key to select.", + "message": "תפריט מילוי אוטומטי של Bitwarden זמין. הקש על מקש החץ למטה כדי לבחור.", "description": "Screen reader text for announcing when the overlay opens on the page" }, "turnOn": { - "message": "Turn on" + "message": "הפעל" }, "ignore": { - "message": "Ignore" + "message": "התעלם" }, "importData": { - "message": "ייבוא נתונים", + "message": "ייבא נתונים", "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" }, "importError": { "message": "שגיאת ייבוא" }, "importErrorDesc": { - "message": "There was a problem with the data you tried to import. Please resolve the errors listed below in your source file and try again." + "message": "הייתה בעיה עם הנתונים שאתה מנסה לייבא. נא לפתור את השגיאות הרשומות למטה בקובץ המקור שלך ולנסות שוב." }, "resolveTheErrorsBelowAndTryAgain": { - "message": "Resolve the errors below and try again." + "message": "פתור את השגיאות למטה ונסה שוב." }, "description": { "message": "תיאור" @@ -3559,7 +3780,7 @@ "message": "הנתונים יובאו בהצלחה" }, "importSuccessNumberOfItems": { - "message": "A total of $AMOUNT$ items were imported.", + "message": "סך הכל יובאו $AMOUNT$ פריטים.", "placeholders": { "amount": { "content": "$1", @@ -3568,46 +3789,46 @@ } }, "tryAgain": { - "message": "Try again" + "message": "נסה שוב" }, "verificationRequiredForActionSetPinToContinue": { - "message": "Verification required for this action. Set a PIN to continue." + "message": "נדרש אימות עבור פעולה זו. הגדר PIN כדי להמשיך." }, "setPin": { - "message": "Set PIN" + "message": "הגדר PIN" }, "verifyWithBiometrics": { - "message": "Verify with biometrics" + "message": "אמת עם זיהוי ביומטרי" }, "awaitingConfirmation": { - "message": "Awaiting confirmation" + "message": "ממתין לאישור" }, "couldNotCompleteBiometrics": { - "message": "Could not complete biometrics." + "message": "לא היה ניתן להשלים את הזיהוי הביומטרי." }, "needADifferentMethod": { - "message": "Need a different method?" + "message": "זקוק לשיטה אחרת?" }, "useMasterPassword": { - "message": "Use master password" + "message": "השתמש בסיסמה ראשית" }, "usePin": { - "message": "Use PIN" + "message": "השתמש ב־PIN" }, "useBiometrics": { - "message": "Use biometrics" + "message": "השתמש בזיהוי ביומטרי" }, "enterVerificationCodeSentToEmail": { - "message": "Enter the verification code that was sent to your email." + "message": "הזן את קוד האימות שנשלח לדוא\"ל שלך." }, "resendCode": { - "message": "Resend code" + "message": "שלח קוד מחדש" }, "total": { - "message": "סך הכול" + "message": "סך הכל" }, "importWarning": { - "message": "You are importing data to $ORGANIZATION$. Your data may be shared with members of this organization. Do you want to proceed?", + "message": "אתה מייבא נתונים אל $ORGANIZATION$. ייתכן שהנתונים שלך ישותפו עם חברים של הארגון הזה. האם אתה רוצה להמשיך?", "placeholders": { "organization": { "content": "$1", @@ -3616,49 +3837,49 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + "message": "שגיאה בהתחברות עם השירות Duo. השתמש בשיטת כניסה דו־שלבית אחרת או פנה אל Duo לסיוע." }, "launchDuoAndFollowStepsToFinishLoggingIn": { - "message": "Launch Duo and follow the steps to finish logging in." + "message": "פתח את Duo ועקוב אחר השלבים כדי לסיים להיכנס." }, "duoRequiredForAccount": { - "message": "Duo two-step login is required for your account." + "message": "נדרשת כניסה דו־שלבית של Duo עבור החשבון שלך." }, "popoutTheExtensionToCompleteLogin": { - "message": "Popout the extension to complete login." + "message": "הקפץ את ההרחבה כדי להשלים כניסה." }, "popoutExtension": { - "message": "Popout extension" + "message": "הקפץ הרחבה" }, "launchDuo": { - "message": "Launch Duo" + "message": "פתח את Duo" }, "importFormatError": { - "message": "Data is not formatted correctly. Please check your import file and try again." + "message": "הנתונים אינם מעוצבים כראוי. נא לבדוק את קובץ הייבוא שלך ולנסות שוב." }, "importNothingError": { - "message": "Nothing was imported." + "message": "לא יובא דבר." }, "importEncKeyError": { - "message": "Error decrypting the exported file. Your encryption key does not match the encryption key used export the data." + "message": "שגיאה בפענוח הקובץ המיוצא. מפתח ההצפנה שלך אינו תואם את מפתח ההצפנה המשמש לייצוא הנתונים." }, "invalidFilePassword": { - "message": "Invalid file password, please use the password you entered when you created the export file." + "message": "סיסמת קובץ שגויה, נא להשתמש בסיסמה שהזנת כשיצרת את קובץ הייצוא." }, "destination": { - "message": "Destination" + "message": "יעד" }, "learnAboutImportOptions": { - "message": "Learn about your import options" + "message": "למד על אפשרויות הייבוא שלך" }, "selectImportFolder": { - "message": "Select a folder" + "message": "בחר תיקייה" }, "selectImportCollection": { - "message": "Select a collection" + "message": "בחר אוסף" }, "importTargetHint": { - "message": "Select this option if you want the imported file contents moved to a $DESTINATION$", + "message": "בחר באפשרות זו אם ברצונך להעביר את תוכן הקובץ המיובא אל $DESTINATION$", "description": "Located as a hint under the import target. Will be appended by either folder or collection, depending if the user is importing into an individual or an organizational vault.", "placeholders": { "destination": { @@ -3668,25 +3889,25 @@ } }, "importUnassignedItemsError": { - "message": "File contains unassigned items." + "message": "קובץ מכיל פריטים לא מוקצים." }, "selectFormat": { - "message": "Select the format of the import file" + "message": "בחר את הפורמט של קובץ הייבוא" }, "selectImportFile": { - "message": "Select the import file" + "message": "בחר את קובץ הייבוא" }, "chooseFile": { - "message": "Choose File" + "message": "בחר קובץ" }, "noFileChosen": { - "message": "No file chosen" + "message": "לא נבחר קובץ" }, "orCopyPasteFileContents": { - "message": "or copy/paste the import file contents" + "message": "או העתק/הדבק את תוכן קובץ הייבוא" }, "instructionsFor": { - "message": "$NAME$ Instructions", + "message": "הוראות עבור $NAME$", "description": "The title for the import tool instructions.", "placeholders": { "name": { @@ -3696,200 +3917,203 @@ } }, "confirmVaultImport": { - "message": "Confirm vault import" + "message": "אשר ייבוא כספת" }, "confirmVaultImportDesc": { - "message": "This file is password-protected. Please enter the file password to import data." + "message": "קובץ זה מוגן סיסמה. נא להזין את סיסמת הקובץ כדי לייבא נתונים." }, "confirmFilePassword": { - "message": "Confirm file password" + "message": "אשר סיסמת קובץ" }, "exportSuccess": { - "message": "Vault data exported" + "message": "נתוני הכספת יוצאו" }, "typePasskey": { - "message": "Passkey" + "message": "מפתח גישה" }, "accessing": { - "message": "Accessing" + "message": "ניגש אל" }, "loggedInExclamation": { - "message": "Logged in!" + "message": "נכנסת!" }, "passkeyNotCopied": { - "message": "Passkey will not be copied" + "message": "מפתח גישה לא יועתק" }, "passkeyNotCopiedAlert": { - "message": "The passkey will not be copied to the cloned item. Do you want to continue cloning this item?" + "message": "מפתח הגישה לא יועתק לפריט המשוכפל. האם ברצונך להמשיך לשכפל פריט זה?" }, "passkeyFeatureIsNotImplementedForAccountsWithoutMasterPassword": { - "message": "Verification required by the initiating site. This feature is not yet implemented for accounts without master password." + "message": "נדרש אימות על ידי האתר היוזם. תכונה זו עדיין לא מיושמת עבור חשבונות ללא סיסמה ראשית." }, "logInWithPasskeyQuestion": { - "message": "Log in with passkey?" + "message": "להיכנס עם מפתח גישה?" }, "passkeyAlreadyExists": { - "message": "A passkey already exists for this application." + "message": "מפתח גישה כבר קיים עבור יישום זה." }, "noPasskeysFoundForThisApplication": { - "message": "No passkeys found for this application." + "message": "לא נמצאו מפתחות גישה עבור יישום זה." }, "noMatchingPasskeyLogin": { - "message": "You do not have a matching login for this site." + "message": "אין לך כניסות תואמות עבור אתר זה." }, "noMatchingLoginsForSite": { - "message": "No matching logins for this site" + "message": "אין כניסות תואמות עבור אתר זה" }, "searchSavePasskeyNewLogin": { - "message": "Search or save passkey as new login" + "message": "חפש או שמור מפתח גישה ככניסה חדשה" }, "confirm": { - "message": "Confirm" + "message": "אשר" }, "savePasskey": { - "message": "Save passkey" + "message": "שמור מפתח גישה" }, "savePasskeyNewLogin": { - "message": "Save passkey as new login" + "message": "שמור מפתח גישה ככניסה חדשה" }, "chooseCipherForPasskeySave": { - "message": "Choose a login to save this passkey to" + "message": "בחר כניסה אליה יישמר מפתח גישה זה" }, "chooseCipherForPasskeyAuth": { - "message": "Choose a passkey to log in with" + "message": "בחר מפתח גישה כדי להיכנס בעזרתו" }, "passkeyItem": { - "message": "Passkey Item" + "message": "פריט מפתח גישה" }, "overwritePasskey": { - "message": "Overwrite passkey?" + "message": "לדרוס מפתח גישה?" }, "overwritePasskeyAlert": { - "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + "message": "פריט זה כבר מכיל מפתח גישה. האם אתה בטוח שברצונך לדרוס את מפתח הגישה הנוכחי?" }, "featureNotSupported": { - "message": "Feature not yet supported" + "message": "תכונה עדיין לא נתמכת" }, "yourPasskeyIsLocked": { - "message": "Authentication required to use passkey. Verify your identity to continue." + "message": "אימות נדרש כדי להשתמש במפתח גישה. אמת את זהותך כדי להמשיך." }, "multifactorAuthenticationCancelled": { - "message": "Multifactor authentication cancelled" + "message": "אימות רב־גורמי בוטל" }, "noLastPassDataFound": { - "message": "No LastPass data found" + "message": "לא נמצאו נתוני LastPass" }, "incorrectUsernameOrPassword": { - "message": "Incorrect username or password" + "message": "שם משתמש או סיסמה שגויים" }, "incorrectPassword": { - "message": "Incorrect password" + "message": "סיסמה שגויה" }, "incorrectCode": { - "message": "Incorrect code" + "message": "קוד שגוי" }, "incorrectPin": { - "message": "Incorrect PIN" + "message": "PIN שגוי" }, "multifactorAuthenticationFailed": { - "message": "Multifactor authentication failed" + "message": "אימות רב־גורמי נכשל" }, "includeSharedFolders": { - "message": "Include shared folders" + "message": "כלול תיקיות משותפות" }, "lastPassEmail": { - "message": "LastPass Email" + "message": "דוא\"ל LastPass" }, "importingYourAccount": { - "message": "Importing your account..." + "message": "מייבא את החשבון שלך..." }, "lastPassMFARequired": { - "message": "LastPass multifactor authentication required" + "message": "נדרש אימות רב־גורמי של LastPass" }, "lastPassMFADesc": { - "message": "Enter your one-time passcode from your authentication app" + "message": "הזן את קוד הגישה החד פעמי שלך מיישום האימות שלך" }, "lastPassOOBDesc": { - "message": "Approve the login request in your authentication app or enter a one-time passcode." + "message": "אשר את בקשת הכניסה ביישום האימות שלך או הזן קוד גישה חד פעמי." }, "passcode": { - "message": "Passcode" + "message": "קוד גישה" }, "lastPassMasterPassword": { - "message": "LastPass master password" + "message": "סיסמה ראשית של LastPass" }, "lastPassAuthRequired": { - "message": "LastPass authentication required" + "message": "נדרש אימות של LastPass" }, "awaitingSSO": { - "message": "Awaiting SSO authentication" + "message": "ממתין לאימות SSO" }, "awaitingSSODesc": { - "message": "Please continue to log in using your company credentials." + "message": "נא להמשיך להיכנס באמצעות אישורי החברה שלך." }, "seeDetailedInstructions": { - "message": "See detailed instructions on our help site at", + "message": "ראה הוראות מפורטות באתר העזרה שלנו ב־", "description": "This is followed a by a hyperlink to the help website." }, "importDirectlyFromLastPass": { - "message": "Import directly from LastPass" + "message": "ייבא ישירות מ־LastPass" }, "importFromCSV": { - "message": "Import from CSV" + "message": "ייבא מ־CSV" }, "lastPassTryAgainCheckEmail": { - "message": "Try again or look for an email from LastPass to verify it's you." + "message": "נסה שוב או חפש דוא\"ל מ־LastPass כדי לאמת שזה אתה." }, "collection": { - "message": "Collection" + "message": "אוסף" }, "lastPassYubikeyDesc": { - "message": "Insert the YubiKey associated with your LastPass account into your computer's USB port, then touch its button." + "message": "הכנס את ה־YubiKey המשויך עם חשבון ה־LastPass שלך אל תוך יציאת USB של המחשב שלך, לאחר מכן גע בכפתור שלו." }, "switchAccount": { - "message": "Switch account" + "message": "החלף חשבון" }, "switchAccounts": { - "message": "Switch accounts" + "message": "החלף חשבונות" }, "switchToAccount": { - "message": "Switch to account" + "message": "החלף לחשבון" }, "activeAccount": { - "message": "Active account" + "message": "חשבון פעיל" + }, + "bitwardenAccount": { + "message": "חשבון Bitwarden" }, "availableAccounts": { - "message": "Available accounts" + "message": "חשבונות זמינים" }, "accountLimitReached": { - "message": "Account limit reached. Log out of an account to add another." + "message": "הגעת למגבלת החשבונות. צא מחשבון כדי להוסיף אחד נוסף." }, "active": { - "message": "active" + "message": "פעיל" }, "locked": { - "message": "locked" + "message": "נעול" }, "unlocked": { - "message": "unlocked" + "message": "פתוח" }, "server": { - "message": "server" + "message": "שרת" }, "hostedAt": { - "message": "hosted at" + "message": "מאורח אצל" }, "useDeviceOrHardwareKey": { - "message": "Use your device or hardware key" + "message": "השתמש במכשיר שלך או במפתח חומרה" }, "justOnce": { - "message": "Just once" + "message": "רק פעם אחת" }, "alwaysForThisSite": { - "message": "Always for this site" + "message": "תמיד עבור אתר זה" }, "domainAddedToExcludedDomains": { - "message": "$DOMAIN$ added to excluded domains.", + "message": "$DOMAIN$ נוסף לרשימת הדומיינים המוחרגים.", "placeholders": { "domain": { "content": "$1", @@ -3898,103 +4122,106 @@ } }, "commonImportFormats": { - "message": "תסדירים נפוצים", + "message": "פורמטים נפוצים", "description": "Label indicating the most common import formats" }, "confirmContinueToBrowserSettingsTitle": { - "message": "Continue to browser settings?", + "message": "להמשיך אל הגדרות הדפדפן?", "description": "Title for dialog which asks if the user wants to proceed to a relevant browser settings page" }, "confirmContinueToHelpCenter": { - "message": "Continue to Help Center?", + "message": "להמשיך אל מרכז העזרה?", "description": "Title for dialog which asks if the user wants to proceed to a relevant Help Center page" }, "confirmContinueToHelpCenterPasswordManagementContent": { - "message": "Change your browser's autofill and password management settings.", + "message": "שנה את הגדרות ניהול סיסמאות ומילוי אוטומטי של הדפדפן שלך.", "description": "Body content for dialog which asks if the user wants to proceed to the Help Center's page about browser password management settings" }, "confirmContinueToHelpCenterKeyboardShortcutsContent": { - "message": "You can view and set extension shortcuts in your browser's settings.", + "message": "אתה יכול לראות ולהגדיר קיצורי דרך להרחבות בהגדרות הדפדפן שלך.", "description": "Body content for dialog which asks if the user wants to proceed to the Help Center's page about browser keyboard shortcut settings" }, "confirmContinueToBrowserPasswordManagementSettingsContent": { - "message": "Change your browser's autofill and password management settings.", + "message": "שנה את הגדרות ניהול סיסמאות ומילוי אוטומטי של הדפדפן שלך.", "description": "Body content for dialog which asks if the user wants to proceed to the browser's password management settings page" }, "confirmContinueToBrowserKeyboardShortcutSettingsContent": { - "message": "You can view and set extension shortcuts in your browser's settings.", + "message": "אתה יכול לראות ולהגדיר קיצורי דרך להרחבות בהגדרות הדפדפן שלך.", "description": "Body content for dialog which asks if the user wants to proceed to the browser's keyboard shortcut settings page" }, "overrideDefaultBrowserAutofillTitle": { - "message": "Make Bitwarden your default password manager?", + "message": "להפוך את Bitwarden למנהל הסיסמאות ברירת המחדל שלך?", "description": "Dialog title facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutofillDescription": { - "message": "Ignoring this option may cause conflicts between Bitwarden autofill suggestions and your browser's.", + "message": "התעלמות מאפשרות זו עלולה לגרום לקונפליקטים בין הצעות למילוי אוטומטי של Bitwarden לאלו של הדפדפן שלך.", "description": "Dialog message facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutoFillSettings": { - "message": "Make Bitwarden your default password manager", + "message": "הפוך את Bitwaren למנהל הסיסמאות ברירת המחדל שלך", "description": "Label for the setting that allows overriding the default browser autofill settings" }, "privacyPermissionAdditionNotGrantedTitle": { - "message": "Unable to set Bitwarden as the default password manager", + "message": "לא ניתן להגדיר את Bitwarden כמנהל הסיסמאות ברירת המחדל", "description": "Title for the dialog that appears when the user has not granted the extension permission to set privacy settings" }, "privacyPermissionAdditionNotGrantedDescription": { - "message": "You must grant browser privacy permissions to Bitwarden to set it as the default password manager.", + "message": "אתה מוכרח להעניק הרשאות פרטיות של דפדפן ל־Bitwarden כדי להגדיר אותו כמנהל הסיסמאות ברירת המחדל.", "description": "Description for the dialog that appears when the user has not granted the extension permission to set privacy settings" }, "makeDefault": { - "message": "Make default", + "message": "הפוך לברירת מחדל", "description": "Button text for the setting that allows overriding the default browser autofill settings" }, "saveCipherAttemptSuccess": { - "message": "Credentials saved successfully!", + "message": "האישורים נשמרו בהצלחה!", "description": "Notification message for when saving credentials has succeeded." }, "passwordSaved": { - "message": "Password saved!", + "message": "הסיסמה נשמרה!", "description": "Notification message for when saving credentials has succeeded." }, "updateCipherAttemptSuccess": { - "message": "Credentials updated successfully!", + "message": "האישורים עודכנו בהצלחה!", "description": "Notification message for when updating credentials has succeeded." }, "passwordUpdated": { - "message": "Password updated!", + "message": "הסיסמה עודכנה!", "description": "Notification message for when updating credentials has succeeded." }, "saveCipherAttemptFailed": { - "message": "Error saving credentials. Check console for details.", + "message": "שגיאה בשמירת אישורים. בדוק מסוף עבור פרטים.", "description": "Notification message for when saving credentials has failed." }, "success": { - "message": "Success" + "message": "הצלחה" }, "removePasskey": { - "message": "Remove passkey" + "message": "הסר מפתח גישה" }, "passkeyRemoved": { - "message": "Passkey removed" + "message": "מפתח גישה הוסר" }, "autofillSuggestions": { - "message": "Autofill suggestions" + "message": "הצעות למילוי אוטומטי" + }, + "itemSuggestions": { + "message": "פריטים מוצעים" }, "autofillSuggestionsTip": { - "message": "Save a login item for this site to autofill" + "message": "שמור פריט כניסה עבור אתר זה כדי למלא אוטומטית" }, "yourVaultIsEmpty": { - "message": "Your vault is empty" + "message": "הכספת שלך ריקה" }, "noItemsMatchSearch": { - "message": "No items match your search" + "message": "אין פריטים שתואמים את החיפוש שלך" }, "clearFiltersOrTryAnother": { - "message": "Clear filters or try another search term" + "message": "נקה מסננים או נסה ביטוי חיפוש אחר" }, "copyInfoTitle": { - "message": "Copy info - $ITEMNAME$", + "message": "העתק פרטים - $ITEMNAME$", "description": "Title for a button that opens a menu with options to copy information from an item.", "placeholders": { "itemname": { @@ -4004,7 +4231,7 @@ } }, "copyNoteTitle": { - "message": "Copy Note - $ITEMNAME$", + "message": "העתק פתק - $ITEMNAME$", "description": "Title for a button copies a note to the clipboard.", "placeholders": { "itemname": { @@ -4014,7 +4241,7 @@ } }, "moreOptionsLabel": { - "message": "More options, $ITEMNAME$", + "message": "עוד אפשרויות, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -4024,7 +4251,7 @@ } }, "moreOptionsTitle": { - "message": "More options - $ITEMNAME$", + "message": "עוד אפשרויות - $ITEMNAME$", "description": "Title for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -4034,7 +4261,7 @@ } }, "viewItemTitle": { - "message": "View item - $ITEMNAME$", + "message": "הצג פריט - $ITEMNAME$", "description": "Title for a link that opens a view for an item.", "placeholders": { "itemname": { @@ -4044,7 +4271,7 @@ } }, "autofillTitle": { - "message": "Autofill - $ITEMNAME$", + "message": "מילוי אוטומטי - $ITEMNAME$", "description": "Title for a button that autofills a login item.", "placeholders": { "itemname": { @@ -4053,41 +4280,55 @@ } } }, + "copyFieldValue": { + "message": "העתק $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { - "message": "No values to copy" + "message": "אין ערכים להעתקה" }, "assignToCollections": { - "message": "Assign to collections" + "message": "הקצה לאוספים" }, "copyEmail": { - "message": "Copy email" + "message": "העתק דוא\"ל" }, "copyPhone": { - "message": "Copy phone" + "message": "העתק טלפון" }, "copyAddress": { - "message": "Copy address" + "message": "העתק כתובת" }, "adminConsole": { - "message": "Admin Console" + "message": "מסוף מנהל" }, "accountSecurity": { - "message": "Account security" + "message": "אבטחת החשבון" }, "notifications": { - "message": "Notifications" + "message": "התראות" }, "appearance": { - "message": "Appearance" + "message": "מראה" }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "שגיאה בהקצאת אוסף יעד." }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "שגיאה בהקצאת תיקיית יעד." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "הצג פריטים ב־$NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -4097,7 +4338,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "חזרה אל $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -4107,10 +4348,10 @@ } }, "new": { - "message": "New" + "message": "חדש" }, "removeItem": { - "message": "Remove $NAME$", + "message": "הסר $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -4120,65 +4361,56 @@ } }, "itemsWithNoFolder": { - "message": "Items with no folder" + "message": "פריטים ללא תיקייה" }, "itemDetails": { - "message": "Item details" + "message": "פרטי הפריט" }, "itemName": { - "message": "Item name" - }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } + "message": "שם הפריט" }, "organizationIsDeactivated": { - "message": "Organization is deactivated" + "message": "הארגון מושבת" }, "owner": { - "message": "Owner" + "message": "בעלים" }, "selfOwnershipLabel": { - "message": "You", + "message": "את/ה", "description": "Used as a label to indicate that the user is the owner of an item." }, "contactYourOrgAdmin": { - "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." + "message": "לא ניתן לגשת לפריטים של ארגונים מושבתים. פנה לבעל הארגון שלך עבור סיוע." }, "additionalInformation": { - "message": "Additional information" + "message": "פרטים נוספים" }, "itemHistory": { - "message": "Item history" + "message": "היסטוריית פריט" }, "lastEdited": { - "message": "Last edited" + "message": "נערך לאחרונה" }, "ownerYou": { - "message": "Owner: You" + "message": "בעלים: את/ה" }, "linked": { - "message": "Linked" + "message": "מקושר" }, "copySuccessful": { - "message": "Copy Successful" + "message": "העתקה מוצלחת" }, "upload": { - "message": "Upload" + "message": "העלה" }, "addAttachment": { - "message": "Add attachment" + "message": "הוסף צרופה" }, "maxFileSizeSansPunctuation": { - "message": "Maximum file size is 500 MB" + "message": "גודל הקובץ המרבי הוא 500MB" }, "deleteAttachmentName": { - "message": "Delete attachment $NAME$", + "message": "מחק צרופה $NAME$", "placeholders": { "name": { "content": "$1", @@ -4187,7 +4419,7 @@ } }, "downloadAttachmentName": { - "message": "Download $NAME$", + "message": "הורד $NAME$", "placeholders": { "name": { "content": "$1", @@ -4196,25 +4428,25 @@ } }, "permanentlyDeleteAttachmentConfirmation": { - "message": "Are you sure you want to permanently delete this attachment?" + "message": "האם אתה בטוח שברצונך למחוק לצמיתות צרופה זו?" }, "premium": { - "message": "Premium" + "message": "פרימיום" }, "freeOrgsCannotUseAttachments": { - "message": "Free organizations cannot use attachments" + "message": "ארגונים חינמיים לא יכולים להשתמש בצרופות" }, "filters": { - "message": "Filters" + "message": "מסננים" }, "filterVault": { - "message": "Filter vault" + "message": "מסנן כספת" }, "filterApplied": { - "message": "One filter applied" + "message": "מסנן אחד הוחל" }, "filterAppliedPlural": { - "message": "$COUNT$ filters applied", + "message": "הוחלו $COUNT$ מסננים", "placeholders": { "count": { "content": "$1", @@ -4223,16 +4455,16 @@ } }, "personalDetails": { - "message": "Personal details" + "message": "פרטים אישיים" }, "identification": { - "message": "Identification" + "message": "הזדהות" }, "contactInfo": { - "message": "Contact info" + "message": "פרטי קשר" }, "downloadAttachment": { - "message": "Download - $ITEMNAME$", + "message": "הורד - $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -4241,23 +4473,23 @@ } }, "cardNumberEndsWith": { - "message": "card number ends with", + "message": "מספר הכרטיס מסתיים עם", "description": "Used within the inline menu to provide an aria description when users are attempting to fill a card cipher." }, "loginCredentials": { - "message": "Login credentials" + "message": "אישורי כניסה" }, "authenticatorKey": { - "message": "Authenticator key" + "message": "מפתח מאמת" }, "autofillOptions": { - "message": "Autofill options" + "message": "אפשרויות מילוי אוטומטי" }, "websiteUri": { - "message": "Website (URI)" + "message": "אתר אינטרנט (URI)" }, "websiteUriCount": { - "message": "Website (URI) $COUNT$", + "message": "אתר אינטרנט (URI) $COUNT$", "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", "placeholders": { "count": { @@ -4267,16 +4499,16 @@ } }, "websiteAdded": { - "message": "Website added" + "message": "אתר האינטרנט נוסף" }, "addWebsite": { - "message": "Add website" + "message": "הוסף אתר אינטרנט" }, "deleteWebsite": { - "message": "Delete website" + "message": "מחק אתר אינטרנט" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "ברירת מחדל ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -4286,7 +4518,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "הצג זיהוי התאמה $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -4295,7 +4527,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "הסתר זיהוי התאמה $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -4304,19 +4536,19 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "למלא אוטומטית בעת טעינת עמוד?" }, "cardExpiredTitle": { - "message": "Expired card" + "message": "כרטיס פג תוקף" }, "cardExpiredMessage": { - "message": "If you've renewed it, update the card's information" + "message": "אם חידשת אותו, עדכן את פרטי הכרטיס" }, "cardDetails": { - "message": "Card details" + "message": "פרטי כרטיס" }, "cardBrandDetails": { - "message": "$BRAND$ details", + "message": "פרטי $BRAND$", "placeholders": { "brand": { "content": "$1", @@ -4325,43 +4557,43 @@ } }, "enableAnimations": { - "message": "Enable animations" + "message": "אפשר הנפשות" }, "showAnimations": { - "message": "Show animations" + "message": "הצג הנפשות" }, "addAccount": { - "message": "Add account" + "message": "הוסף חשבון" }, "loading": { - "message": "Loading" + "message": "טוען" }, "data": { - "message": "Data" + "message": "נתונים" }, "passkeys": { - "message": "Passkeys", + "message": "מפתחות גישה", "description": "A section header for a list of passkeys." }, "passwords": { - "message": "Passwords", + "message": "סיסמאות", "description": "A section header for a list of passwords." }, "logInWithPasskeyAriaLabel": { - "message": "Log in with passkey", + "message": "כניסה עם מפתח גישה", "description": "ARIA label for the inline menu button that logs in with a passkey." }, "assign": { - "message": "Assign" + "message": "הקצה" }, "bulkCollectionAssignmentDialogDescriptionSingular": { - "message": "Only organization members with access to these collections will be able to see the item." + "message": "רק חברי ארגון עם גישה לאוספים אלה יוכלו לראות את הפריט." }, "bulkCollectionAssignmentDialogDescriptionPlural": { - "message": "Only organization members with access to these collections will be able to see the items." + "message": "רק חברי ארגון עם גישה לאוספים אלה יוכלו לראות את הפריטים." }, "bulkCollectionAssignmentWarning": { - "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "message": "בחרת ב־$TOTAL_COUNT$ פריטים. אינך יכול לעדכן $READONLY_COUNT$ מהפריטים בגלל שאין לך הרשאות עריכה.", "placeholders": { "total_count": { "content": "$1", @@ -4373,37 +4605,37 @@ } }, "addField": { - "message": "Add field" + "message": "הוסף שדה" }, "add": { - "message": "Add" + "message": "הוסף" }, "fieldType": { - "message": "Field type" + "message": "סוג שדה" }, "fieldLabel": { - "message": "Field label" + "message": "תווית שדה" }, "textHelpText": { - "message": "Use text fields for data like security questions" + "message": "השתמש בשדות טקסט עבור נתונים כמו שאלות אבטחה" }, "hiddenHelpText": { - "message": "Use hidden fields for sensitive data like a password" + "message": "השתמש בשדות נסתרים עבור מידע רגיש כמו סיסמה" }, "checkBoxHelpText": { - "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + "message": "השתמש בתיבות סימון אם תרצה למלא אוטומטית תיבת סימון של טופס, כמו זכור דוא\"ל" }, "linkedHelpText": { - "message": "Use a linked field when you are experiencing autofill issues for a specific website." + "message": "השתמש בשדה מקושר כאשר אתה חווה בעיות מילוי אוטומטי עם אתר מסוים." }, "linkedLabelHelpText": { - "message": "Enter the the field's html id, name, aria-label, or placeholder." + "message": "הזן את מזהה ה־html, שם, תווית aria או מציין מיקום." }, "editField": { - "message": "Edit field" + "message": "ערוך שדה" }, "editFieldLabel": { - "message": "Edit $LABEL$", + "message": "ערוך $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4412,7 +4644,7 @@ } }, "deleteCustomField": { - "message": "Delete $LABEL$", + "message": "מחק $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4421,7 +4653,7 @@ } }, "fieldAdded": { - "message": "$LABEL$ added", + "message": "$LABEL$ נוסף", "placeholders": { "label": { "content": "$1", @@ -4430,7 +4662,7 @@ } }, "reorderToggleButton": { - "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "message": "סדר מחדש את $LABEL$. השתמש במקש חץ כדי להעביר את הפריט למעלה או למטה.", "placeholders": { "label": { "content": "$1", @@ -4438,8 +4670,11 @@ } } }, + "reorderWebsiteUriButton": { + "message": "סדר מחדש כתובת URI של אתר. השתמש במקש חץ כדי להעביר את הפריט למעלה או למטה." + }, "reorderFieldUp": { - "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "message": "$LABEL$ עבר למעלה, מיקום $INDEX$ מתוך $LENGTH$", "placeholders": { "label": { "content": "$1", @@ -4456,13 +4691,13 @@ } }, "selectCollectionsToAssign": { - "message": "Select collections to assign" + "message": "בחר אוספים להקצות" }, "personalItemTransferWarningSingular": { - "message": "1 item will be permanently transferred to the selected organization. You will no longer own this item." + "message": "פריט 1 יועבר לצמיתות לארגון הנבחר. לא תהיה יותר הבעלים של הפריט הזה." }, "personalItemsTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to the selected organization. You will no longer own these items.", + "message": "$PERSONAL_ITEMS_COUNT$ פריטים יועברו לצמיתות לארגון הנבחר. לא תהיה יותר הבעלים של הפריטים האלה.", "placeholders": { "personal_items_count": { "content": "$1", @@ -4471,7 +4706,7 @@ } }, "personalItemWithOrgTransferWarningSingular": { - "message": "1 item will be permanently transferred to $ORG$. You will no longer own this item.", + "message": "פריט 1 יועבר לצמיתות אל $ORG$. לא תהיה יותר הבעלים של הפריט הזה.", "placeholders": { "org": { "content": "$1", @@ -4480,7 +4715,7 @@ } }, "personalItemsWithOrgTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to $ORG$. You will no longer own these items.", + "message": "$PERSONAL_ITEMS_COUNT$ פריטים יועברו לצמיתות אל $ORG$. לא תהיה יותר הבעלים של הפריטים האלה.", "placeholders": { "personal_items_count": { "content": "$1", @@ -4493,22 +4728,13 @@ } }, "successfullyAssignedCollections": { - "message": "Successfully assigned collections" + "message": "אוספים הוקצו בהצלחה" }, "nothingSelected": { - "message": "You have not selected anything." - }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } + "message": "לא בחרת כלום." }, "itemsMovedToOrg": { - "message": "Items moved to $ORGNAME$", + "message": "פריטים הועברו אל $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -4517,7 +4743,7 @@ } }, "itemMovedToOrg": { - "message": "Item moved to $ORGNAME$", + "message": "פריט הועבר אל $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -4526,7 +4752,7 @@ } }, "reorderFieldDown": { - "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "message": "$LABEL$ עבר למטה, מיקום $INDEX$ מתוך $LENGTH$", "placeholders": { "label": { "content": "$1", @@ -4543,283 +4769,304 @@ } }, "itemLocation": { - "message": "Item Location" + "message": "מיקום פריט" }, "fileSend": { - "message": "File Send" + "message": "סֵנְד של קובץ" }, "fileSends": { - "message": "File Sends" + "message": "סֵנְדים של קובץ" }, "textSend": { - "message": "Text Send" + "message": "סֵנְד של טקסט" }, "textSends": { - "message": "Text Sends" - }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" + "message": "סֵנְדים של טקסט" }, "accountActions": { - "message": "Account actions" + "message": "פעולות חשבון" }, "showNumberOfAutofillSuggestions": { - "message": "Show number of login autofill suggestions on extension icon" + "message": "הצג את מספר ההצעות למילוי כניסה אוטומטי בסמל ההרחבה" }, "showQuickCopyActions": { - "message": "Show quick copy actions on Vault" + "message": "הצג פעולות העתקה מהירה בכספת" }, "systemDefault": { - "message": "System default" + "message": "ברירת מחדל של המערכת" }, "enterprisePolicyRequirementsApplied": { - "message": "Enterprise policy requirements have been applied to this setting" + "message": "דרישות מדיניות ארגונית הוחלו על הגדרה זו" }, "sshPrivateKey": { - "message": "Private key" + "message": "מפתח פרטי" }, "sshPublicKey": { - "message": "Public key" + "message": "מפתח ציבורי" }, "sshFingerprint": { - "message": "Fingerprint" + "message": "טביעת אצבע" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "סוג מפתח" }, "sshKeyAlgorithmED25519": { "message": "ED25519" }, "sshKeyAlgorithmRSA2048": { - "message": "RSA 2048-Bit" + "message": "RSA‏ 2048 סיביות" }, "sshKeyAlgorithmRSA3072": { - "message": "RSA 3072-Bit" + "message": "RSA‏ 3072 סיביות" }, "sshKeyAlgorithmRSA4096": { - "message": "RSA 4096-Bit" + "message": "RSA‏ 4096 סיביות" }, "retry": { - "message": "Retry" + "message": "נסה שוב" }, "vaultCustomTimeoutMinimum": { - "message": "Minimum custom timeout is 1 minute." + "message": "פסק זמן מותאם אישית מינימלי הוא דקה 1." }, "additionalContentAvailable": { - "message": "Additional content is available" + "message": "תוכן נוסף זמין" }, "fileSavedToDevice": { - "message": "File saved to device. Manage from your device downloads." + "message": "הקובץ נשמר למכשיר. נהל מהורדות המכשיר שלך." }, "showCharacterCount": { - "message": "Show character count" + "message": "הצג מונה תווים" }, "hideCharacterCount": { - "message": "Hide character count" + "message": "הסתר מונה תווים" }, "itemsInTrash": { - "message": "Items in trash" + "message": "פריטים באשפה" }, "noItemsInTrash": { - "message": "No items in trash" + "message": "אין פריטים באשפה" }, "noItemsInTrashDesc": { - "message": "Items you delete will appear here and be permanently deleted after 30 days" + "message": "פריטים שאתה מוחק יופיעו כאן ויימחקו לצמיתות לאחר 30 יום" }, "trashWarning": { - "message": "Items that have been in trash more than 30 days will automatically be deleted" + "message": "פריטים שהיו באשפה יותר מ־30 יום יימחקו באופן אוטומטי" }, "restore": { - "message": "Restore" + "message": "שחזר" }, "deleteForever": { - "message": "Delete forever" + "message": "מחק לנצח" }, "noEditPermissions": { - "message": "You don't have permission to edit this item" + "message": "אין לך הרשאות לערוך פריט זה" + }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "ביטול נעילה ביומטרי אינו זמין בגלל שביטול נעילה עם PIN או סיסמה נדרש תחילה." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "ביטול נעילה ביומטרי אינו זמין כעת." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "ביטול נעילה ביומטרי אינו זמין בשל קבצי מערכת המוגדרים באופן שגוי." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "ביטול נעילה ביומטרי אינו זמין בשל קבצי מערכת המוגדרים באופן שגוי." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "ביטול נעילה ביומטרי אינו זמין בגלל שיישום שולחן העבודה של Bitwarden סגור." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "ביטול נעילה ביומטרי אינו זמין בגלל שהוא לא מופעל עבור $EMAIL$ ביישום שולחן העבודה של Bitwarden.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "ביטול נעילה ביומטרי אינו זמין כעת מסיבה לא ידועה." }, "authenticating": { - "message": "Authenticating" + "message": "מאמת" }, "fillGeneratedPassword": { - "message": "Fill generated password", + "message": "מלא סיסמה שנוצרה", "description": "Heading for the password generator within the inline menu" }, "passwordRegenerated": { - "message": "Password regenerated", + "message": "סיסמה נוצרה מחדש", "description": "Notification message for when a password has been regenerated" }, "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "message": "לשמור כניסה ב־Bitwarden?", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { - "message": "Space", + "message": "רווח", "description": "Represents the space key in screen reader content as a readable word" }, "tildeCharacterDescriptor": { - "message": "Tilde", + "message": "טילדה", "description": "Represents the ~ key in screen reader content as a readable word" }, "backtickCharacterDescriptor": { - "message": "Backtick", + "message": "בקטיק", "description": "Represents the ` key in screen reader content as a readable word" }, "exclamationCharacterDescriptor": { - "message": "Exclamation mark", + "message": "סימן קריאה", "description": "Represents the ! key in screen reader content as a readable word" }, "atSignCharacterDescriptor": { - "message": "At sign", + "message": "כרוכית", "description": "Represents the @ key in screen reader content as a readable word" }, "hashSignCharacterDescriptor": { - "message": "Hash sign", + "message": "סולמית", "description": "Represents the # key in screen reader content as a readable word" }, "dollarSignCharacterDescriptor": { - "message": "Dollar sign", + "message": "סימן הדולר", "description": "Represents the $ key in screen reader content as a readable word" }, "percentSignCharacterDescriptor": { - "message": "Percent sign", + "message": "סימן אחוז", "description": "Represents the % key in screen reader content as a readable word" }, "caretCharacterDescriptor": { - "message": "Caret", + "message": "קרט", "description": "Represents the ^ key in screen reader content as a readable word" }, "ampersandCharacterDescriptor": { - "message": "Ampersand", + "message": "אמפרסֵנְד", "description": "Represents the & key in screen reader content as a readable word" }, "asteriskCharacterDescriptor": { - "message": "Asterisk", + "message": "כוכבית", "description": "Represents the * key in screen reader content as a readable word" }, "parenLeftCharacterDescriptor": { - "message": "Left parenthesis", + "message": "סוגר שמאלי", "description": "Represents the ( key in screen reader content as a readable word" }, "parenRightCharacterDescriptor": { - "message": "Right parenthesis", + "message": "סוגר ימני", "description": "Represents the ) key in screen reader content as a readable word" }, "hyphenCharacterDescriptor": { - "message": "Underscore", + "message": "קו תחתון", "description": "Represents the _ key in screen reader content as a readable word" }, "underscoreCharacterDescriptor": { - "message": "Hyphen", + "message": "מקף", "description": "Represents the - key in screen reader content as a readable word" }, "plusCharacterDescriptor": { - "message": "Plus", + "message": "פלוס", "description": "Represents the + key in screen reader content as a readable word" }, "equalsCharacterDescriptor": { - "message": "Equals", + "message": "שוויון", "description": "Represents the = key in screen reader content as a readable word" }, "braceLeftCharacterDescriptor": { - "message": "Left brace", + "message": "סוגר מסולסל שמאלי", "description": "Represents the { key in screen reader content as a readable word" }, "braceRightCharacterDescriptor": { - "message": "Right brace", + "message": "סוגר מסולסל ימני", "description": "Represents the } key in screen reader content as a readable word" }, "bracketLeftCharacterDescriptor": { - "message": "Left bracket", + "message": "סוגר מרובע שמאלי", "description": "Represents the [ key in screen reader content as a readable word" }, "bracketRightCharacterDescriptor": { - "message": "Right bracket", + "message": "סוגר מרובע ימני", "description": "Represents the ] key in screen reader content as a readable word" }, "pipeCharacterDescriptor": { - "message": "Pipe", + "message": "צינור", "description": "Represents the | key in screen reader content as a readable word" }, "backSlashCharacterDescriptor": { - "message": "Back slash", + "message": "לוכסן שמאלי", "description": "Represents the back slash key in screen reader content as a readable word" }, "colonCharacterDescriptor": { - "message": "Colon", + "message": "נקודתיים", "description": "Represents the : key in screen reader content as a readable word" }, "semicolonCharacterDescriptor": { - "message": "Semicolon", + "message": "נקודה ופסיק", "description": "Represents the ; key in screen reader content as a readable word" }, "doubleQuoteCharacterDescriptor": { - "message": "Double quote", + "message": "מירכאה כפולה", "description": "Represents the double quote key in screen reader content as a readable word" }, "singleQuoteCharacterDescriptor": { - "message": "Single quote", + "message": "מירכאה בודדת", "description": "Represents the ' key in screen reader content as a readable word" }, "lessThanCharacterDescriptor": { - "message": "Less than", + "message": "קטן מ־", "description": "Represents the < key in screen reader content as a readable word" }, "greaterThanCharacterDescriptor": { - "message": "Greater than", + "message": "גדול מ־", "description": "Represents the > key in screen reader content as a readable word" }, "commaCharacterDescriptor": { - "message": "Comma", + "message": "פסיק", "description": "Represents the , key in screen reader content as a readable word" }, "periodCharacterDescriptor": { - "message": "Period", + "message": "נקודה", "description": "Represents the . key in screen reader content as a readable word" }, "questionCharacterDescriptor": { - "message": "Question mark", + "message": "סימן שאלה", "description": "Represents the ? key in screen reader content as a readable word" }, "forwardSlashCharacterDescriptor": { - "message": "Forward slash", + "message": "לוכסן", "description": "Represents the / key in screen reader content as a readable word" }, "lowercaseAriaLabel": { - "message": "Lowercase" + "message": "אות קטנה" }, "uppercaseAriaLabel": { - "message": "Uppercase" + "message": "אות גדולה" }, "generatedPassword": { - "message": "Generated password" + "message": "נוצרה סיסמה" }, "compactMode": { - "message": "Compact mode" + "message": "מצב קומפקטי" }, "beta": { - "message": "Beta" + "message": "בטא" }, "importantNotice": { - "message": "Important notice" + "message": "הודעה חשובה" }, "setupTwoStepLogin": { - "message": "Set up two-step login" + "message": "הגדר כניסה דו־שלבית" }, "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + "message": "Bitwarden ישלח קוד לדוא\"ל החשבון שלך כדי לאמת כניסות ממכשירים חדשים החל מפברואר 2025." }, "newDeviceVerificationNoticeContentPage2": { - "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + "message": "אתה יכול להגדיר כניסה דו־שלבית כדרך חלופית להגן על החשבון שלך או לשנות את הדוא\"ל שלך לאחד שאתה יכול לגשת אליו." }, "remindMeLater": { - "message": "Remind me later" + "message": "הזכר לי מאוחר יותר" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", + "message": "יש לך גישה מהימנה לדוא\"ל שלך, $EMAIL$?", "placeholders": { "email": { "content": "$1", @@ -4828,24 +5075,69 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "לא, אין לי" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" + "message": "כן, אני יכול לגשת לדוא\"ל שלי באופן מהימן" }, "turnOnTwoStepLogin": { - "message": "Turn on two-step login" + "message": "הפעל כניסה דו־שלבית" }, "changeAcctEmail": { - "message": "Change account email" + "message": "שנה את דוא\"ל החשבון" }, "extensionWidth": { - "message": "Extension width" + "message": "רוחב הרחבה" }, "wide": { - "message": "Wide" + "message": "רחב" }, "extraWide": { - "message": "Extra wide" + "message": "רחב במיוחד" + }, + "sshKeyWrongPassword": { + "message": "הסיסמה שהזנת שגויה." + }, + "importSshKey": { + "message": "ייבוא" + }, + "confirmSshKeyPassword": { + "message": "אשר סיסמה" + }, + "enterSshKeyPasswordDesc": { + "message": "הזן את הסיסמה עבור מפתח ה־SSH." + }, + "enterSshKeyPassword": { + "message": "הזן סיסמה" + }, + "invalidSshKey": { + "message": "מפתח ה־SSH אינו תקין" + }, + "sshKeyTypeUnsupported": { + "message": "סוג מפתח ה־SSH אינו נתמך" + }, + "importSshKeyFromClipboard": { + "message": "ייבא מפתח מלוח ההעתקה" + }, + "sshKeyImported": { + "message": "מפתח SSH יובא בהצלחה" + }, + "cannotRemoveViewOnlyCollections": { + "message": "אינך יכול להסיר אוספים עם הרשאות צפייה בלבד: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "נא לעדכן את יישום שולחן העבודה שלך" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "כדי להשתמש בביטול נעילה ביומטרי, נא לעדכן את יישום שולחן העבודה שלך, להשבית ביטול נעילה בעזרת טביעת אצבע בהגדרות שולחן העבודה." + }, + "changeAtRiskPassword": { + "message": "שנה סיסמה בסיכון" } } diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index 10ad502be82..748d9eb966b 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Master Password Hint (optional)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Copy notes" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { "message": "Fill", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password Hint" - }, - "enterEmailToGetHint": { - "message": "अपने मास्टर पासवर्ड संकेत प्राप्त करने के लिए अपने खाते का ईमेल पता दर्ज करें।" - }, "getMasterPasswordHint": { "message": "मास्टर पासवर्ड संकेत प्राप्त करें" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Edit Folder" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "New folder" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Generate passphrase" }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "regeneratePassword": { "message": "Regenerate Password" }, @@ -454,22 +482,6 @@ "length": { "message": "लंबाई" }, - "uppercase": { - "message": "बड़े अक्षर (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "छोटे अक्षर (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "संख्या (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "विशेष अक्षर (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Include", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Number of Words" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "आपका वेब ब्राउज़र आसान क्लिपबोर्ड कॉपीिंग का समर्थन नहीं करता है। इसके बजाय इसे मैन्युअल रूप से कॉपी करें।" }, - "verifyIdentity": { - "message": "पहचान सत्यापित करें" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "आपकी वॉल्ट लॉक हो गई है। जारी रखने के लिए अपने मास्टर पासवर्ड को सत्यापित करें।" @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "आपके ईमेल पर भेजा गया कोड भरें" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "अपने प्रमाणक ऐप से कोड डालें" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Restart registration" }, @@ -875,6 +904,9 @@ "no": { "message": "नहीं" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "An unexpected error has occured." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "टैब पेज पर कार्ड दिखाएं" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "आसान ऑटो-फिल के लिए टैब पेज पर कार्ड आइटम सूचीबद्ध करें।" }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "टैब पेज पर पहचान दिखाएं" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "क्लिपबोर्ड खाली करें", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Yes, Save Now" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ को बिटवार्डन में सहेजा गया।", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ को बिटवार्डन में अपडेट किया गया।", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "मौजूदा लॉगिन को अपडेट करने के लिए कहें" }, @@ -1084,10 +1169,6 @@ "message": "प्रकाश", "description": "Light color" }, - "solarizedDark": { - "message": "सौरीकृत अंधेरा", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Export from" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Purchase Premium" }, - "premiumPurchaseAlert": { - "message": "आप bitwarden.com वेब वॉल्ट पर प्रीमियम सदस्यता खरीद सकते हैं।क्या आप अब वेबसाइट पर जाना चाहते हैं?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "मुझे याद रखें" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "फिर से सत्यापन कोड ईमेल भेजें" }, "useAnotherTwoStepMethod": { "message": "एक और दो-चरण लॉगिन विधि का उपयोग करें" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "अपने कंप्यूटर के यूएसबी पोर्ट में अपने YubiKey डालें, फिर इसके बटन को स्पर्श करें।" }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "नया टैब खोलें" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "वेबऑथन प्रमाणित करें" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "Login Unavailable" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Two-step Login Options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "अपने दो कारक प्रदाताओं के सभी के लिए उपयोग खो दिया है? अपने खाते से सभी दो-कारक प्रदाताओं को अक्षम करने के लिए अपने रिकवरी कोड का उपयोग करें।" }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Self-hosted Environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premise hosted bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Custom Environment" }, - "customEnvironmentFooter": { - "message": "उन्नत उपयोगकर्ताओं के लिए। आप स्वतंत्र रूप से प्रत्येक सेवा का आधार URL निर्दिष्ट कर सकते हैं।" - }, "baseUrl": { "message": "सर्वर URL" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "सॉर्ट करने के लिए ड्रैग करें" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "शब्द" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Username generator" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "Use this password" }, @@ -2063,12 +2163,24 @@ "message": "to create a strong unique password", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "वॉल्ट मध्यांतर कार्रवाई" }, "vaultTimeoutAction1": { "message": "टाइमआउट कार्रवाई" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "लॉक", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "बहिष्कृत डोमेन" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +3022,20 @@ "error": { "message": "एरर" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "उपयोगकर्ता नाम बनाएँ" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Settings have been edited" - }, - "environmentEditedClick": { - "message": "यहाँ क्लिक करें" - }, - "environmentEditedReset": { - "message": "to reset to pre-configured settings" - }, "serverVersion": { "message": "Server version" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Log in with device" }, - "loginWithDeviceEnabledInfo": { - "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" - }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "View all log in options" }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" - }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "You will be notified once the request is approved" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3396,38 +3649,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "डोमेन उपनाम" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Active account" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Available accounts" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "No values to copy" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organization is deactivated" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "You have not selected anything." }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "बिटवार्डन का नया रूप!" - }, - "bitwardenNewLookDesc": { - "message": "वॉल्ट टैब से ऑटोफिल और सर्च करना पहले से कहीं ज़्यादा आसान और सहज है। सबकुछ ध्यान से देखें!" - }, "accountActions": { "message": "Account actions" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra wide" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index c93922cb913..b88fc45493f 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Podsjetnik glavne lozinke (neobavezno)" }, + "passwordStrengthScore": { + "message": "Ocjena jačine lozinke: $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Pridruži se organizaciji" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Kopiraj bilješke" }, + "copy": { + "message": "Kopiraj", + "description": "Copy to clipboard" + }, "fill": { "message": "Ispuni", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Unesi svoju adresu e-pošte računa i poslat ćemo ti tvoj podsjetnik" }, - "passwordHint": { - "message": "Podsjetnik za lozinku" - }, - "enterEmailToGetHint": { - "message": "Unesi adresu e-pošte svog računa za primitak podsjetnika glavne lozinke." - }, "getMasterPasswordHint": { "message": "Slanje podsjetnika glavne lozinke" }, @@ -334,7 +341,7 @@ "message": "Bitwarden autentifikator" }, "continueToAuthenticatorPageDesc": { - "message": "Bitwarden autentifikator omogućuje pohranu ključeva za autentifikaciju i generiranje TOTP kodova za dvostruku autentifikaciju. Saznaj više na web stranici bitwarden.com" + "message": "Bitwarden autentifikator omogućuje pohranu autentifikatorskih ključeva i generiranje TOTP kodova za dvostruku autentifikaciju. Saznaj više na web stranici bitwarden.com" }, "bitwardenSecretsManager": { "message": "Bitwarden Secrets Manager" @@ -372,6 +379,15 @@ "editFolder": { "message": "Uredi mapu" }, + "editFolderWithName": { + "message": "Uredi mapu: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "Nova mapa" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Generiraj frazu lozinke" }, + "passwordGenerated": { + "message": "Lozinka generirana" + }, + "passphraseGenerated": { + "message": "Frazna lozinka generirana" + }, + "usernameGenerated": { + "message": "Korisničko ime generirano" + }, + "emailGenerated": { + "message": "e-pošta generirana" + }, "regeneratePassword": { "message": "Ponovno generiraj lozinku" }, @@ -454,22 +482,6 @@ "length": { "message": "Duljina" }, - "uppercase": { - "message": "Velika slova (A - Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Mala slova (a - z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Brojevi (0 - 9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Posebni znakovi (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Uključi", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Uključi posebne znakove", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Broj riječi" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Web preglednik ne podržava jednostavno kopiranje međuspremnika. Umjesto toga ručno kopirajte." }, - "verifyIdentity": { - "message": "Potvrdi identitet" + "verifyYourIdentity": { + "message": "Potvrdi svoj identitet" + }, + "weDontRecognizeThisDevice": { + "message": "Ne prepoznajemo ovaj uređaj. Za potvrdu identiteta unesi kôd poslan e-poštom." + }, + "continueLoggingIn": { + "message": "Nastavi prijavu" }, "yourVaultIsLocked": { "message": "Tvoj trezor je zaključan. Potvrdi glavnu lozinku za nastavak." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Prijavi se u Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Unesi kôd poslan e-poštom" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Unesi kôd iz svoje aplikacije za autentifikaciju" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Za autentifikaciju dodirni svoj YubiKey" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Za tvoj je račun potrebna Duo prijava u dva koraka. Za dovršetak prijave, slijedi daljnje korake." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Prati korake za dovršetak prijave." + }, "restartRegistration": { "message": "Ponovno pokreni registraciju" }, @@ -875,6 +904,9 @@ "no": { "message": "Ne" }, + "location": { + "message": "Lokacija" + }, "unexpectedError": { "message": "Došlo je do neočekivane pogreške." }, @@ -885,7 +917,7 @@ "message": "Mapa dodana" }, "twoStepLoginConfirmation": { - "message": "Prijava dvostrukom autentifikacijom čini tvoj račun još sigurnijim tako što će zahtijevati da potvrdiš prijavu putem drugog uređaja pomoću sigurnosnog koda, autentifikatorske aplikacije, SMS-om, pozivom ili e-poštom. Prijavu dvostrukom autentifikacijom možeš omogućiti na web trezoru. Želiš li sada posjetiti bitwarden.com?" + "message": "Prijava dvostrukom autentifikacijom čini tvoj račun još sigurnijim tako što će zahtijevati potvrdu prijave drugim uređajem kao što je sigurnosni ključ, autentifikatorska aplikacija, SMS, poziv ili e-pošta. Prijavu dvostrukom autentifikacijom možeš omogućiti na web trezoru. Želiš li sada posjetiti bitwarden.com?" }, "twoStepLoginConfirmationContent": { "message": "Učini svoj račun sigurnijim uključivanjem prijave dvofaktorskom autentifikacijom u Bitwarden web aplikaciji." @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Pitaj za dodavanje stavke ako nije pronađena u tvojem trezoru. Primjenjuje se na sve prijavljene račune." }, - "showCardsInVaultView": { - "message": "Prikaži kartice kao prijedloge za auto-ispunu u prikazu trezora" + "showCardsInVaultViewV2": { + "message": "Uvijek prikaži kartice kao prijedloge za auto-ispunu u prikazu trezora" }, "showCardsCurrentTab": { "message": "Prikaži platne kartice" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Prikazuj platne kartice za jednostavnu auto-ispunu." }, - "showIdentitiesInVaultView": { - "message": "Prikaži identitete kao prijedloge za auto-ispunu u prikazu trezora" + "showIdentitiesInVaultViewV2": { + "message": "Uvijek prikaži identitete kao prijedloge za auto-ispunu u prikazu trezora" }, "showIdentitiesCurrentTab": { "message": "Prikaži identitete" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Klikni stavke za auto-ispunu na prikazu trezora" }, + "clickToAutofill": { + "message": "Kliknite stavku u prijedlogu auto-ispune za popunjavanje" + }, "clearClipboard": { "message": "Očisti međuspremnik", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Spremi" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ spremljeno u Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ ažurirano u Bitwardenu.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Spremi novu prijavu", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Ažuriraj prijavu", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Spremiti prijavu?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Ažurirati postojeću prijavu?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Prijava spremljena", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Prijava ažurirana", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Greška kod spremanja", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Ups! Nismo mogli ovo spasiti. Pokušaj ručno unijeti detalje.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Upitaj za ažuriranje trenutne prijave" }, @@ -1084,10 +1169,6 @@ "message": "Svijetla", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized Dark", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Izvezi iz" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Kupi premium članstvo" }, - "premiumPurchaseAlert": { - "message": "Možeš kupiti premium članstvo na web trezoru. Želiš li sada posjetiti bitwarden.com?" - }, "premiumPurchaseAlertV2": { "message": "Premium možeš kupiti u postavkama računa na Bitwarden web aplikaciji." }, @@ -1302,7 +1380,7 @@ "message": "Automatski kopiraj TOTP" }, "disableAutoTotpCopyDesc": { - "message": "Ako za prijavu postoji autentifikatorski ključ, kopiraj TOTP kôd za provjeru u međuspremnik nakon auto-ispune prijave." + "message": "Ako za prijavu postoji ključ autentifikatora, kopiraj TOTP kôd za provjeru u međuspremnik nakon auto-ispune prijave." }, "enableAutoBiometricsPrompt": { "message": "Traži biometrijsku autentifikaciju pri pokretanju" @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Zapamti me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Ne pitaj na ovom uređaju idućih 30 dana" + }, "sendVerificationCodeEmailAgain": { "message": "Ponovno slanje kontrolnog koda e-poštom" }, "useAnotherTwoStepMethod": { "message": "Koristiti drugi način prijave dvostrukom autentifikacijom" }, + "selectAnotherMethod": { + "message": "Odaberi drugi način", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Koristi kôd za oporavak" + }, "insertYubiKey": { "message": "Umetni svoj YubiKey u USB priključak računala, a zatim dodirni njegovu tipku." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Otvori novu karticu" }, + "openInNewTab": { + "message": "Otvori u novoj kartici" + }, "webAuthnAuthenticate": { "message": "Ovjeri WebAuthn" }, + "readSecurityKey": { + "message": "Pročitaj sigurnosni ključ" + }, + "awaitingSecurityKeyInteraction": { + "message": "Čekanje na interakciju sa sigurnosnim ključem..." + }, "loginUnavailable": { "message": "Prijava nije dostupna" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Mogućnosti prijave dvostrukom autentifikacijom" }, + "selectTwoStepLoginMethod": { + "message": "Odaberi način prijave dvostrukom autentifikacijom" + }, "recoveryCodeDesc": { "message": "Izgubljen je pristup uređaju za dvostruku autentifikaciju? Koristi svoj kôd za oporavak za onemogućavanje svih pružatelja usluga dvostruke autentifikacije na tvojem računu." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Vlastito hosting okruženje" }, - "selfHostedEnvironmentFooter": { - "message": "Navedi osnovni URL svoje lokalno smještene Bitwarden instalacije." - }, "selfHostedBaseUrlHint": { "message": "Navedi osnovni URL svoje lokalne Bitwarden instalacije, npr.: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Prilagođeno okruženje" }, - "customEnvironmentFooter": { - "message": "Za napredne korisnike. Samostalno možeš odrediti osnovni URL svake usluge." - }, "baseUrl": { "message": "URL poslužitelja" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Povuci za sortiranje" }, + "dragToReorder": { + "message": "Povuci za premještanje" + }, "cfTypeText": { "message": "Tekst" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Generator korisničkih imena" }, + "useThisEmail": { + "message": "Koristi ovu e-poštu" + }, "useThisPassword": { "message": "Koristi ovu lozinku" }, @@ -2063,12 +2163,24 @@ "message": "za stvaranje snažne, jedinstvene lozinke", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Prilagodba trezora" + }, "vaultTimeoutAction": { "message": "Nakon isteka trezora" }, "vaultTimeoutAction1": { "message": "Radnja nakon isteka " }, + "newCustomizationOptionsCalloutTitle": { + "message": "Nove mogućnosti prilagodbe" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Prilagodi svoje iskustvo trezora brzim kopiranjem, kompaktnim načinom rada i više!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "Pogledaj sve postavke izgleda" + }, "lock": { "message": "Zaključaj", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domene", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blokirane domene" + }, + "learnMoreAboutBlockedDomains": { + "message": "Saznaj više o blokiranim domenama" + }, "excludedDomains": { "message": "Izuzete domene" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden neće nuditi spremanje podataka za prijavu za ove domene za sve prijavljene račune. Moraš osvježiti stranicu kako bi promjene stupile na snagu." }, + "blockedDomainsDesc": { + "message": "Auto-ispuna i druge vezane značajke neće biti ponuđene za ova web mjesta. Potrebno je osvježiti stranicu zaprimjenu postavki." + }, + "autofillBlockedNoticeV2": { + "message": "Auto-ispuna je blokirana za ovu web stranicu." + }, + "autofillBlockedNoticeGuidance": { + "message": "Promijeni ovo u postavkama" + }, + "change": { + "message": "Promijeni" + }, + "changeButtonTitle": { + "message": "Promijeni lozinku - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "Rizične lozinke" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ traži da promijeniš jednu rizičnu lozinku.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "Broj rizičnih lozinki koje $ORGANIZATION$ traži da promijeniš: $COUNT$.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Broj rizičnih lozinki koje tvoja orgnaizacija traži da promijeniš: $COUNT$.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Pregledaj i promijeni jednu rizičnu lozinku" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Broj rizičnih lozinki za pregled i promjenu: $COUNT$", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Brže promijeni rizične lozinke" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Ažuriraj svoje postavke kako za brzu auto-ispunu svojih lozinki i generiranje novih" + }, + "reviewAtRiskLogins": { + "message": "Pregledaj rizične prijave" + }, + "reviewAtRiskPasswords": { + "message": "Pregdledaj rizične lozinke" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Lozinke tvoje organizacije su rizične jer su slabe, nanovo korištene i/ili iscurile.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Ilustracija liste rizičnih prijava" + }, + "generatePasswordSlideDesc": { + "message": "Brzo generiraj jake, jedinstvene lozinke koristeći Bitwarden dijalog auto-ispune direktno na stranici.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Ilustracija Bitwarden dijalog auto-ispune s prikazom generirane lozinke" + }, + "updateInBitwarden": { + "message": "Ažuriraj u Bitwardenu" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden će te pitati treba li ažurirati lozinku u upravitelju lozinki.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Ilustracija Bitwarden upita za ažuriranje prijave" + }, + "turnOnAutofill": { + "message": "Uključi auto-ispunu" + }, + "turnedOnAutofill": { + "message": "Auto-ispuna uključena" + }, + "dismiss": { + "message": "Odbaci" + }, "websiteItemLabel": { "message": "Web stranica $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Spremljene promjene blokiranih domena" + }, "excludedDomainsSavedSuccess": { "message": "Spremljene promjene izuzete domene" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Pogreška" }, + "decryptionError": { + "message": "Pogreška pri dešifriranju" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nije mogao dešifrirati sljedeće stavke trezora." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktiraj službu za korisnike", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "kako bi izbjegli gubitak podataka.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generiraj korisničko ime" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ je odbio tvoj zahtjev. Obrati se svom pružatelju usluga za pomoć.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ je odbio tvoj zahtjev: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Nije moguće dobiti $SERVICENAME$ maskirani ID računa e-pošte.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Postavke su izmijenjene" - }, - "environmentEditedClick": { - "message": "Klikni ovdje" - }, - "environmentEditedReset": { - "message": "za ponovno postavljanje zadanih postavki" - }, "serverVersion": { "message": "Verzija poslužitelja" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Prijava glavnom lozinkom" }, - "loggingInAs": { - "message": "Prijava kao" - }, - "notYou": { - "message": "Nisi ti?" - }, "newAroundHere": { "message": "Novi korisnik?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Prijava uređajem" }, - "loginWithDeviceEnabledInfo": { - "message": "Prijava uređajem mora biti namještena u postavka Bitwarden mobilne aplikacije. Trebaš drugu opciju?" - }, "fingerprintPhraseHeader": { "message": "Jedinstvena fraza" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "Pogledaj sve mogućnosti prijave" }, - "viewAllLoginOptionsV1": { - "message": "Pogledaj sve mogućnosti prijave" - }, "notificationSentDevice": { "message": "Obavijest je poslana na tvoj uređaj." }, + "notificationSentDevicePart1": { + "message": "Otključaj Bitwarden na svojem uređaju ili na" + }, + "notificationSentDeviceAnchor": { + "message": "web trezoru" + }, + "notificationSentDevicePart2": { + "message": "Provjeri slaže li se jedinstvena fraza s ovdje prikazanom prije odobravanja." + }, "aNotificationWasSentToYourDevice": { "message": "Obavijest je poslana na tvoj uređaj" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Provjeri je li trezor otključan i slaže li se jedinstvena fraza s drugim uređajem" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "Dobiti ćeš obavijest kada je tvoj zahtjev odobren" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Prijava pokrenuta" }, + "logInRequestSent": { + "message": "Zahtjev poslan" + }, "exposedMasterPassword": { "message": "Ukradena glavna lozinka" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Zatraži odobrenje administratora" }, - "approveWithMasterPassword": { - "message": "Odobri glavnom lozinkom" - }, "ssoIdentifierRequired": { "message": "Potreban je identifikator organizacije." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Tvoj zahtjev je poslan administratoru." }, - "youWillBeNotifiedOnceApproved": { - "message": "Dobiti ćeš obavijest kada bude odobreno." - }, "troubleLoggingIn": { "message": "Problem s prijavom?" }, @@ -3396,38 +3649,6 @@ "message": "Sažmi/Proširi", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Uvezi svoje podatke u Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Zaštititi svoje LastPass podatke i uvezi ih u Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Spremi kao nekriptiranu datoteku", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Uvezi u Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Uvoz...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Uvoz podataka u trezor uspješan!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Greška pri uvozu. Provjeri konzolu za detalje.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Došlo je do mrežne greške tijekom uvoza.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domene" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Aktivni račun" }, + "bitwardenAccount": { + "message": "Bitwarden račun" + }, "availableAccounts": { "message": "Dostupni računi" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Prijedlozi auto-ispune" }, + "itemSuggestions": { + "message": "Predložene stavke" + }, "autofillSuggestionsTip": { "message": "Spremi u auto-ispunu stavku prijave za ovu stranicu" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Kopiraj $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "Nema vrijednosti za kopiranje" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Naziv stavke" }, - "cannotRemoveViewOnlyCollections": { - "message": "S dopuštenjima samo za prikaz ne možeš ukloniti zbirke: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organizacija je deaktivirana" }, @@ -4248,7 +4480,7 @@ "message": "Vjerodajnice za prijavu" }, "authenticatorKey": { - "message": "Kôd za provjeru" + "message": "Ključ autentifikatora" }, "autofillOptions": { "message": "Postavke auto-ispune" @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Ponovno poredaj URI. Koristi tipke sa strelicom za pomicanje stavke gore ili dolje." + }, "reorderFieldUp": { "message": "$LABEL$ pomaknut gore, pozicija $INDEX$ od $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "Ništa nije odabrano." }, - "movedItemsToOrg": { - "message": "Odabrane stavke premještene u $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Stavke premještene u $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Send tekstovi" }, - "bitwardenNewLook": { - "message": "Bitwarden ima novi izgled!" - }, - "bitwardenNewLookDesc": { - "message": "Auto-ispuna i pretraga iz kartice Trezor je lakša i intuitivnija nego ikad prije. Razgledaj!" - }, "accountActions": { "message": "Radnje na računu" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "Nemaš prava za uređivanje ove stavke" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrijsko otključavanje nije dostupno jer je prvo potrebno otključati PIN-om ili lozinkom." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrijsko otključavanje trenutno nije dostupno." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrijsko otključavanje nije dostupno zbog pogrešno konfiguriranih sistemskih datoteka." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrijsko otključavanje nije dostupno zbog pogrešno konfiguriranih sistemskih datoteka." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometrijsko otključavanje nije dostupno jer je Bitwarden dekstop aplikacija zatvorena." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometrijsko otključavanje nije dostupno jer nije omogućeno za $EMAIL$ u Bitwarden desktop aplikaciji.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrijsko otključavanje trenutno nije dostupno iz nepoznatog razloga." + }, "authenticating": { "message": "Autentifikacija" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Ekstra široko" + }, + "sshKeyWrongPassword": { + "message": "Unesena lozinka nije ispravna." + }, + "importSshKey": { + "message": "Uvoz" + }, + "confirmSshKeyPassword": { + "message": "Potvrdi lozinku" + }, + "enterSshKeyPasswordDesc": { + "message": "Unesi lozinku za SSH ključ." + }, + "enterSshKeyPassword": { + "message": "Unesi lozinku" + }, + "invalidSshKey": { + "message": "SSH ključ nije valjan" + }, + "sshKeyTypeUnsupported": { + "message": "Tip SSH ključa nije podržan" + }, + "importSshKeyFromClipboard": { + "message": "Uvezi ključ iz međuspremnika" + }, + "sshKeyImported": { + "message": "SSH ključ uspješno uvezen" + }, + "cannotRemoveViewOnlyCollections": { + "message": "S dopuštenjima samo za prikaz ne možeš ukloniti zbirke: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Molimo, ažuriraj svoju desktop aplikaciju" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "Za korištenje biometrijskog otključavanja ažuriraj desktop aplikaciju ili nemogući otključavanje otiskom prsta u desktop aplikaciji." + }, + "changeAtRiskPassword": { + "message": "Promijeni rizičnu lozinku" } } diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index 8b8c9722909..80b1dbf095d 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Mesterjelszó emlékeztető (nem kötelező)" }, + "passwordStrengthScore": { + "message": "A jelszó erősségi pontszáma $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Csatlakozás szervezethez" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Jegyzet másolása" }, + "copy": { + "message": "Másolás", + "description": "Copy to clipboard" + }, "fill": { "message": "Kitöltés", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Adjuk meg fiók email címét és elküldésre kerül a jelszóra vonatkozó tipp." }, - "passwordHint": { - "message": "Jelszó emlékeztető" - }, - "enterEmailToGetHint": { - "message": "Írd be a felhasználóhoz kötött e-mail címed, hogy megkapd a mesterjelszó emlékeztetőt." - }, "getMasterPasswordHint": { "message": "Kérj mesterjelszó emlékeztetőt" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Mappa szerkesztése" }, + "editFolderWithName": { + "message": "Mappa szerkesztése: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "Új mappa" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Jelmondat generálás" }, + "passwordGenerated": { + "message": "A jelszó generálásra került." + }, + "passphraseGenerated": { + "message": "A jelmondat generálásra került." + }, + "usernameGenerated": { + "message": "A felhasználónév generálásra került." + }, + "emailGenerated": { + "message": "Az email generálásra került." + }, "regeneratePassword": { "message": "Jelszó újragenerálása" }, @@ -454,22 +482,6 @@ "length": { "message": "Hossz" }, - "uppercase": { - "message": "Nagybetűs (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Kisbetűs (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Számok (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Speciális karakterek (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Bevonás", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Speciális karakterek bevonása", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Szavak száma" }, @@ -644,9 +652,15 @@ "browserNotSupportClipboard": { "message": "A webböngésződ nem támogat könnyű vágólap másolást. Másold manuálisan inkább." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Személyazonosság ellenőrzése" }, + "weDontRecognizeThisDevice": { + "message": "Nem ismerhető fel ez az eszköz. Írjuk be az email címünkre küldött kódot a személyazonosság igazolásához." + }, + "continueLoggingIn": { + "message": "A bejelentkezés folytatása" + }, "yourVaultIsLocked": { "message": "A széf zárolásra került. A folytatáshoz meg kell adni a mesterjelszót." }, @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Bejelentkezés a Bitwardenbe" }, + "enterTheCodeSentToYourEmail": { + "message": "Adjuk meg az email címre elküldött kódot." + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Adjuk meg a hitelesítő alkalmazása által generált kódot." + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Nyomjuk meg a YubiKey-t a hitelesítéshez." + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo kétlépcsős bejelentkezés szükséges a fiókhoz. Kövessük az alábbi lépéseket a bejelentkezés befejezéséhez." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Kövessük az alábbi lépéseket a bejelentkezés befejezéséhez." + }, "restartRegistration": { "message": "Restart registration" }, @@ -875,6 +904,9 @@ "no": { "message": "Nem" }, + "location": { + "message": "Hely" + }, "unexpectedError": { "message": "Váratlan hiba történt." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Egy elem hozzáadásának kérése, ha az nem található a széfben. Minden bejelentkezett fiókra vonatkozik." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Mindig jelenítse meg a kártyákat automatikus kitöltési javaslatként a Széf nézetben" }, "showCardsCurrentTab": { "message": "Kártyák megjelenítése a Fül oldalon" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Kártyaelemek listázása a Fül oldalon a könnyű automatikus kitöltéshez." }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Mindig jelenítse meg a személyazonosságokat automatikus kitöltési javaslatként a Széf nézetben" }, "showIdentitiesCurrentTab": { "message": "Azonosítások megjelenítése a Fül oldalon" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Kattintsunk az elemekre az automatikus kitöltéshez a Széf nézetben" }, + "clickToAutofill": { + "message": "A kitöltéshez kattintsunk az automatikus kitöltési javaslat elemeire." + }, "clearClipboard": { "message": "Vágólap ürítése", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Mentés" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ mentésre került a Bitwardenben.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ frissítésre került a Bitwardenben.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Mentés új bejelentkezésként", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Bejelentkezés frissítése", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Bejelentkezés mentése?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Létező bejelentkezés frissítése?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "A bejelentkezés mentésre került.", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "A bejelentkezés frissítésre került.", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Hiba történt a bejelentkezés mentésekor.", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Létező bejelentkezés frissítés kérése" }, @@ -1084,10 +1169,6 @@ "message": "Világos", "description": "Light color" }, - "solarizedDark": { - "message": "Szolarizált sötét", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Exportálás innen:" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Prémium funkció megvásárlása" }, - "premiumPurchaseAlert": { - "message": "A prémium tagság megvásárolható a bitwarden.com webes széfben. Szeretnénk felkeresni a webhelyet most?" - }, "premiumPurchaseAlertV2": { "message": "Prémium szolgáltatást vásárolhatunk a Bitwarden webalkalmazás fiókbeállításai között." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Emlékezz rám" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Ne kérdezzen újra ezen az eszközön 30 napig" + }, "sendVerificationCodeEmailAgain": { "message": "Megerősítő kód e-mail újra küldése" }, "useAnotherTwoStepMethod": { "message": "Más két lépcsős bejelentkezés használata" }, + "selectAnotherMethod": { + "message": "Másik módszer választás", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Helyreállító kód használata" + }, "insertYubiKey": { "message": "Illeszd be a YubiKey-t a számítógéped egyik USB portjába, majd nyomd meg a gombját." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Új fül megnyitása" }, + "openInNewTab": { + "message": "Megnyitás új fülön" + }, "webAuthnAuthenticate": { "message": "WebAutn hitelesítés" }, + "readSecurityKey": { + "message": "Biztonsági kulcs olvasása" + }, + "awaitingSecurityKeyInteraction": { + "message": "Várakozás a biztonsági kulcs interakciójára..." + }, "loginUnavailable": { "message": "A bejelentkezés nem érhető el." }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Kétlépcsős bejelentkezés opciók" }, + "selectTwoStepLoginMethod": { + "message": "Kétlépcsős bejelentkezési mód használata" + }, "recoveryCodeDesc": { "message": "Elveszett a hozzáférés az összes kétlépcsős szolgáltatóhoz? A helyreállító kód használatával letilthatók fiókból a kétlépcsős szolgáltatók." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Saját üzemeltetésű környezet" }, - "selfHostedEnvironmentFooter": { - "message": "A helyileg működtetett Bitwarden telepítés alap webcímének megadása." - }, "selfHostedBaseUrlHint": { "message": "Adjuk meg a helyileg tárolt Bitwarden telepítés alap webcímét. Példa: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Egyedi környezet" }, - "customEnvironmentFooter": { - "message": "Haladó felhasználóknak. Minden egyes szolgáltatás alap URL-jét külön megadhatod." - }, "baseUrl": { "message": "Szerver URL" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Húzás a rendezéshez" }, + "dragToReorder": { + "message": "Átrendezés áthúzással" + }, "cfTypeText": { "message": "Szöveg" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Felhasználónév generátor" }, + "useThisEmail": { + "message": "Ezen email használata" + }, "useThisPassword": { "message": "Jelszó használata" }, @@ -2063,12 +2163,24 @@ "message": "erős egyedi jelszó létrehozásához", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Széf testreszabás" + }, "vaultTimeoutAction": { "message": "Széf időkifutás művelet" }, "vaultTimeoutAction1": { "message": "Időkifutási művelet" }, + "newCustomizationOptionsCalloutTitle": { + "message": "Új testreszabási opciók" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Szabjuk testre tárhely élményét gyors másolási műveletekkel, kompakt móddal és még sok mással!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "Az összes megjelenési beállítás megtekintése" + }, "lock": { "message": "Lezárás", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Tartomány", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Letiltott tartományok" + }, + "learnMoreAboutBlockedDomains": { + "message": "További információ a letiltott tartományokról" + }, "excludedDomains": { "message": "Kizárt domainek" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "A Bitwarden nem kéri a bejelentkezési adatok mentését ezeknél a tartományoknál az összes bejelentkezési fiókra vonatkozva. A változtatások életbe lépéséhez frissíteni kell az oldalt." }, + "blockedDomainsDesc": { + "message": "Az automatikus kitöltés és az egyéb kapcsolódó funkciók ezeken a webhelyeken nincsenek a kínálatban. A változtatások életbe lépéséhez frissíteni kell az oldalt." + }, + "autofillBlockedNoticeV2": { + "message": "Az automatikus kitöltés blokkolásra került ezen a webhelyen." + }, + "autofillBlockedNoticeGuidance": { + "message": "Megváltoztatás a beállításokban" + }, + "change": { + "message": "Módosítás" + }, + "changeButtonTitle": { + "message": "Jelszó módosítás - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "Veszélyes jelszavak" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ egy jelszó megváltoztatását kéri, mert az kockázatos.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ $COUNT$ jelszó megváltoztatását kéri, mert azok kockázatosak.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "A szervezetek $COUNT$ jelszó megváltoztatását kérik, mert azok kockázatosak.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Tekintsük át és módosítsuk az egyik veszélyeztetett jelszót." + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Tekintsük át és módosítsunk $COUNT$ kockázatnak kitett jelszót.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Változtassuk meg gyorsabban a veszélyeztetett jelszavakat." + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Frissítsük a beállításokat, hogy gyorsan automatikusan kitölthessük a jelszavakat és újakat generálhassunk." + }, + "reviewAtRiskLogins": { + "message": "Kockázatos bejelentkezések áttekintése" + }, + "reviewAtRiskPasswords": { + "message": "Kockázatos jelszavak áttekintése" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "A szervezeti jelszavak kockázatosak, mert gyengék, újra felhasználásra kerültek és/vagy nyilvánosságra kerültek.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "A kockázatos bejelentkezések listájának illusztrációja" + }, + "generatePasswordSlideDesc": { + "message": "Gyorsan generálhatunk erős, egyedi jelszót a Bitwarden automatikus kitöltési menüjével a kockázatos webhelyen.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "A Bitwarden automatikus kitöltési menüjének illusztrációja, amely egy generált jelszót jelenít meg." + }, + "updateInBitwarden": { + "message": "Frissítés a Bitwardenben" + }, + "updateInBitwardenSlideDesc": { + "message": "A Bitwarden ezután felkér a jelszó frissítésére a jelszókezelőben.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illusztráció a Bitwarden értesítéséről, amely felszólítja a felhasználót a bejelentkezési adatok frissítésére." + }, + "turnOnAutofill": { + "message": "Automatikus kitöltés bekapcsolása" + }, + "turnedOnAutofill": { + "message": "Az automatikus kitöltés bekapcsolásra került." + }, + "dismiss": { + "message": "Elvetés" + }, "websiteItemLabel": { "message": "Webhely $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "A letiltott tartomány módosítások mentésre kerültek." + }, "excludedDomainsSavedSuccess": { "message": "A kizárt tartomány módosítások mentésre kerültek." }, @@ -2789,6 +3022,20 @@ "error": { "message": "Hiba" }, + "decryptionError": { + "message": "Visszafejtési hiba" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "A Bitwarden nem tudta visszafejteni az alább felsorolt ​​széf elemeket." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Ügyfélszolgálat elérése", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "további adatvesztés elkerülése érdekében.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Felhasználónév generálása" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ elutasította a kérést. Vegyük fel a kapcsolatot szolgáltatóval segítségért.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ elutasította a kérést: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Nem lehet beolvasni $SERVICENAME$ maszkolt email fiók azonosítót.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "A beállítások szerkesztésre kerültek." - }, - "environmentEditedClick": { - "message": "Kattintás ide" - }, - "environmentEditedReset": { - "message": "az előre konfigurált beállítások visszaállításához." - }, "serverVersion": { "message": "Szerver verzió" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Bejelentkezés mesterjelszóval" }, - "loggingInAs": { - "message": "Bejelentkezve mint" - }, - "notYou": { - "message": "Ez tévedés?" - }, "newAroundHere": { "message": "Új felhasználó vagyunk?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Bejelentkezés eszközzel" }, - "loginWithDeviceEnabledInfo": { - "message": "Az eszközzel történő bejelentkezést a Biwarden mobilalkalmazás beállításaiban kell beállítani. Másik opcióra van szükség?" - }, "fingerprintPhraseHeader": { "message": "Ujjlenyomat kifejezés" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "Összes bejelentkezési opció megtekintése" }, - "viewAllLoginOptionsV1": { - "message": "Összes bejelentkezési opció megtekintése" - }, "notificationSentDevice": { "message": "Egy értesítés lett elküldve az eszközre." }, + "notificationSentDevicePart1": { + "message": "A Bitwarden zárolás feloldása az eszközön vagy: " + }, + "notificationSentDeviceAnchor": { + "message": "webalkalmazás" + }, + "notificationSentDevicePart2": { + "message": "Jóváhagyás előtt győződjünk meg arról, hogy az ujjlenyomat kifejezés megegyezik az alábbi kifejezéssel." + }, "aNotificationWasSentToYourDevice": { "message": "Egy értesítés lett elküldve az eszközre." }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Ellenőrizzük, hogy a széf feloldásra került és az ujjlenyomat kifejezés egyezik a másik eszközön levővel." - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "A kérelem jóváhagyása után értesítés érkezik." }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "A bejelentkezés elindításra került." }, + "logInRequestSent": { + "message": "A kérés elküldésre került." + }, "exposedMasterPassword": { "message": "Kiszivárgott mesterjelszó" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Adminisztrátori jóváhagyás kérés" }, - "approveWithMasterPassword": { - "message": "Jóváhagyás mesterjelszóval" - }, "ssoIdentifierRequired": { "message": "A szervezeti SSO azonosító megadása szükséges." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "A kérés elküldésre került az adminisztrátornak." }, - "youWillBeNotifiedOnceApproved": { - "message": "A jóváhagyás után értesítés érkezik." - }, "troubleLoggingIn": { "message": "Probléma van a bejelentkezéssel?" }, @@ -3396,38 +3649,6 @@ "message": "Összezárás váltás", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Adatok importálása a Bitwardenbe?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "A LastPass adatok megvédése és importálása a Bitwardenbe?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Mentés titkosítatlan fájlként", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Importálás a Bitwardenbe", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importálás...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Az adatok sikeresen importálásra kerültek.", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Hiba történt az importálás során. A részletekért ellenőrizzük a konzolt.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Hálózati hiba történt az importálás során.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Áldomain" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Aktív fiók" }, + "bitwardenAccount": { + "message": "Bitwarden fiók" + }, "availableAccounts": { "message": "Elérhető fiókok" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Automatikus kitöltés javaslatok" }, + "itemSuggestions": { + "message": "Javasolt elemek" + }, "autofillSuggestionsTip": { "message": "A bejelentkezési elem mentése ehhez a webhelyhez az automatikus kitöltéshez" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "$FIELD$, $VALUE$ másolása", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "Nincsenek másolandó értékek." }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Elem neve" }, - "cannotRemoveViewOnlyCollections": { - "message": "Nem távolíthatók el a csak megtekintési engedéllyel bíró gyűjtemények: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organization is deactivated" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "A webhely URI átrendezése. A nyílbillentyűkkel mozgassuk az elemet felfelé vagy lefelé." + }, "reorderFieldUp": { "message": "$LABEL$ feljebb került, $INDEX$/$LENGTH$ pozícióba", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "You have not selected anything." }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Szöveg küldés" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Fiókműveletek" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "Nincs jogosulltság ezen elem szerkesztéséheu." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "A biometrikus feloldás nem érhető el, mert először PIN kóddal vagy jelszóval kell feloldani." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "A biometrikus feloldás jelenleg nem érhető el." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "A biometrikus feloldás nem érhető el a rosszul konfigurált rendszerfájlok miatt." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "A biometrikus feloldás nem érhető el a rosszul konfigurált rendszerfájlok miatt." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "A biometrikus feloldás nem érhető el, mert a Bitwarden asztali alkalmazás be van zárva." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "A biometrikus feloldás nem érhető el, mert nincs engedélyezve $EMAIL$ számára a Bitwarden asztali alkalmazásban.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "A biometrikus feloldás jelenleg ismeretlen okból nem érhető el." + }, "authenticating": { "message": "Authenticating" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra széles" + }, + "sshKeyWrongPassword": { + "message": "A megadott jelszó helytelen." + }, + "importSshKey": { + "message": "Importálás" + }, + "confirmSshKeyPassword": { + "message": "Jelszó megerősítése" + }, + "enterSshKeyPasswordDesc": { + "message": "Adjuk meg az SSH kulcs jelszót." + }, + "enterSshKeyPassword": { + "message": "Jelszó megadása" + }, + "invalidSshKey": { + "message": "Az SSH kulcs érvénytelen." + }, + "sshKeyTypeUnsupported": { + "message": "Az SSH kulcstípus nem támogatott." + }, + "importSshKeyFromClipboard": { + "message": "Kulcs importálása vágólapból" + }, + "sshKeyImported": { + "message": "Az SSH kulcs sikeresen importálásra került." + }, + "cannotRemoveViewOnlyCollections": { + "message": "Nem távolíthatók el a csak megtekintési engedéllyel bíró gyűjtemények: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Frissítsük az asztali alkalmazást." + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "A biometrikus feloldás használatához frissítsük az asztali alkalmazást vagy tiltsuk le az ujjlenyomatos feloldást az asztali beállításokban." + }, + "changeAtRiskPassword": { + "message": "Kockázatos jelszó megváltoztatása" } } diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index 7b1ad51e0b7..0146fb2a000 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Petunjuk Kata Sandi Utama (opsional)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Bergabung ke organisasi" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Salin catatan" }, + "copy": { + "message": "Salin", + "description": "Copy to clipboard" + }, "fill": { "message": "Isikan", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Masukkan alamat surel akun Anda dan petunjuk kata sandi Anda akan dikirimkan kepada Anda" }, - "passwordHint": { - "message": "Petunjuk Kata Sandi" - }, - "enterEmailToGetHint": { - "message": "Masukkan email akun Anda untuk menerima pentunjuk sandi utama Anda." - }, "getMasterPasswordHint": { "message": "Dapatkan petunjuk sandi utama" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Sunting Folder" }, + "editFolderWithName": { + "message": "Sunting folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "Folder baru" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Buat frasa sandi" }, + "passwordGenerated": { + "message": "Kata sandi dibuat" + }, + "passphraseGenerated": { + "message": "Frasa sandi dibuat" + }, + "usernameGenerated": { + "message": "Nama pengguna dibuat" + }, + "emailGenerated": { + "message": "Surel dibuat" + }, "regeneratePassword": { "message": "Buat Ulang Kata Sandi" }, @@ -454,22 +482,6 @@ "length": { "message": "Panjang" }, - "uppercase": { - "message": "Huruf besar (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Huruf kecil (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Angka (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "karakter khusus (contoh.! @#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Sertakan", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Sertakan karakter khusus", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Jumlah Kata" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Peramban Anda tidak mendukung menyalin clipboard dengan mudah. Salin secara manual." }, - "verifyIdentity": { - "message": "Verifikasi Identitas Anda" + "verifyYourIdentity": { + "message": "Verifikasikan identitas Anda" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "Brankas Anda terkunci. Verifikasi kata sandi utama Anda untuk melanjutkan." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Masuk ke Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Mulai ulang pendaftaran" }, @@ -875,6 +904,9 @@ "no": { "message": "Tidak" }, + "location": { + "message": "Lokasi" + }, "unexpectedError": { "message": "Terjadi kesalahan yang tak diduga." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Tanyakan untuk menambah sebuah benda jika benda itu tidak ditemukan di brankas Anda. Diterapkan ke seluruh akun yang telah masuk." }, - "showCardsInVaultView": { - "message": "Tampilkan kartu sebagai saran isi otomatis pada tampilan Brankas" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "Tamplikan kartu pada halaman Tab" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Buat tampilan daftar benda dari kartu pada halaman Tab untuk isi otomatis yang mudah." }, - "showIdentitiesInVaultView": { - "message": "Tampilkan identitas sebagai saran isi otomatis pada tampilan Brankas" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "Tampilkan identitas pada halaman Tab" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "Hapus Papan Klip", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Iya, Simpan Sekarang" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Tanyakan untuk memperbarui masuk yang sudah ada" }, @@ -1084,10 +1169,6 @@ "message": "Terang", "description": "Light color" }, - "solarizedDark": { - "message": "Gelap Solarized", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Ekspor dari" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Beli Keanggotaan Premium" }, - "premiumPurchaseAlert": { - "message": "Anda dapat membeli keanggotaan premium di brankas web bitwarden.com. Anda ingin mengunjungi situs web sekarang?" - }, "premiumPurchaseAlertV2": { "message": "Anda dapat membeli Premium dari pilihan akun Anda pada aplikasi web Bitwarden." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Ingat saya" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Kirim ulang email kode verifikasi" }, "useAnotherTwoStepMethod": { "message": "Gunakan metode masuk dua langkah lainnya" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Masukkan YubiKey Anda ke port USB komputer Anda, lalu sentuh tombolnya." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Buka tab baru" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "Autentikasi dengan WebAuthn." }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "Info Masuk Tidak Tersedia" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Opsi Info Masuk Dua Langkah" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Kehilangan akses ke semua penyedia dua faktor Anda? Gunakan kode pemulihan untuk menonaktifkan semua penyedia dua faktor dari akun Anda." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Lingkungan Penyedia Personal" }, - "selfHostedEnvironmentFooter": { - "message": "Tetapkan URL dasar penyedia personal pemasangan Bitwarden Anda." - }, "selfHostedBaseUrlHint": { "message": "Tentukan URL dasar dari pemasangan Bitwarden di server Anda. Contoh: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Lingkungan Khusus" }, - "customEnvironmentFooter": { - "message": "Untuk pengguna tingkat lanjut. Anda bisa menentukan basis dari URL masing-masing layanan secara independen." - }, "baseUrl": { "message": "URL Server" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Seret untuk mengurutkan" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Teks" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Pembuat nama pengguna" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "Gunakan kata sandi ini" }, @@ -2063,12 +2163,24 @@ "message": "untuk membuat sebuah kata sandi unit yang kuat", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "Tindakan Batas Waktu Brankas" }, "vaultTimeoutAction1": { "message": "Batas waktu tindakan" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "Kunci", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domain", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "Domain yang Dikecualikan" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden tidak akan meminta untuk menyimpan rincian login untuk domain tersebut. Anda harus menyegarkan halaman agar perubahan diterapkan." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Ubah" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "Situs web $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Perubahan domain yang diabaikan telah disimpan" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Galat" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Buat nama pengguna baru" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Gagal mendapatkan akun ID surel bertopeng dari $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Pengaturan telah disunting" - }, - "environmentEditedClick": { - "message": "Tekan di sini" - }, - "environmentEditedReset": { - "message": "untuk mengatur ulang ke pengaturan awal" - }, "serverVersion": { "message": "Versi server" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Masuk dengan kata sandi utama" }, - "loggingInAs": { - "message": "Masuk sebagai" - }, - "notYou": { - "message": "Bukan Anda?" - }, "newAroundHere": { "message": "Baru di sini?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Masuk dengan perangkat" }, - "loginWithDeviceEnabledInfo": { - "message": "Masuk dengan perangkat harus diatur di pengaturan aplikasi Bitwarden. Perlu pilihan lainnya?" - }, "fingerprintPhraseHeader": { "message": "Frasa sidik jari" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "Lihat semua pilihan masuk" }, - "viewAllLoginOptionsV1": { - "message": "Lihat semua pilihan masuk" - }, "notificationSentDevice": { "message": "Sebuah pemberitahuan dikirim ke perangkat Anda." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "Sebuah pemberitahuan telah dikirim ke perangkat Anda" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Pastikan akun Anda terbuka dan frasa sidik jari cocok pada perangkat lainnya" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "Anda akan diberitahu setelah permintaan disetujui" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Memulai login" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Kata Sandi Utama yang Terpapar" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Minta persetujuan admin" }, - "approveWithMasterPassword": { - "message": "Setujui dengan kata sandi utama" - }, "ssoIdentifierRequired": { "message": "Pengenal SSO organisasi diperlukan." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Permintaan Anda telah dikirim ke admin Anda." }, - "youWillBeNotifiedOnceApproved": { - "message": "Anda akan diberitahu setelah disetujui." - }, "troubleLoggingIn": { "message": "Kesulitan masuk?" }, @@ -3396,38 +3649,6 @@ "message": "Saklar lipat", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Impor data Anda ke Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Lindungi data LastPass Anda dan impor ke Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Simpan sebagai berkas yang tidak dienkripsi", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Impor ke Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Mengimpor...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data berhasil diimpor!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Gagal mengimpor. Periksa konsol untuk rinciannya.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Kesalahan jaringan ditemui ketika mengimpor.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Domain alias" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Akun aktif" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Akun yang tersedia" }, @@ -3979,7 +4203,10 @@ "message": "Kunci sandi dihapus" }, "autofillSuggestions": { - "message": "Saran isi otomatis" + "message": "Autofill suggestions" + }, + "itemSuggestions": { + "message": "Suggested items" }, "autofillSuggestionsTip": { "message": "Simpan benda login untuk situs ini ke isi otomatis" @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "Tidak ada nilai untuk disalin" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Nama benda" }, - "cannotRemoveViewOnlyCollections": { - "message": "Anda tidak dapat menghapus koleksi dengan izin hanya lihat: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organisasi dinonaktifkan" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "You have not selected anything." }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -4557,14 +4783,8 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { - "message": "Account actions" + "message": "Tindakan akun" }, "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" @@ -4573,22 +4793,22 @@ "message": "Show quick copy actions on Vault" }, "systemDefault": { - "message": "System default" + "message": "Baku sistem" }, "enterprisePolicyRequirementsApplied": { - "message": "Enterprise policy requirements have been applied to this setting" + "message": "Persyaratan kebijakan perusahaan telah diterapkan ke pengaturan ini" }, "sshPrivateKey": { - "message": "Private key" + "message": "Kunci privat" }, "sshPublicKey": { - "message": "Public key" + "message": "Kunci publik" }, "sshFingerprint": { - "message": "Fingerprint" + "message": "Sidik jari" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "Tipe kunci" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -4603,7 +4823,7 @@ "message": "RSA 4096-Bit" }, "retry": { - "message": "Retry" + "message": "Coba lagi" }, "vaultCustomTimeoutMinimum": { "message": "Minimum custom timeout is 1 minute." @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4828,16 +5075,16 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "Tidak" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" + "message": "Ya, saya dapat mengakses surel saya secara handla" }, "turnOnTwoStepLogin": { "message": "Turn on two-step login" }, "changeAcctEmail": { - "message": "Change account email" + "message": "Ubah surel akun" }, "extensionWidth": { "message": "Lebar ekstensi" @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Ekstra lebar" + }, + "sshKeyWrongPassword": { + "message": "Kata sandi yang Anda masukkan tidak benar." + }, + "importSshKey": { + "message": "Impor" + }, + "confirmSshKeyPassword": { + "message": "Konfirmasi kata sandi" + }, + "enterSshKeyPasswordDesc": { + "message": "Masukkan kata sandi untuk kunci SSH." + }, + "enterSshKeyPassword": { + "message": "Masukkan kata sandi" + }, + "invalidSshKey": { + "message": "Kunci SSH tidak valid" + }, + "sshKeyTypeUnsupported": { + "message": "Tipe kunci SSH tidak didukung" + }, + "importSshKeyFromClipboard": { + "message": "Impor kunci dari papan klip" + }, + "sshKeyImported": { + "message": "Kunci SSH sukses diimpor" + }, + "cannotRemoveViewOnlyCollections": { + "message": "Anda tidak dapat menghapus koleksi dengan izin hanya lihat: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Harap perbarui aplikasi desktop Anda" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "Untuk memakai pembuka kunci biometrik, harap perbarui aplikasi desktop Anda, atau matikan buka kunci sidik jari dalam pengaturan desktop." + }, + "changeAtRiskPassword": { + "message": "Ubah kata sandi yang berrisiko" } } diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index 6490a441832..cb8e414ca37 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -20,7 +20,7 @@ "message": "Crea account" }, "newToBitwarden": { - "message": "Nuovo in Bitwarden?" + "message": "Sei un nuovo utente?" }, "logInWithPasskey": { "message": "Accedi con passkey" @@ -29,7 +29,7 @@ "message": "Usa il Single Sign-On" }, "welcomeBack": { - "message": "Bentornato" + "message": "Bentornat*" }, "setAStrongPassword": { "message": "Imposta una password robusta" @@ -62,7 +62,7 @@ "message": "Un suggerimento per la password principale può aiutarti a ricordarla se la dimentichi." }, "masterPassHintText": { - "message": "Se dimentichi la password, il suggerimento password può essere inviato alla tua email. $CURRENT$/$MAXIMUM$ massimo carattere.", + "message": "Se dimentichi la password, il suggerimento password può essere inviato alla tua email. Attualmente $CURRENT$ caratteri, massimo $MAXIMUM$.", "placeholders": { "current": { "content": "$1", @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Suggerimento per la password principale (facoltativo)" }, + "passwordStrengthScore": { + "message": "Complessità password: $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Unisciti all'organizzazione" }, @@ -150,7 +159,7 @@ "message": "Copia numero passaporto" }, "copyLicenseNumber": { - "message": "Copia numero licenza" + "message": "Copia numero patente" }, "copyPrivateKey": { "message": "Copia chiave privata" @@ -176,6 +185,10 @@ "copyNotes": { "message": "Copia note" }, + "copy": { + "message": "Copia", + "description": "Copy to clipboard" + }, "fill": { "message": "Riempi", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -193,10 +206,10 @@ "message": "Riempi automaticamente identità" }, "fillVerificationCode": { - "message": "Riempi codice di verifica" + "message": "Inserisci codice di verifica" }, "fillVerificationCodeAria": { - "message": "Riempi Codice di Verifica", + "message": "Inserisci codice di verifica", "description": "Aria label for the heading displayed the inline menu for totp code autofill" }, "generatePasswordCopied": { @@ -239,7 +252,7 @@ "message": "Aggiungi elemento" }, "accountEmail": { - "message": "Email dell'account" + "message": "Account email" }, "requestHint": { "message": "Richiedi suggerimento" @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Inserisci l'indirizzo email dell'account e ti invieremo il tuo suggerimento per la password" }, - "passwordHint": { - "message": "Suggerimento per la password" - }, - "enterEmailToGetHint": { - "message": "Inserisci l'indirizzo email del tuo account per ricevere il suggerimento per la password principale." - }, "getMasterPasswordHint": { "message": "Ottieni suggerimento della password principale" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Modifica cartella" }, + "editFolderWithName": { + "message": "Modifica cartella: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "Nuova cartella" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Genera passphrase" }, + "passwordGenerated": { + "message": "Parola d'accesso generata" + }, + "passphraseGenerated": { + "message": "Frase d'accesso generata" + }, + "usernameGenerated": { + "message": "Nome utente generato" + }, + "emailGenerated": { + "message": "E-mail generata" + }, "regeneratePassword": { "message": "Rigenera password" }, @@ -454,22 +482,6 @@ "length": { "message": "Lunghezza" }, - "uppercase": { - "message": "Maiuscole (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Minuscole (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Numeri (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Caratteri speciali (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Includi", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Includi caratteri speciali", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Numero di parole" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Il tuo browser non supporta copiare dagli appunti. Copialo manualmente." }, - "verifyIdentity": { - "message": "Verifica identità" + "verifyYourIdentity": { + "message": "Verifica la tua identità" + }, + "weDontRecognizeThisDevice": { + "message": "Inserisci il codice inviato alla tua e-mail per verificare la tua identità." + }, + "continueLoggingIn": { + "message": "Continua l'accesso" }, "yourVaultIsLocked": { "message": "La tua cassaforte è bloccata. Verifica la tua identità per continuare." @@ -854,8 +868,23 @@ "logInToBitwarden": { "message": "Accedi a Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Inserisci il codice inviato alla tua e-mail" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Inserisci il codice dalla tua app di autenticazione" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Premi la tua YubiKey per accedere" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Il login Duo in due passaggi è richiesto per il tuo account. Segui i passaggi qui sotto per completare l'accesso." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Segui i passaggi qui sotto per completare l'accesso." + }, "restartRegistration": { - "message": "Riprova la registrazione" + "message": "Ricomincia la registrazione" }, "expiredLink": { "message": "Link scaduto" @@ -875,6 +904,9 @@ "no": { "message": "No" }, + "location": { + "message": "Luogo" + }, "unexpectedError": { "message": "Si è verificato un errore imprevisto." }, @@ -891,7 +923,7 @@ "message": "Rendi il tuo account più sicuro impostando l'autenticazione a due fattori nell'app web di Bitwarden." }, "twoStepLoginConfirmationTitle": { - "message": "Aprire web app?" + "message": "Vuoi continuare sulla web app?" }, "editedFolder": { "message": "Cartella salvata" @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Chiedi di creare un nuovo elemento se non ce n'è uno nella tua cassaforte. Si applica a tutti gli account sul dispositivo." }, - "showCardsInVaultView": { - "message": "Mostra le carte come suggerimenti di riempimento automatico nella vista cassaforte" + "showCardsInVaultViewV2": { + "message": "Mostra sempre le carte come suggerimenti di riempimento automatico nella vista cassaforte" }, "showCardsCurrentTab": { "message": "Mostra le carte nella sezione Scheda" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Mostra le carte nella sezione Scheda per riempirle automaticamente." }, - "showIdentitiesInVaultView": { - "message": "Mostra le identità come suggerimenti di riempimento automatico nella vista cassaforte" + "showIdentitiesInVaultViewV2": { + "message": "Mostra sempre le identità come suggerimenti di riempimento automatico nella vista cassaforte" }, "showIdentitiesCurrentTab": { "message": "Mostra le identità nella sezione Scheda" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Clicca gli oggetti da riempire dalla sezione Cassaforte" }, + "clickToAutofill": { + "message": "Clicca elementi nel suggerimento di riempimento automatico per riempire" + }, "clearClipboard": { "message": "Cancella appunti", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Salva" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ salvato in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ aggiornato in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Salva come nuovo accesso", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Aggiorna accesso", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Salvare l'accesso?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Aggiornare l'accesso esistente?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Accesso salvato", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Accesso aggiornato", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Errore nel salvataggio", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! Non abbiamo potuto salvarlo. Prova a inserire manualmente i dettagli.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Chiedi di aggiornare il login esistente" }, @@ -1084,10 +1169,6 @@ "message": "Chiaro", "description": "Light color" }, - "solarizedDark": { - "message": "Scuro solarizzato", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Esporta da" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Passa a Premium" }, - "premiumPurchaseAlert": { - "message": "Puoi acquistare il un abbonamento Premium dalla cassaforte web su bitwarden.com. Vuoi visitare il sito?" - }, "premiumPurchaseAlertV2": { "message": "Puoi acquistare Premium dalle impostazioni del tuo account sull'app web Bitwarden." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Ricordami" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Non chiedere più su questo dispositivo per 30 giorni" + }, "sendVerificationCodeEmailAgain": { "message": "Invia di nuovo l'email con codice di verifica" }, "useAnotherTwoStepMethod": { "message": "Usa un altro metodo di verifica in due passaggi" }, + "selectAnotherMethod": { + "message": "Seleziona un altro metodo", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Usa il tuo codice di recupero" + }, "insertYubiKey": { "message": "Inserisci la tua YubiKey nella porta USB del computer, poi premi il suo pulsante." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Apri nuova scheda" }, + "openInNewTab": { + "message": "Apri in una nuova scheda" + }, "webAuthnAuthenticate": { "message": "Autenticazione WebAuthn" }, + "readSecurityKey": { + "message": "Leggi chiave di sicurezza" + }, + "awaitingSecurityKeyInteraction": { + "message": "In attesa di interazione con la chiave di sicurezza..." + }, "loginUnavailable": { "message": "Login non disponibile" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Opzioni di verifica in due passaggi" }, + "selectTwoStepLoginMethod": { + "message": "Seleziona metodo di accesso in due passaggi" + }, "recoveryCodeDesc": { "message": "Hai perso l'accesso a tutti i tuoi metodi di verifica in due passaggi? Usa il tuo codice di recupero per disattivarli tutti dal tuo account." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Ambiente self-hosted" }, - "selfHostedEnvironmentFooter": { - "message": "Specifica l'URL principale della tua installazione self-hosted di Bitwarden." - }, "selfHostedBaseUrlHint": { "message": "Specifica lo URL principale della tua installazione self-hosted di Bitwarden. Esempio: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Ambiente personalizzato" }, - "customEnvironmentFooter": { - "message": "Per utenti avanzati. Puoi specificare l'URL principale di ogni servizio indipendentemente." - }, "baseUrl": { "message": "URL del server" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Trascina per ordinare" }, + "dragToReorder": { + "message": "Trascina per riordinare" + }, "cfTypeText": { "message": "Testo" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Generatore di nomi utente" }, + "useThisEmail": { + "message": "Usa questa e-mail" + }, "useThisPassword": { "message": "Usa questa password" }, @@ -2063,12 +2163,24 @@ "message": "per creare una password univoca robusta", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Personalizzazione cassaforte" + }, "vaultTimeoutAction": { "message": "Azione timeout cassaforte" }, "vaultTimeoutAction1": { "message": "Azione al timeout" }, + "newCustomizationOptionsCalloutTitle": { + "message": "Nuove opzioni di personalizzazione" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Personalizza l'esperienza della tua cassaforte con azioni di copia rapida, modalità compatta e altro ancora!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "Vedi tutte le impostazioni di aspetto" + }, "lock": { "message": "Blocca", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domini", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Domini bloccati" + }, + "learnMoreAboutBlockedDomains": { + "message": "Info sui domini bloccati" + }, "excludedDomains": { "message": "Domini esclusi" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden non chiederà di salvare le credenziali di accesso per questi domini per tutti gli account sul dispositivo. Ricarica la pagina affinché le modifiche abbiano effetto." }, + "blockedDomainsDesc": { + "message": "Per questi siti, riempimento automatico e funzionalità simili non saranno disponibili. Ricarica la pagina per applicare le modifiche." + }, + "autofillBlockedNoticeV2": { + "message": "La compilazione automatica è bloccata per questo sito." + }, + "autofillBlockedNoticeGuidance": { + "message": "Modifica questo nelle impostazioni" + }, + "change": { + "message": "Cambia" + }, + "changeButtonTitle": { + "message": "Cambia parola d'accesso - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "Parola d'accesso a rischio" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ ti chiede di cambiare una parola d'accesso perché è a rischio.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ ti sta richiedendo di modificare $COUNT$ parole d'accesso perché sono a rischio.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Le tue organizzazioni ti chiedono di modificare le $COUNT$ parole d'accesso perché sono a rischio.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Rivedi e modifica una parola d'accesso a rischio" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Rivedi e modifica $COUNT$ parole d'accesso a rischio", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Cambia le parole d'accesso a rischio più velocemente" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Aggiorna le impostazioni in modo da poter rapidamente riempire automaticamente le parole d'accesso e generarne di nuove" + }, + "reviewAtRiskLogins": { + "message": "Redi accessi a rischio" + }, + "reviewAtRiskPasswords": { + "message": "Rivedi parole d'accesso a rischio" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Le parole d'accesso dell'organizzazione sono a rischio perché sono deboli, riutilizzate, e/o esposte.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustrazione di un elenco di accessi a rischio" + }, + "generatePasswordSlideDesc": { + "message": "Genera rapidamente una parola d'accesso forte e unica con il menu' di riempimento automatico Bitwarden nel sito a rischio.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustrazione del menu' di riempimento automatico Bitwarden che mostra una parola d'accesso generata" + }, + "updateInBitwarden": { + "message": "Aggiorna in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden ti chiederà quindi di aggiornare la parola d'accesso nel gestore di parole d'accesso.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustrazione di una notifica Bitwarden che richiede all'utente di aggiornare l'accesso" + }, + "turnOnAutofill": { + "message": "Attiva riempimento automatico" + }, + "turnedOnAutofill": { + "message": "Riempimento automatico attivato" + }, + "dismiss": { + "message": "Ignora" + }, "websiteItemLabel": { "message": "Sito $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Modifiche ai domini bloccati salvate" + }, "excludedDomainsSavedSuccess": { "message": "Modifiche del dominio escluso salvate" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Errore" }, + "decryptionError": { + "message": "Errore di decifrazione" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden non può decifrare gli elementi elencati di seguito." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contatta il cliente correttamente", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "per evitare ulteriori perdite di dati.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Genera nome utente" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ ha rifiutato la tua richiesta. Contatta il tuo fornitore di servizi per assistenza.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ ha respinto la tua richiesta: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Impossibile ottenere l'ID dell'account email mascherato di $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Impostazioni modificate" - }, - "environmentEditedClick": { - "message": "Clicca qui" - }, - "environmentEditedReset": { - "message": "per ritornare alle impostazioni preconfigurate" - }, "serverVersion": { "message": "Versione server" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Accedi con password principale" }, - "loggingInAs": { - "message": "Accedendo come" - }, - "notYou": { - "message": "Non sei tu?" - }, "newAroundHere": { "message": "Nuovo da queste parti?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Accedi con dispositivo" }, - "loginWithDeviceEnabledInfo": { - "message": "L'accesso con dispositivo deve essere abilitato nelle impostazioni dell'app Bitwarden. Ti serve un'altra opzione?" - }, "fingerprintPhraseHeader": { "message": "Frase impronta" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "Visualizza tutte le opzioni di accesso" }, - "viewAllLoginOptionsV1": { - "message": "Visualizza tutte le opzioni di accesso" - }, "notificationSentDevice": { "message": "Una notifica è stata inviata al tuo dispositivo." }, + "notificationSentDevicePart1": { + "message": "Sblocca Bitwarden sul tuo dispositivo o su" + }, + "notificationSentDeviceAnchor": { + "message": "app web" + }, + "notificationSentDevicePart2": { + "message": "Assicurarsi che la frase di impronta digitale corrisponda a quella sottostante prima dell'approvazione." + }, "aNotificationWasSentToYourDevice": { "message": "Una notifica è stata inviata al tuo dispositivo" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Assicurati che il tuo account sia sbloccato e che la frase dell'impronta digitale corrisponda nell'altro dispositivo" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "Sarai notificato una volta che la richiesta sarà approvata" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Accesso avviato" }, + "logInRequestSent": { + "message": "Richiesta inviata" + }, "exposedMasterPassword": { "message": "Password principale violata" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Richiedi approvazione dell'amministratore" }, - "approveWithMasterPassword": { - "message": "Approva con password principale" - }, "ssoIdentifierRequired": { "message": "Identificatore SSO dell'organizzazione obbligatorio." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "La tua richiesta è stata inviata al tuo amministratore." }, - "youWillBeNotifiedOnceApproved": { - "message": "Riceverai una notifica una volta approvato." - }, "troubleLoggingIn": { "message": "Problemi ad accedere?" }, @@ -3396,38 +3649,6 @@ "message": "Comprimi/espandi", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Importare i tuoi dati su Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Proteggere i tuoi dati LastPass e importarli su Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Salva come file non crittografato", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Importa su Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importazione in corso...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Dati importati!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Errore durante l'importazione. Controlla la console per i dettagli.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Errore di connessione durante l'importazione.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Dominio alias" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Account attivo" }, + "bitwardenAccount": { + "message": "Account Bitwarden" + }, "availableAccounts": { "message": "Account disponibili" }, @@ -3979,7 +4203,10 @@ "message": "Passkey rimossa" }, "autofillSuggestions": { - "message": "Suggerimenti per il riempimento automatico" + "message": "Suggerimenti riempimento automatico" + }, + "itemSuggestions": { + "message": "Elementi suggeriti" }, "autofillSuggestionsTip": { "message": "Salva un elemento di accesso per questo sito da riempire automaticamente" @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copia $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "Nessun valore da copiare" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Nome elemento" }, - "cannotRemoveViewOnlyCollections": { - "message": "Non puoi rimuovere raccolte con i soli permessi di visualizzazione: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "L'organizzazione è disattivata" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Riordina l'URI del sito web. Usa il tasto freccia per spostare l'elemento su o giù." + }, "reorderFieldUp": { "message": "$LABEL$ spostato su, in posizione $INDEX$ di $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "Non hai selezionato nulla." }, - "movedItemsToOrg": { - "message": "Elementi selezionati spostati in $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Elementi spostati su $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Send Testo" }, - "bitwardenNewLook": { - "message": "Bitwarden ha un nuovo look!" - }, - "bitwardenNewLookDesc": { - "message": "È più facile e intuitivo che mai utilizzare il riempimento automatico e cercare dalla scheda Cassaforte. Dai un'occhiata!" - }, "accountActions": { "message": "Azioni dell'account" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "Non hai i permessi per modificare questo elemento" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Lo sblocco biometrico non è disponibile perché è necessario prima sbloccare con PIN o parola d'accesso." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Lo sblocco biometrico non è attualmente disponibile." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Lo sblocco biometrico non è disponibile a causa di file di sistema mal configurati." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Lo sblocco biometrico non è disponibile a causa di file di sistema mal configurati." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Lo sblocco biometrico non è disponibile perché l'app desktop Bitwarden è chiusa." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Lo sblocco biometrico non è disponibile perché non è abilitato per $EMAIL$ nell'app desktop Bitwarden.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Lo sblocco biometrico non è attualmente disponibile per un motivo sconosciuto." + }, "authenticating": { "message": "Autenticazione" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Molto larga" + }, + "sshKeyWrongPassword": { + "message": "La parola d'accesso inserita non è corretta." + }, + "importSshKey": { + "message": "Importa" + }, + "confirmSshKeyPassword": { + "message": "Conferma parola d'accesso" + }, + "enterSshKeyPasswordDesc": { + "message": "Inserisci la parola d'accesso per la chiave SSH." + }, + "enterSshKeyPassword": { + "message": "Inserisci parola d'accesso" + }, + "invalidSshKey": { + "message": "La chiave SSH non è valida" + }, + "sshKeyTypeUnsupported": { + "message": "Il tipo di chiave SSH non è supportato" + }, + "importSshKeyFromClipboard": { + "message": "Importa chiave dagli Appunti" + }, + "sshKeyImported": { + "message": "Chiave SSH importata correttamente" + }, + "cannotRemoveViewOnlyCollections": { + "message": "Non puoi rimuovere raccolte con i soli permessi di visualizzazione: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Aggiornare l'applicazione desktop" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "Per usare lo sblocco biometrico, aggiornare l'applicazione desktop o disabilitare lo sblocco dell'impronta digitale nelle impostazioni del desktop." + }, + "changeAtRiskPassword": { + "message": "Cambia parola d'accesso a rischio" } } diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index 42dd73020ec..b67d0b62a6e 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "マスターパスワードのヒント (省略可能)" }, + "passwordStrengthScore": { + "message": "パスワードの強度スコア $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "組織に参加" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "メモをコピー" }, + "copy": { + "message": "コピー", + "description": "Copy to clipboard" + }, "fill": { "message": "入力", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "アカウントのメールアドレスを入力すると、パスワードのヒントが送信されます" }, - "passwordHint": { - "message": "パスワードのヒント" - }, - "enterEmailToGetHint": { - "message": "マスターパスワードのヒントを受信するアカウントのメールアドレスを入力してください。" - }, "getMasterPasswordHint": { "message": "マスターパスワードのヒントを取得する" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "フォルダーを編集" }, + "editFolderWithName": { + "message": "フォルダーを編集: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "新しいフォルダー" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "パスフレーズを生成" }, + "passwordGenerated": { + "message": "パスワードを生成しました" + }, + "passphraseGenerated": { + "message": "パスフレーズを生成しました" + }, + "usernameGenerated": { + "message": "ユーザー名を生成しました" + }, + "emailGenerated": { + "message": "メールアドレスを生成しました" + }, "regeneratePassword": { "message": "パスワードの再生成" }, @@ -454,22 +482,6 @@ "length": { "message": "長さ" }, - "uppercase": { - "message": "大文字(A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "小文字(a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "数字 (0~9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "特殊文字(!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "含む文字", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "特殊記号を含める", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "単語数" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "お使いのブラウザはクリップボードへのコピーに対応していません。手動でコピーしてください" }, - "verifyIdentity": { - "message": "本人確認を行う" + "verifyYourIdentity": { + "message": "本人確認" + }, + "weDontRecognizeThisDevice": { + "message": "このデバイスは未確認です。本人確認のため、メールアドレスに送信されたコードを入力してください。" + }, + "continueLoggingIn": { + "message": "ログインを続ける" }, "yourVaultIsLocked": { "message": "保管庫がロックされています。続行するには本人確認を行ってください。" @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Bitwarden にログイン" }, + "enterTheCodeSentToYourEmail": { + "message": "メールアドレスに送信されたコードを入力してください" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "認証アプリに表示されているコードを入力してください" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "YubiKey を押して認証してください" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "このアカウントでは Duo 二段階認証を行う必要があります。以下の手順に従ってログインを完了してください。" + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "以下の手順に従ってログインを完了してください。" + }, "restartRegistration": { "message": "登録を再度始める" }, @@ -875,6 +904,9 @@ "no": { "message": "いいえ" }, + "location": { + "message": "場所" + }, "unexpectedError": { "message": "予期せぬエラーが発生しました。" }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "保管庫にアイテムが見つからない場合は、アイテムを追加するよう要求します。ログインしているすべてのアカウントに適用されます。" }, - "showCardsInVaultView": { - "message": "保管庫ビューに自動入力の候補としてカードを表示する" + "showCardsInVaultViewV2": { + "message": "保管庫ビューの自動入力の候補として、カードを常に表示する" }, "showCardsCurrentTab": { "message": "タブページにカードを表示" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "自動入力を簡単にするために、タブページにカードアイテムを表示します" }, - "showIdentitiesInVaultView": { - "message": "保管庫ビューに自動入力の候補として ID を表示する" + "showIdentitiesInVaultViewV2": { + "message": "保管庫ビューの自動入力の候補として、 ID を常に表示する" }, "showIdentitiesCurrentTab": { "message": "タブページに ID を表示" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "保管庫で、自動入力するアイテムをクリックしてください" }, + "clickToAutofill": { + "message": "入力候補のアイテムをクリックして自動入力する" + }, "clearClipboard": { "message": "クリップボードの消去", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "保存する" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ を Bitwarden に保存しました。", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ を Bitwarden 内で更新しました。", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "新規のログイン情報として保存", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "ログイン情報を更新", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "ログイン情報を保存しますか?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "既存のログイン情報を更新しますか?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "ログイン情報を保存しました", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "ログイン情報が更新されました", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "保存エラー", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "保存できませんでした。詳細を手動で入力してください。", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "既存のログイン情報の更新を尋ねる" }, @@ -1084,10 +1169,6 @@ "message": "ライト", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized ダーク", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "エクスポート元" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "プレミアム会員に加入" }, - "premiumPurchaseAlert": { - "message": "プレミアム会員権は bitwarden.com ウェブ保管庫で購入できます。ウェブサイトを開きますか?" - }, "premiumPurchaseAlertV2": { "message": "Bitwarden ウェブアプリでアカウント設定からプレミアムを購入できます。" }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "情報を保存する" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "このデバイスで30日間再表示しない" + }, "sendVerificationCodeEmailAgain": { "message": "確認コードをメールで再送" }, "useAnotherTwoStepMethod": { "message": "他の2段階認証方法を使用" }, + "selectAnotherMethod": { + "message": "別の方法を選択", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "リカバリーコードを使用する" + }, "insertYubiKey": { "message": " YubiKey を USB ポートに挿入し、ボタンをタッチしてください。" }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "新しいタブを開く" }, + "openInNewTab": { + "message": "新しいタブで開く" + }, "webAuthnAuthenticate": { "message": "WebAuthn の認証" }, + "readSecurityKey": { + "message": "セキュリティキーの読み取り" + }, + "awaitingSecurityKeyInteraction": { + "message": "セキュリティキーとの通信を待ち受け中…" + }, "loginUnavailable": { "message": "ログインできません。" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "2段階認証オプション" }, + "selectTwoStepLoginMethod": { + "message": "2段階認証の方法を選択" + }, "recoveryCodeDesc": { "message": "すべての2段階認証プロパイダにアクセスできなくなったときは、リカバリーコードを使用するとアカウントの2段階認証を無効化できます。" }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "セルフホスティング環境" }, - "selfHostedEnvironmentFooter": { - "message": "セルフホスティングしている Bitwarden のベース URL を指定してください。" - }, "selfHostedBaseUrlHint": { "message": "オンプレミスホストした Bitwarden のベース URL を指定してください。例: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "カスタム環境" }, - "customEnvironmentFooter": { - "message": "上級者向けです。各サービスのベース URL を個別に指定できます。" - }, "baseUrl": { "message": "サーバー URL" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "ドラッグして並べ替え" }, + "dragToReorder": { + "message": "ドラッグして並べ替え" + }, "cfTypeText": { "message": "テキスト" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "ユーザー名生成ツール" }, + "useThisEmail": { + "message": "このメールアドレスを使う" + }, "useThisPassword": { "message": "このパスワードを使用する" }, @@ -2063,12 +2163,24 @@ "message": "を使うと強力で一意なパスワードを作れます", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "保管庫のカスタマイズ" + }, "vaultTimeoutAction": { "message": "保管庫タイムアウト時のアクション" }, "vaultTimeoutAction1": { "message": "タイムアウト時のアクション" }, + "newCustomizationOptionsCalloutTitle": { + "message": "新しいカスタマイズオプション" + }, + "newCustomizationOptionsCalloutContent": { + "message": "クイックコピーやコンパクトモードなどで保管庫をカスタマイズできます!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "すべての外観設定を表示" + }, "lock": { "message": "ロック", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "ドメイン", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "ブロックしたドメイン" + }, + "learnMoreAboutBlockedDomains": { + "message": "ブロックされたドメインについてもっと詳しく" + }, "excludedDomains": { "message": "除外するドメイン" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden はログインしているすべてのアカウントで、これらのドメインのログイン情報を保存するよう要求しません。 変更を有効にするにはページを更新する必要があります。" }, + "blockedDomainsDesc": { + "message": "自動入力やその他の関連機能はこれらのウェブサイトには提供されません。変更を反映するにはページを更新する必要があります。" + }, + "autofillBlockedNoticeV2": { + "message": "このウェブサイトへの自動入力はブロックされています。" + }, + "autofillBlockedNoticeGuidance": { + "message": "設定でこれを変更する" + }, + "change": { + "message": "変更" + }, + "changeButtonTitle": { + "message": "パスワードの変更 - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "リスクがあるパスワード" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ から、 1 件の危険なパスワードを変更するよう求められています。", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ から、 $COUNT$ 件の危険なパスワードを変更するよう求められています。", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "複数の所属先組織から、 $COUNT$ 件の危険なパスワードを変更するよう求められています。", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "1 件の危険なパスワードを確認・変更する" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "$COUNT$ 件の危険なパスワードを確認・変更する", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "危険なパスワードをより素早く変更する" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "設定を更新して、パスワードを素早く自動入力したり、新しいパスワードを生成できるようにしましょう" + }, + "reviewAtRiskLogins": { + "message": "危険な状態のログイン情報を確認" + }, + "reviewAtRiskPasswords": { + "message": "危険な状態のパスワードを確認" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "組織で使用するパスワードが脆弱である、または再利用されているか流出しており、危険な状態です。", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "危険な状態にあるログイン情報の一覧表示の例" + }, + "generatePasswordSlideDesc": { + "message": "Bitwarden の自動入力メニューで、強力で一意なパスワードをすぐに生成しましょう。", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Bitwarden の自動入力メニューで、生成されたパスワードが表示されている例" + }, + "updateInBitwarden": { + "message": "Bitwarden 上のデータを更新" + }, + "updateInBitwardenSlideDesc": { + "message": "続いて、 Bitwarden がパスワードマネージャーに保存されたパスワードを更新するよう促します。", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "ユーザーにログイン情報を更新するよう促す Bitwarden の通知例" + }, + "turnOnAutofill": { + "message": "自動入力をオンにする" + }, + "turnedOnAutofill": { + "message": "自動入力をオンにしました" + }, + "dismiss": { + "message": "閉じる" + }, "websiteItemLabel": { "message": "ウェブサイト $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "ブロックするドメインの変更を保存しました" + }, "excludedDomainsSavedSuccess": { "message": "除外ドメインの変更を保存しました" }, @@ -2789,6 +3022,20 @@ "error": { "message": "エラー" }, + "decryptionError": { + "message": "復号エラー" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden は以下の保管庫のアイテムを復号できませんでした。" + }, + "contactCSToAvoidDataLossPart1": { + "message": "カスタマーサクセスに問い合わせて、", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "さらなるデータ損失を回避してください。", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "ユーザー名を生成" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ はリクエストを拒否しました。サービスプロバイダーにお問い合わせください。", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ があなたのリクエストを拒否しました: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "$SERVICENAME$ マスク済みメールアカウント ID を取得できませんでした。", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "設定を更新しました" - }, - "environmentEditedClick": { - "message": "ここをクリック" - }, - "environmentEditedReset": { - "message": "すると初期設定に戻します" - }, "serverVersion": { "message": "サーバーのバージョン" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "マスターパスワードでログイン" }, - "loggingInAs": { - "message": "ログイン中:" - }, - "notYou": { - "message": "あなたではないですか?" - }, "newAroundHere": { "message": "初めてですか?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "デバイスでログイン" }, - "loginWithDeviceEnabledInfo": { - "message": "Bitwarden アプリの設定でデバイスでログインする必要があります。別のオプションが必要ですか?" - }, "fingerprintPhraseHeader": { "message": "パスフレーズ" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "すべてのログインオプションを表示" }, - "viewAllLoginOptionsV1": { - "message": "すべてのログインオプションを表示" - }, "notificationSentDevice": { "message": "デバイスに通知を送信しました。" }, + "notificationSentDevicePart1": { + "message": "デバイスまたは" + }, + "notificationSentDeviceAnchor": { + "message": "ウェブアプリ" + }, + "notificationSentDevicePart2": { + "message": "上で、Bitwarden をロック解除してください。承認する前に、フィンガープリントフレーズが以下と一致していることを確認してください。" + }, "aNotificationWasSentToYourDevice": { "message": "お使いのデバイスに通知が送信されました" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "アカウントがロック解除されていることと、フィンガープリントフレーズが他の端末で一致していることを確認してください" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "リクエストが承認されると通知されます" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "ログイン開始" }, + "logInRequestSent": { + "message": "リクエストが送信されました" + }, "exposedMasterPassword": { "message": "流出したマスターパスワード" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "管理者の承認を要求する" }, - "approveWithMasterPassword": { - "message": "マスターパスワードで承認する" - }, "ssoIdentifierRequired": { "message": "組織の SSO ID が必要です。" }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "要求を管理者に送信しました。" }, - "youWillBeNotifiedOnceApproved": { - "message": "承認されると通知されます。 " - }, "troubleLoggingIn": { "message": "ログインできない場合" }, @@ -3396,38 +3649,6 @@ "message": "開く/閉じる", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Bitwarden にデータをインポートしますか?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "LastPass データを Bitwarden にインポートしますか?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "暗号化されていないファイルとして保存", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Bitwarden にインポート", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "インポート中...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "データをインポートしました!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "インポート中にエラーが発生しました。詳細はコンソールを確認してください。", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "インポート中にネットワークエラーが発生しました。", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "エイリアスドメイン" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "アクティブなアカウント" }, + "bitwardenAccount": { + "message": "Bitwarden アカウント" + }, "availableAccounts": { "message": "利用可能なアカウント" }, @@ -3979,7 +4203,10 @@ "message": "パスキーを削除しました" }, "autofillSuggestions": { - "message": "候補を自動入力する" + "message": "自動入力の候補" + }, + "itemSuggestions": { + "message": "推奨アイテム" }, "autofillSuggestionsTip": { "message": "自動入力するためにこのサイトのログインアイテムを保存します" @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "$FIELD$ 「$VALUE$」 をコピー", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "コピーする値がありません" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "アイテム名" }, - "cannotRemoveViewOnlyCollections": { - "message": "表示のみの権限が与えられているコレクションを削除することはできません: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "組織は無効化されています" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "ウェブサイトの URI の順序を変更します。矢印キーを押すとアイテムを上下に移動します。" + }, "reorderFieldUp": { "message": "$LABEL$ を上に移動しました。$INDEX$ / $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "何も選択されていません。" }, - "movedItemsToOrg": { - "message": "選択したアイテムを $ORGNAME$ に移動しました", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "アイテムを $ORGNAME$ に移動しました", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "テキスト Send" }, - "bitwardenNewLook": { - "message": "Bitwarden が新しい外観になりました。" - }, - "bitwardenNewLookDesc": { - "message": "保管庫タブからの自動入力と検索がこれまで以上に簡単で直感的になりました。" - }, "accountActions": { "message": "アカウントの操作" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "このアイテムを編集する権限がありません" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "PINまたはパスワードによるロック解除が最初に必要なため、生体認証によるロック解除は利用できません。" + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "生体認証によるロック解除は現在利用できません。" + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "システムファイルの設定が誤っているため、生体認証によるロック解除は利用できません。" + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "システムファイルの設定が誤っているため、生体認証によるロック解除は利用できません。" + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Bitwarden デスクトップアプリが閉じているため、生体認証によるロック解除は利用できません。" + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "生体認証による $EMAIL$ のロック解除は、 Bitwarden デスクトップアプリ上で有効になっていないため、利用できません。", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "生体認証によるロック解除は、不明な理由により現在利用できません。" + }, "authenticating": { "message": "認証中" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "エクストラワイド" + }, + "sshKeyWrongPassword": { + "message": "入力されたパスワードが間違っています。" + }, + "importSshKey": { + "message": "インポート" + }, + "confirmSshKeyPassword": { + "message": "パスワードを確認" + }, + "enterSshKeyPasswordDesc": { + "message": "SSH キーのパスワードを入力します。" + }, + "enterSshKeyPassword": { + "message": "パスワードを入力" + }, + "invalidSshKey": { + "message": "SSH キーが無効です" + }, + "sshKeyTypeUnsupported": { + "message": "サポートされていない種類の SSH キーです" + }, + "importSshKeyFromClipboard": { + "message": "クリップボードからキーをインポート" + }, + "sshKeyImported": { + "message": "SSH キーのインポートに成功しました" + }, + "cannotRemoveViewOnlyCollections": { + "message": "表示のみの権限が与えられているコレクションを削除することはできません: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "デスクトップアプリを更新してください" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "生体認証によるロック解除を使用するには、デスクトップアプリを更新するか、デスクトップの設定で指紋認証によるロック解除を無効にしてください。" + }, + "changeAtRiskPassword": { + "message": "危険なパスワードの変更" } } diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index 5da73c6755b..818d7cdcd19 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Master password hint (optional)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Copy notes" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { "message": "Fill", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "საქაღალდის ჩასწორება" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "ახალი საქაღალდე" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Generate passphrase" }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "regeneratePassword": { "message": "Regenerate password" }, @@ -454,22 +482,6 @@ "length": { "message": "სიგრძე" }, - "uppercase": { - "message": "Uppercase (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Lowercase (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Numbers (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Special characters (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "ჩართვა", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "სიტყვათა რაოდენობა" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Your web browser does not support easy clipboard copying. Copy it manually instead." }, - "verifyIdentity": { - "message": "Verify identity" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "Your vault is locked. Verify your identity to continue." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Restart registration" }, @@ -875,6 +904,9 @@ "no": { "message": "არა" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "An unexpected error has occurred." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "Show cards on Tab page" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "List card items on the Tab page for easy autofill." }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "Show identities on Tab page" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "ბუფერის გასუფთავება", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "შენახვა" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Ask to update existing login" }, @@ -1084,10 +1169,6 @@ "message": "ღია", "description": "Light color" }, - "solarizedDark": { - "message": "სოლარიზებული მუქი", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Export from" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Purchase Premium" }, - "premiumPurchaseAlert": { - "message": "You can purchase Premium membership on the bitwarden.com web vault. Do you want to visit the website now?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "დამიმახსოვრე" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "ახალი ჩანართის გახსნა" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "Login unavailable" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Lost access to all of your two-factor providers? Use your recovery code to turn off all two-factor providers from your account." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Self-hosted environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Custom environment" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "სერვერის URL" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Drag to sort" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "ტექსტი" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Username generator" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "Use this password" }, @@ -2063,12 +2163,24 @@ "message": "to create a strong unique password", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "Vault timeout action" }, "vaultTimeoutAction1": { "message": "Timeout action" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "ჩაკეტვა", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "დომენები", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +3022,20 @@ "error": { "message": "შეცდომა" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Settings have been edited" - }, - "environmentEditedClick": { - "message": "დააწკაპუნეთ აქ" - }, - "environmentEditedReset": { - "message": "to reset to pre-configured settings" - }, "serverVersion": { "message": "სერვერის ვერსია" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Log in with device" }, - "loginWithDeviceEnabledInfo": { - "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" - }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "View all log in options" }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" - }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "You will be notified once the request is approved" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3396,38 +3649,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "შემოტანა...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Active account" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Available accounts" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "No values to copy" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "ჩანაწერის სახელი" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organization is deactivated" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "You have not selected anything." }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "ავთენტიკაცია" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra wide" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index 6ab3755c8f4..c9c29611deb 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Master password hint (optional)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Copy notes" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { "message": "Fill", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Edit folder" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "New folder" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Generate passphrase" }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "regeneratePassword": { "message": "Regenerate password" }, @@ -454,22 +482,6 @@ "length": { "message": "Length" }, - "uppercase": { - "message": "Uppercase (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Lowercase (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Numbers (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Special characters (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Include", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Number of words" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Your web browser does not support easy clipboard copying. Copy it manually instead." }, - "verifyIdentity": { - "message": "Verify identity" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "Your vault is locked. Verify your identity to continue." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Restart registration" }, @@ -875,6 +904,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "An unexpected error has occurred." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "Show cards on Tab page" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "List card items on the Tab page for easy autofill." }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "Show identities on Tab page" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "Clear clipboard", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Save" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Ask to update existing login" }, @@ -1084,10 +1169,6 @@ "message": "Light", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized dark", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Export from" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Purchase Premium" }, - "premiumPurchaseAlert": { - "message": "You can purchase Premium membership on the bitwarden.com web vault. Do you want to visit the website now?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Open new tab" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "Login unavailable" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Lost access to all of your two-factor providers? Use your recovery code to turn off all two-factor providers from your account." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Self-hosted environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Custom environment" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "Server URL" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Drag to sort" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Text" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Username generator" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "Use this password" }, @@ -2063,12 +2163,24 @@ "message": "to create a strong unique password", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "Vault timeout action" }, "vaultTimeoutAction1": { "message": "Timeout action" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "Lock", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Settings have been edited" - }, - "environmentEditedClick": { - "message": "Click here" - }, - "environmentEditedReset": { - "message": "to reset to pre-configured settings" - }, "serverVersion": { "message": "Server version" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Log in with device" }, - "loginWithDeviceEnabledInfo": { - "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" - }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "View all log in options" }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" - }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "You will be notified once the request is approved" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3396,38 +3649,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Active account" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Available accounts" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "No values to copy" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organization is deactivated" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "You have not selected anything." }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra wide" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index 09e26c18b5b..c8aff3a6488 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "ಮಾಸ್ಟರ್ ಪಾಸ್ವರ್ಡ್ ಸುಳಿವು (ಐಚ್ಛಿಕ)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Copy notes" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { "message": "Fill", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "ಪಾಸ್ವರ್ಡ್ ಸುಳಿವು" - }, - "enterEmailToGetHint": { - "message": "ವಿಸ್ತರಣೆಯನ್ನು ಪ್ರಾರಂಭಿಸಲು ಮೆನುವಿನಲ್ಲಿರುವ ಬಿಟ್‌ವಾರ್ಡೆನ್ ಐಕಾನ್ ಟ್ಯಾಪ್ ಮಾಡಿ." - }, "getMasterPasswordHint": { "message": "ಮಾಸ್ಟರ್ ಪಾಸ್ವರ್ಡ್ ಸುಳಿವನ್ನು ಪಡೆಯಿರಿ" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "ಫೋಲ್ಡರ್ ಸಂಪಾದಿಸಿ" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "New folder" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Generate passphrase" }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "regeneratePassword": { "message": "ಪಾಸ್ವರ್ಡ್ ಅನ್ನು ಪುನರುತ್ಪಾದಿಸಿ" }, @@ -454,22 +482,6 @@ "length": { "message": "ಉದ್ದ" }, - "uppercase": { - "message": "Uppercase (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Lowercase (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Numbers (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Special characters (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Include", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "ಪದಗಳ ಸಂಖ್ಯೆ" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "ನಿಮ್ಮ ವೆಬ್ ಬ್ರೌಸರ್ ಸುಲಭವಾದ ಕ್ಲಿಪ್‌ಬೋರ್ಡ್ ನಕಲು ಮಾಡುವುದನ್ನು ಬೆಂಬಲಿಸುವುದಿಲ್ಲ. ಬದಲಿಗೆ ಅದನ್ನು ಹಸ್ತಚಾಲಿತವಾಗಿ ನಕಲಿಸಿ." }, - "verifyIdentity": { - "message": "Verify identity" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "ನಿಮ್ಮ ವಾಲ್ಟ್ ಲಾಕ್ ಆಗಿದೆ. ಮುಂದುವರೆಯಲು ನಿಮ್ಮ ಮಾಸ್ಟರ್ ಪಾಸ್‌ವರ್ಡ್ ಅನ್ನು ಪರಿಶೀಲಿಸಿ." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Restart registration" }, @@ -875,6 +904,9 @@ "no": { "message": "ಇಲ್ಲ" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "ಅನಿರೀಕ್ಷಿತ ದೋಷ ಸಂಭವಿಸಿದೆ." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "Show cards on Tab page" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "List card items on the Tab page for easy autofill." }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "Show identities on Tab page" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "ಕ್ಲಿಪ್‌ಬೋರ್ಡ್ ತೆರವುಗೊಳಿಸಿ", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "ಹೌದು, ಈಗ ಉಳಿಸಿ" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Ask to update existing login" }, @@ -1084,10 +1169,6 @@ "message": "ಬೆಳಕು", "description": "Light color" }, - "solarizedDark": { - "message": "ಡಾರ್ಕ್ ಸೌರ", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Export from" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "ಪ್ರೀಮಿಯಂ ಖರೀದಿಸಿ" }, - "premiumPurchaseAlert": { - "message": "ನೀವು ಬಿಟ್ವಾರ್ಡೆನ್.ಕಾಮ್ ವೆಬ್ ವಾಲ್ಟ್ನಲ್ಲಿ ಪ್ರೀಮಿಯಂ ಸದಸ್ಯತ್ವವನ್ನು ಖರೀದಿಸಬಹುದು. ನೀವು ಈಗ ವೆಬ್‌ಸೈಟ್‌ಗೆ ಭೇಟಿ ನೀಡಲು ಬಯಸುವಿರಾ?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "ನನ್ನನ್ನು ನೆನಪಿನಲ್ಲಿ ಇಡು" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "ಪರಿಶೀಲನೆ ಕೋಡ್ ಇಮೇಲ್ ಅನ್ನು ಮತ್ತೆ ಕಳುಹಿಸಿ" }, "useAnotherTwoStepMethod": { "message": "ಮತ್ತೊಂದು ಎರಡು-ಹಂತದ ಲಾಗಿನ್ ವಿಧಾನವನ್ನು ಬಳಸಿ" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "ನಿಮ್ಮ ಯುಬಿಕಿಯನ್ನು ನಿಮ್ಮ ಕಂಪ್ಯೂಟರ್‌ನ ಯುಎಸ್‌ಬಿ ಪೋರ್ಟ್ಗೆ ಸೇರಿಸಿ, ನಂತರ ಅದರ ಗುಂಡಿಯನ್ನು ಸ್ಪರ್ಶಿಸಿ." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "ಹೊಸ ಟ್ಯಾಬ್ ತೆರೆಯಿರಿ" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "WebAuthn ಅನ್ನು ಪ್ರಮಾಣಿಕರಿಸು" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "ಲಾಗಿನ್ ಲಭ್ಯವಿಲ್ಲ" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "ಎರಡು ಹಂತದ ಲಾಗಿನ್ ಆಯ್ಕೆಗಳು" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "ನಿಮ್ಮ ಎಲ್ಲಾ ಎರಡು ಅಂಶ ಪೂರೈಕೆದಾರರಿಗೆ ಪ್ರವೇಶವನ್ನು ಕಳೆದುಕೊಂಡಿದ್ದೀರಾ? ನಿಮ್ಮ ಖಾತೆಯಿಂದ ಎಲ್ಲಾ ಎರಡು ಅಂಶ ಪೂರೈಕೆದಾರರನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲು ನಿಮ್ಮ ಮರುಪಡೆಯುವಿಕೆ ಕೋಡ್ ಬಳಸಿ." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "ಸ್ವಯಂ ಆತಿಥೇಯ ಪರಿಸರ" }, - "selfHostedEnvironmentFooter": { - "message": "ನಿಮ್ಮ ಆನ್-ಪ್ರಮೇಯ ಹೋಸ್ಟ್ ಮಾಡಿದ ಬಿಟ್‌ವಾರ್ಡೆನ್ ಸ್ಥಾಪನೆಯ ಮೂಲ URL ಅನ್ನು ನಿರ್ದಿಷ್ಟಪಡಿಸಿ." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "ಕಸ್ಟಮ್ ಪರಿಸರ" }, - "customEnvironmentFooter": { - "message": "ಸುಧಾರಿತ ಬಳಕೆದಾರರಿಗಾಗಿ. ನೀವು ಪ್ರತಿ ಸೇವೆಯ ಮೂಲ URL ಅನ್ನು ಸ್ವತಂತ್ರವಾಗಿ ನಿರ್ದಿಷ್ಟಪಡಿಸಬಹುದು." - }, "baseUrl": { "message": "ಸರ್ವರ್ URL" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "ವಿಂಗಡಿಸಲು ಎಳೆಯಿರಿ" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "ಪಠ್ಯ" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Username generator" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "Use this password" }, @@ -2063,12 +2163,24 @@ "message": "to create a strong unique password", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "ವಾಲ್ಟ್ ಸಮಯ ಮೀರುವ ಕ್ರಿಯೆ" }, "vaultTimeoutAction1": { "message": "Timeout action" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "ಲಾಕ್‌", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "ಹೊರತುಪಡಿಸಿದ ಡೊಮೇನ್ಗಳು" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Settings have been edited" - }, - "environmentEditedClick": { - "message": "Click here" - }, - "environmentEditedReset": { - "message": "to reset to pre-configured settings" - }, "serverVersion": { "message": "Server version" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Log in with device" }, - "loginWithDeviceEnabledInfo": { - "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" - }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "View all log in options" }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" - }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "You will be notified once the request is approved" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3396,38 +3649,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Active account" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Available accounts" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "No values to copy" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organization is deactivated" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "You have not selected anything." }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra wide" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index d6da55f600d..cd54ac47506 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "마스터 비밀번호 힌트 (선택)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "\"조직\"에 가입하기" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "노트 복사" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { "message": "채우기", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "계정 이메일 주소를 입력하세요. 그 주소로 비밀번호 힌트가 전송될 것 입니다." }, - "passwordHint": { - "message": "비밀번호 힌트" - }, - "enterEmailToGetHint": { - "message": "마스터 비밀번호 힌트를 받으려면 계정의 이메일 주소를 입력하세요." - }, "getMasterPasswordHint": { "message": "마스터 비밀번호 힌트 얻기" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "폴더 편집" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "새 폴더" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "암호 생성" }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "regeneratePassword": { "message": "비밀번호 재생성" }, @@ -454,22 +482,6 @@ "length": { "message": "길이" }, - "uppercase": { - "message": "대문자 (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "소문자 (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "숫자 (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "특수 문자 (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "포함", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "특수 문자 포함", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "단어 수" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "사용하고 있는 웹 브라우저가 쉬운 클립보드 복사를 지원하지 않습니다. 직접 복사하세요." }, - "verifyIdentity": { - "message": "신원 확인" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "보관함이 잠겨 있습니다. 마스터 비밀번호를 입력하여 계속하세요." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Bitwarden에 로그인" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "등록 재시작" }, @@ -875,6 +904,9 @@ "no": { "message": "아니오" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "예기치 못한 오류가 발생했습니다." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "보관함에 항목이 없을 경우 추가하라는 메시지를 표시합니다. 모든 로그인된 계정에 적용됩니다." }, - "showCardsInVaultView": { - "message": "보관함 보기에서 카드 자동완성 제안를 표시" + "showCardsInVaultViewV2": { + "message": "보관함 보기에서 언제나 카드 자동 완성 제안을 표시" }, "showCardsCurrentTab": { "message": "탭 페이지에 카드 표시" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "간편한 자동완성을 위해 탭에 카드 항목들을 나열" }, - "showIdentitiesInVaultView": { - "message": "보관함 보기에서 신원들의 자동완성 제안을 표시" + "showIdentitiesInVaultViewV2": { + "message": "보관함 보기에서 언제나 신원의 자동 완성 제안을 표시" }, "showIdentitiesCurrentTab": { "message": "탭 페이지에 신원들을 표시" @@ -1005,7 +1037,10 @@ "message": "간편한 자동완성을 위해 탭에 신원 항목들을 나열" }, "clickToAutofillOnVault": { - "message": "Click items to autofill on Vault view" + "message": "보관함 보기에서 항목을 클릭하여 자동 완성" + }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" }, "clearClipboard": { "message": "클립보드 비우기", @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "예, 지금 저장하겠습니다." }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "현재 로그인으로 업데이트할 건지 묻기" }, @@ -1084,10 +1169,6 @@ "message": "밝은 테마", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized Dark", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "~(으)로부터 내보내기" }, @@ -1107,10 +1188,10 @@ "message": "이 비밀번호는 이 파일을 파일 내보내거나, 가져오는데 사용됩니다." }, "accountRestrictedOptionDescription": { - "message": "계정의 사용자 이름과 마스터 비밀번호에서 파생된 계정 암호화 키를 사용하여 내보내기를 암호화하고, 현재 Bitwarden계정으로 가져오기를 제한해보세요. " + "message": "내보내기를 당신의 계정의 사용자이름과 마스터비밀번호로부터 파생된 계정 암호화 키를 사용하여 암호화하고, 현재의 Bitwarden 계정으로만 가져오도록 제한합니다." }, "passwordProtectedOptionDescription": { - "message": "파일 비밀번호를 설정하여, 내보내기를 암호화하고, 해독에 그 파일 비밀번호를 사용하는 Bitwarden계정에 가져오세요." + "message": "파일에 비밀번호를 설정하여 내보내기를 암호화하고, 어느 Bitwarden 계정으로든 그 비밀번호로 해독하여 가져오기 합니다." }, "exportTypeHeading": { "message": "내보내기 유형" @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "프리미엄 멤버십 구입" }, - "premiumPurchaseAlert": { - "message": "bitwarden.com 웹 보관함에서 프리미엄 멤버십을 구입할 수 있습니다. 지금 웹 사이트를 방문하시겠습니까?" - }, "premiumPurchaseAlertV2": { "message": "Bitwarden 웹 앱의 계정 설정에서 프리미엄에 대한 결제를 할 수 있습니다." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "기억하기" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "인증 코드 이메일 다시 보내기" }, "useAnotherTwoStepMethod": { "message": "다른 2단계 인증 사용" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "YubiKey를 컴퓨터의 USB 포트에 삽입하고 이 버튼을 누르세요." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "새 탭 열기" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "WebAuthn 인증" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "로그인 불가능" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "2단계 인증 옵션" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "모든 2단계 인증을 사용할 수 없는 상황인가요? 복구 코드를 사용하여 계정의 모든 2단계 인증을 비활성화할 수 있습니다." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "자체 호스팅 환경" }, - "selfHostedEnvironmentFooter": { - "message": "온-프레미스 Bitwarden이 호스팅되고 있는 서버의 기본 URL을 지정하세요." - }, "selfHostedBaseUrlHint": { "message": "온-프레미스 Bitwarden이 호스팅되고 있는 서버의 기본 URL을 지정하세요. 예: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "사용자 지정 환경" }, - "customEnvironmentFooter": { - "message": "고급 사용자 전용 설정입니다. 각 서비스의 기본 URL을 개별적으로 지정할 수 있습니다." - }, "baseUrl": { "message": "서버 URL" }, @@ -1478,7 +1572,7 @@ "message": "카드를 제안으로 표시" }, "showInlineMenuOnIconSelectionLabel": { - "message": "아이콘을 선택하면 제안이 표시됩니다." + "message": "아이콘을 선택할 때 제안을 표시" }, "showInlineMenuOnFormFieldsDescAlt": { "message": "로그인한 모든 계정에 적용" @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "드래그하여 정렬" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "텍스트" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "사용자 이름 생성기" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "이 비밀번호 사용" }, @@ -2063,12 +2163,24 @@ "message": "강력한 고유 비밀번호를 만드세요", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "보관함 시간 제한 초과시 동작" }, "vaultTimeoutAction1": { "message": "시간초과 시 행동" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "잠금", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "도메인", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "제외된 도메인" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "BItwarden은 로그인한 모든 계정에 대해 이러한 도메인에 대한 로그인 세부 정보를 저장하도록 요청하지 않습니다. 변경 사항을 적용하려면 페이지를 새로 고쳐야 합니다" }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "웹사이트 $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "제외된 도메인 변경 사항 저장됨" }, @@ -2789,6 +3022,20 @@ "error": { "message": "오류" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "아이디 생성" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "$SERVICENAME$ 마스크된 이메일 계정 ID를 얻을 수 없습니다.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "설정이 편집되었습니다" - }, - "environmentEditedClick": { - "message": "여기를 클릭하세요." - }, - "environmentEditedReset": { - "message": "사전 구성된 설정으로 재설정하려면" - }, "serverVersion": { "message": "서버 버전" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "마스터 비밀번호로 로그인" }, - "loggingInAs": { - "message": "다음으로 로그인 중" - }, - "notYou": { - "message": "본인이 아닌가요?" - }, "newAroundHere": { "message": "새로 찾아오셨나요?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "기기로 로그인" }, - "loginWithDeviceEnabledInfo": { - "message": "기기로 로그인하려면 Bitwarden 모바일 앱 설정에서 설정해야 합니다. 다른 방식이 필요하신가요?" - }, "fingerprintPhraseHeader": { "message": "지문 구절" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "모든 로그인 방식 보기" }, - "viewAllLoginOptionsV1": { - "message": "모든 로그인 옵션 보기" - }, "notificationSentDevice": { "message": "기기에 알림이 전송되었습니다." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "기기에 알림이 전송되었습니다." }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "반드시 계정이 잠금 해제되었고, 지문 구절이 다른 기기에서 일치하는지 확인해주세요." - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "요청이 승인되면 알림을 받게 됩니다" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "로그인 시작" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "노출된 마스터 비밀번호" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "관리자 인증 필요" }, - "approveWithMasterPassword": { - "message": "마스터 비밀번호로 승인" - }, "ssoIdentifierRequired": { "message": "조직의 SSO 식별자가 필요합니다" }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "요청이 관리자에게 전송되었습니다." }, - "youWillBeNotifiedOnceApproved": { - "message": "승인되면 알림을 받게 됩니다." - }, "troubleLoggingIn": { "message": "로그인에 문제가 있나요?" }, @@ -3396,38 +3649,6 @@ "message": "토글이 붕괴됨", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "데이터를 Bitwarden으로 가져오시겠습니까?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "LastPass 데이터를 보호하고 Bitwarden으로 가져오시겠습니까?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "암호화되지 않은 파일로 저장", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Bitwarden으로 가져오기", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "가져오는 중...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "데이터 가져오기 성공!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "가져오는 중 오류가 발생했습니다. 자세한 내용은 콘솔을 확인하세요.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "가져오기 중에 네트워크 오류가 발생했습니다.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "도메인 별칭" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "계정 활성화" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "사용 가능한 계정" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "자동 완성 제안" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "이 사이트에서 자동으로 작성할 로그인 항목 저장" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "복사할 값이 없습니다" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "항목 이름" }, - "cannotRemoveViewOnlyCollections": { - "message": "보기 권한만 있는 컬렉션은 제거할 수 없습니다: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "조직이 비활성화되었습니다" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$을 위로 이동했습니다. 위치: $INDEX$ / $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "아무것도 선택하지 않았습니다." }, - "movedItemsToOrg": { - "message": "선택한 항목이 $ORGNAME$(으)로 이동됨", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "항목들이 $ORGNAME$로 이동했습니다", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "텍스트 Send" }, - "bitwardenNewLook": { - "message": "Bitwarden이 새로운 모습으로 돌아왔습니다!" - }, - "bitwardenNewLookDesc": { - "message": "보관함 탭에서 자동 완성하고 검색하는 것이 그 어느 때보다 쉽고 직관적입니다. 둘러보세요!" - }, "accountActions": { "message": "계정 작업" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "아이템을 수정할 권한이 없습니다." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "인증 중" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "매우 넓게" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "보기 권한만 있는 컬렉션은 제거할 수 없습니다: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index eaf1cb9f9db..8bf8a8f7518 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Pagrindinio slaptažodžio užuomina (neprivaloma)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Copy notes" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { "message": "Fill", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Slaptažodžio užuomina" - }, - "enterEmailToGetHint": { - "message": "Įveskite savo paskyros el. pašto adresą, kad gautumėte pagrindinio slaptažodžio užuominą." - }, "getMasterPasswordHint": { "message": "Gauti pagrindinio slaptažodžio užuominą" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Redaguoti aplankalą" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "New folder" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Generate passphrase" }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "regeneratePassword": { "message": "Generuoti slaptažodį iš naujo" }, @@ -454,22 +482,6 @@ "length": { "message": "Ilgis" }, - "uppercase": { - "message": "Didžiosiomis (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Mažosiomis (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Skaitmenys (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Specialieji simboliai (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Include", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Žodžių skaičius" }, @@ -530,7 +538,7 @@ "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { - "message": "Enterprise policy requirements have been applied to your generator options.", + "message": "Jūsų generatoriaus parinktims taikomi įmonės politikos reikalavimai.", "description": "Indicates that a policy limits the credential generator screen." }, "searchVault": { @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Jūsų žiniatinklio naršyklė nepalaiko automatinio kopijavimo. Vietoj to nukopijuokite rankiniu būdu." }, - "verifyIdentity": { - "message": "Patvirtinti tapatybę" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "Jūsų saugykla užrakinta. Norėdami tęsti, patikrinkite pagrindinį slaptažodį." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Restart registration" }, @@ -875,6 +904,9 @@ "no": { "message": "Ne" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "Įvyko netikėta klaida." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Paprašykite pridėti elementą, jei jo nerasta Jūsų saugykloje. Taikoma visoms prisijungusioms paskyroms." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Visada rodyti korteles kaip automatinio pildymo pasiūlymus saugyklos rodinyje" }, "showCardsCurrentTab": { "message": "Rodyti korteles skirtuko puslapyje" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Pateikti kortelių elementų skirtuko puslapyje sąrašą, kad būtų lengva automatiškai užpildyti." }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Visada rodyti tapatybes kaip automatinio pildymo pasiūlymus saugyklos rodinyje" }, "showIdentitiesCurrentTab": { "message": "Rodyti tapatybes skirtuko puslapyje" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "Išvalyti iškarpinę", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Išsaugoti" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Paprašyti atnaujinti esamą prisijungimą" }, @@ -1084,10 +1169,6 @@ "message": "Šviesi", "description": "Light color" }, - "solarizedDark": { - "message": "Saulėtas tamsą", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Export from" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Įsigyti Premium" }, - "premiumPurchaseAlert": { - "message": "Galite įsigyti „Premium“ narystę „bitwarden.com“ žiniatinklio saugykloje. Ar norite apsilankyti svetainėje dabar?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Prisiminti mane" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Pakartotinai atsiųsti patvirtinimo koda el. paštu" }, "useAnotherTwoStepMethod": { "message": "Naudoti dar vieną dviejų žingsnių prisijungimo metodą" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Įkišk YubiKey į savo kompiuterio USB prievadą, tada paliesk jo mygtuką." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Atidaryti naują skirtuką" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "Autentifikuoti WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "Prisijungimas nepasiekiamas" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Dviejų žingsnių prisijungimo parinktys" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Praradai prieigą prie visų savo dviejų veiksnių teikėjų? Naudok atkūrimo kodą, kad iš savo paskyros išjungtum visus dviejų veiksnių teikėjus." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Savarankiškai sukurta aplinka" }, - "selfHostedEnvironmentFooter": { - "message": "Nurodykite vietinio priglobto „Bitwarden“ diegimo bazinį URL." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Individualizuota aplinka" }, - "customEnvironmentFooter": { - "message": "Pažengusiems naudotojams. Galite nurodyti kiekvienos paslaugos pagrindinį URL adresą atskirai." - }, "baseUrl": { "message": "Serverio URL" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Rūšiuok, kad surūšiuotum" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Tekstas" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Username generator" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "Use this password" }, @@ -2063,12 +2163,24 @@ "message": "to create a strong unique password", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "Vault skirtojo laiko veiksmas" }, "vaultTimeoutAction1": { "message": "Timeout action" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "Užrakinti", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domenai", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "Išskirti domenai" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "„Bitwarden“ neprašys išsaugoti prisijungimo detalių šiems domenams, visose prisijungusiose paskyrose. Turite atnaujinti puslapį, kad pokyčiai pradėtų galioti." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Klaida" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generuoti vartotojo vardą" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Nepavyksta gauti „$SERVICENAME$“ užmaskuoto el. pašto paskyros ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Nustatymai buvo paredaguoti" - }, - "environmentEditedClick": { - "message": "Spauskite čia" - }, - "environmentEditedReset": { - "message": "iš naujo nustatyti iš anksto sukonfigūruotus nustatymus" - }, "serverVersion": { "message": "Serverio versija" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Prisijungti su pagrindiniu slaptažodžiu" }, - "loggingInAs": { - "message": "Prisijungimas kaip" - }, - "notYou": { - "message": "Ne jūs?" - }, "newAroundHere": { "message": "Ar jūs naujas čia?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Prisijunkite naudodami įrenginį" }, - "loginWithDeviceEnabledInfo": { - "message": "Prisijungti su įrenginiu turi būti nustatyta Bitwarden aplikacijos nustatymuose. Reikia kito pasirinkimo?" - }, "fingerprintPhraseHeader": { "message": "Pirštų atspaudų frazė" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "View all log in options" }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" - }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "You will be notified once the request is approved" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Pradėtas prisijungimas" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Prašyti administratoriaus patvirtinimo" }, - "approveWithMasterPassword": { - "message": "Patvirtinti su pagrindiniu slaptažodžiu" - }, "ssoIdentifierRequired": { "message": "Organizacijos SSO identifikatorius yra reikalingas." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Tavo prašymas išsiųstas administratoriui." }, - "youWillBeNotifiedOnceApproved": { - "message": "Tu būsi praneštas (-a), kai bus patvirtinta." - }, "troubleLoggingIn": { "message": "Problemos prisijungiant?" }, @@ -3396,38 +3649,6 @@ "message": "Perjungti sutrumpinimą", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Importuoti duomenis į Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Apsaugoti LastPass duomenis ir importuoti į Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Išsaugoti kaip neužšifruotą failą", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Importuoti į Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importuojama...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Duomenys sėkmingai importuoti.", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Klaida importuojant. Išsamesnės informacijos patikrink konsolėje.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Importuojant įvyko tinklo klaida.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Domeno slapyvardis" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Active account" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Pasiekiamos paskyros" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "No values to copy" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Elemento pavadinimas" }, - "cannotRemoveViewOnlyCollections": { - "message": "Negalite pašalinti kolekcijų su Peržiūrėti tik leidimus: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organization is deactivated" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "You have not selected anything." }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra wide" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "Negalite pašalinti kolekcijų su Peržiūrėti tik leidimus: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index 2c2a9c3c69c..7b5f077f69d 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Galvenās paroles norāde (nav nepieciešama)" }, + "passwordStrengthScore": { + "message": "Paroles stipruma novērtējums $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Pievienoties apvienībai" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Ievietot piezīmes starpliktuvē" }, + "copy": { + "message": "Ievietot starpliktuvē", + "description": "Copy to clipboard" + }, "fill": { "message": "Aizpildīt", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Jāievada sava konta e-pasta adrese, un paroles norāde tiks nosūtīta" }, - "passwordHint": { - "message": "Paroles norāde" - }, - "enterEmailToGetHint": { - "message": "Norādīt konta e-pasta adresi, lai saņemtu galvenās paroles norādi." - }, "getMasterPasswordHint": { "message": "Saņemt galvenās paroles norādi" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Labot mapi" }, + "editFolderWithName": { + "message": "Labot mapi: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "Jauna mape" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Izveidot paroles vārdkopu" }, + "passwordGenerated": { + "message": "Parole izveidota" + }, + "passphraseGenerated": { + "message": "Paroles vārdkopa izveidota" + }, + "usernameGenerated": { + "message": "Lietotājvārds izveidots" + }, + "emailGenerated": { + "message": "E-pasta adrese izveidota" + }, "regeneratePassword": { "message": "Pārizveidot paroli" }, @@ -454,22 +482,6 @@ "length": { "message": "Garums" }, - "uppercase": { - "message": "Lielie burti (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Mazie burti (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Cipari (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Īpašās rakstzīmes (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Iekļaut", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Iekļaut īpašās rakstzīmes", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Vārdu skaits" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Pārlūks neatbalsta vienkāršo ievietošanu starpliktuvē. Tā vietā tas jāievieto starpliktuvē pašrocīgi." }, - "verifyIdentity": { - "message": "Identitātes apliecināšana" + "verifyYourIdentity": { + "message": "Apliecināt savu identitāti" + }, + "weDontRecognizeThisDevice": { + "message": "Mēs neatpazīstam šo ierīci. Jāievada kods, kas tika nosūtīts e-pastā, lai apliecinātu savu identitāti." + }, + "continueLoggingIn": { + "message": "Turpināt pieteikšanos" }, "yourVaultIsLocked": { "message": "Glabātava ir aizslēgta. Jāapliecina sava identitāte, lai turpinātu." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Pieteikties Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Jāievada e-pastā nosūtītais kods" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Jāievada kods no savas autentificētājlietotnes" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Jāpiespiež sava YubiKey ierīce, lai autentificētu" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Ir nepieciešama Duo divpakāpju pieteikšanās, lai pieteiktos savā kontā. Jāseko zemāk esošajām norādēm, lai pabeigtu pieteikšanos." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Jāseko zemāk esošajām norādēm, lai pabeigtu pieteikšanos." + }, "restartRegistration": { "message": "Sākt reģistrēšanos no jauna" }, @@ -875,6 +904,9 @@ "no": { "message": "Nē" }, + "location": { + "message": "Atrašanās vieta" + }, "unexpectedError": { "message": "Ir radusies neparedzēta kļūda." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Vaicāt, vai pievienot vienumu, ja glabātavā tāds nav atrodams. Attiecas uz visiem kontiem, kuri ir pieteikušies." }, - "showCardsInVaultView": { - "message": "Rādīt kartes kā automātiskās aizpildes ieteikumus glabātavas skatā" + "showCardsInVaultViewV2": { + "message": "Glabātavas skatā vienmēr rādīt kartes kā automātiskās aizpildes ieteikumus" }, "showCardsCurrentTab": { "message": "Rādīt kartes cilnes lapā" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Attēlot kartes ciļņu lapā vieglākai aizpildīšanai." }, - "showIdentitiesInVaultView": { - "message": "Rādīt identitātes kā automātiskās aizpildes ieteikumus glabātavas skatā" + "showIdentitiesInVaultViewV2": { + "message": "Glabātavas skatā vienmēr rādīt identitātes kā automātiskās aizpildes ieteikumus" }, "showIdentitiesCurrentTab": { "message": "Rādīt identitātes cilnes pārskatā" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Glabātavas skatā jāklikšķina uz vienumiem, lai automātiski aizpildītu" }, + "clickToAutofill": { + "message": "Klikšķināt uz vienumiem automātiskās aizpildes ieteikumos, lai aizpildītu" + }, "clearClipboard": { "message": "Notīrīt starpliktuvi", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Jā, saglabāt" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saglabāts Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ atjaunināts Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Saglabāt kā jaunu pieteikšanās vienumu", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Atjaunināt pieteikšanās vienumu", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Saglabāt pieteikšanās vienumu?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Atjaunināt esošo pieteikšanās vienumu?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Pieteikšanās vienums saglabāts", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Pieteikšanās vienums atjaunināts", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Kļūda saglabāšanas laikā", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Ak nē! Mēs nevarējā šo saglabāt. Jāmēģina pašrocīgi ievadīt informāciju.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Vaicāt atjaunināt esošu pieteikšanās vienumu" }, @@ -1084,10 +1169,6 @@ "message": "Gaišs", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized Dark", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Izgūt no" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Iegādāties Premium" }, - "premiumPurchaseAlert": { - "message": "Premium dalību ir iespējams iegādāties bitwarden.com tīmekļa glabātavā. Vai tagad apmeklēt tīmekļvietni?" - }, "premiumPurchaseAlertV2": { "message": "Premium var iegādāties Bitwarden tīmekļa lietotnē sava konta iestatījumos." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Atcerēties mani" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Šajā ierīcē 30 dienas vairs nevaicāt" + }, "sendVerificationCodeEmailAgain": { "message": "Sūtīt apstiprinājuma koda e-pastu vēlreiz" }, "useAnotherTwoStepMethod": { "message": "Izmantot citu divpakāpju pieteikšanās veidu" }, + "selectAnotherMethod": { + "message": "Atlasīt citu veidu", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Izmantot savu atkopes kodu" + }, "insertYubiKey": { "message": "Ievieto savu YubiKey datora USB ligzdā un pieskaries tā pogai!" }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Atvērt jaunu cilni" }, + "openInNewTab": { + "message": "Atvērt jaunā cilnē" + }, "webAuthnAuthenticate": { "message": "Autentificēt WebAuthn" }, + "readSecurityKey": { + "message": "Nolasīt drošības atslēgu" + }, + "awaitingSecurityKeyInteraction": { + "message": "Gaida mijiedarbību ar drošības atslēgu..." + }, "loginUnavailable": { "message": "Pieteikšanās nav pieejama" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Divpakāpju pieteikšanās iespējas" }, + "selectTwoStepLoginMethod": { + "message": "Atlasīt divpakāpju pieteikšanās veidu" + }, "recoveryCodeDesc": { "message": "Zaudēta piekļuve visiem divpakāpju nodrošinātājiem? Izmanto atkopšanas kodus, lai atspējotu visus sava konta divpakāpju nodrošinātājus!" }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Pašuzturēta vide" }, - "selfHostedEnvironmentFooter": { - "message": "Norādīt pašuzstādīta Bitwarden pamata URL." - }, "selfHostedBaseUrlHint": { "message": "Jānorāda sava pašizvietotā Bitward servera pamata URL. Piemērs: https://bitwarden.uznemums.lv" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Pielāgota vide" }, - "customEnvironmentFooter": { - "message": "Pieredzējušiem lietotājiem. Ir iespējams norādīt URL katram pakalpojumam atsevišķi." - }, "baseUrl": { "message": "Servera URL" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Vilkt, lai kārtotu" }, + "dragToReorder": { + "message": "Vilkt, lai pārkārtotu" + }, "cfTypeText": { "message": "Teksts" }, @@ -1898,7 +1995,7 @@ "description": "URI match detection for autofill." }, "defaultMatchDetection": { - "message": "Noklusētā atbilstības noteikšana", + "message": "Noklusējuma atbilstības noteikšana", "description": "Default URI match detection for autofill." }, "toggleOptions": { @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Lietotājvārdu veidotājs" }, + "useThisEmail": { + "message": "Izmantot šo e-pasta adresi" + }, "useThisPassword": { "message": "Izmantot šo paroli" }, @@ -2063,12 +2163,24 @@ "message": ", lai izveidotu spēcīgu un vienreizēju paroli", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Glabātavas pielāgošana" + }, "vaultTimeoutAction": { "message": "Glabātavas noildzes darbība" }, "vaultTimeoutAction1": { "message": "Noildzes darbība" }, + "newCustomizationOptionsCalloutTitle": { + "message": "Jaunas pielāgošanas iespējas" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Savu glabātavas pieredzi var pielāgot ar ātrām kopēšanas darbībām, ciešo izkārtojumu un vēl." + }, + "newCustomizationOptionsCalloutLink": { + "message": "Apskatīt visus izskata iestatījumus" + }, "lock": { "message": "Slēgt", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domēna vārdi", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Liegtie domēna vārdi" + }, + "learnMoreAboutBlockedDomains": { + "message": "Uzzināt vairāk par liegtajiem domēna vārdiem" + }, "excludedDomains": { "message": "Izņēmuma domēni" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden nevaicās saglabāt pieteikšanās datus visiem šī domēna kontiem, kuri ir pieteikušies. Ir jāpārlādē lapa, lai iedarbotos izmaiņas." }, + "blockedDomainsDesc": { + "message": "Automātiskā aizpilde un citas saistītās iespējas šajās tīmekļvietnēs netiks piedāvātas. Ir jāatsvaidzina lapa, lai izmaiņas iedarbotos." + }, + "autofillBlockedNoticeV2": { + "message": "Automātiskā aizpilde šajā tīmekļvietnē ir liegta." + }, + "autofillBlockedNoticeGuidance": { + "message": "To var mainīt iestatījumos" + }, + "change": { + "message": "Mainīt" + }, + "changeButtonTitle": { + "message": "Mainīt paroli - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "Riskam pakļautās paroles" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ pieprasa mainīt vienu paroli, jo tā ir pakļauta riskam.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ pieprasa mainīt $COUNT$ paroles, jo tās ir pakļautas riskam.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Apvienības pieprasa mainīt $COUNT$ paroles, jo tās ir pakļautas riskam.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Pārskatīt un mainīt vienu riskam pakļautu paroli" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Pārskatīt un mainīt $COUNT$ riskam pakļautās paroles", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Mainīt riskam pakļautās paroles ātrāk" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Jāatjaunina savi iestatījumi, lai varētu veikli aizpildīt paroles automātiski un izveidot jaunas" + }, + "reviewAtRiskLogins": { + "message": "Pārskatīt riskam pakļautos pieteikšanās vienumus" + }, + "reviewAtRiskPasswords": { + "message": "Pārskatīt riskam pakļautās paroles" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Apvienības paroles ir pakļautas riskam, jo tās ir vājas, atkārtoti izmantotas un/vai noplūdušas.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Riskam pakļauto pieteikšanās vienumu saraksta attēlojums" + }, + "generatePasswordSlideDesc": { + "message": "Riskam pakļauto vienumu vietnē ar automātiskās aizpildes izvēlni var ātri izveidot stipru, neatkārtojamu paroli.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Bitwarden automātiskās izvēlnes attēlojums, kurā ir redzama izveidota parole" + }, + "updateInBitwarden": { + "message": "Atjaunināt Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden tad vaicās atjaunināt paroli paroļu pārvaldniekā.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Bitwarden paziņojuma, kas aicina lietotāju atjaunināt pieteikšanās vienumu, attēlojums" + }, + "turnOnAutofill": { + "message": "Ieslēgt automātisko aizpildi" + }, + "turnedOnAutofill": { + "message": "Automātiskā aizpilde ieslēgta" + }, + "dismiss": { + "message": "Noraidīt" + }, "websiteItemLabel": { "message": "Tīmekļvietne $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Liegtā domēna vārda izmaiņas sglabātas" + }, "excludedDomainsSavedSuccess": { "message": "Saglabātas vērā neņemto domēna vārdu izmaiņas" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Kļūda" }, + "decryptionError": { + "message": "Atšifrēšanas kļūda" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nevarēja atšifrēt zemāk uzskaitītos glabātavas vienumus." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Jāsazinās ar klientu atbalstu,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "lai izvairītos no papildu datu zaudējumiem.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Izveidot lietotājvārdu" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ atteica pieprasījumu. Lūgums sazināties ar savu pakalpojma nodrošinātāju, lai iegūtu palīdzību.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ atteica pieprasījumu: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Neizdevās iegūt $SERVICENAME$ aizsegta e-pasta konta Id.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Iestatījumi ir izmainīti" - }, - "environmentEditedClick": { - "message": "Klikšķināt šeit" - }, - "environmentEditedReset": { - "message": "lai atiestatītu pirmsuzstādītos iestatījumus" - }, "serverVersion": { "message": "Servera versija" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Pieteikties ar galveno paroli" }, - "loggingInAs": { - "message": "Piesakās kā" - }, - "notYou": { - "message": "Tas neesi Tu?" - }, "newAroundHere": { "message": "Jauns šeit?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Pieteikties ar ierīci" }, - "loginWithDeviceEnabledInfo": { - "message": "Ir jāuzstāda pieteikšanās ar ierīci Bitwarden lietotnes iestatījumos. Nepieciešama cita iespēja?" - }, "fingerprintPhraseHeader": { "message": "Atpazīšanas vārdkopa" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "Skatīt visas pieteikšanās iespējas" }, - "viewAllLoginOptionsV1": { - "message": "Skatīt visas pieteikšanās iespējas" - }, "notificationSentDevice": { "message": "Uz ierīci ir nosūtīts paziņojums." }, + "notificationSentDevicePart1": { + "message": "Bitwarden jāatslēdz savā ierīcē vai" + }, + "notificationSentDeviceAnchor": { + "message": "tīmekļa lietotnē" + }, + "notificationSentDevicePart2": { + "message": "Pirms apstiprināšanas jāpārliecinās, ka pirkstu nospieduma vārdkopa atbilst zemāk esošajai." + }, "aNotificationWasSentToYourDevice": { "message": "Uz ierīci tika nosūtīts paziņojums" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Lūgums pārliecināties, ka konts ir atslēgts un atpazīšanas vārdkopa ir tāda pati arī otrā ierīcē" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "Tiks paziņots, tiklīdz pieprasījums būs apstiprināts" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Uzsākta pieteikšanās" }, + "logInRequestSent": { + "message": "Pieprasījums nosūtīts" + }, "exposedMasterPassword": { "message": "Noplūdusi galvenā parole" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Pieprasīt pārvaldītāja apstiprinājumu" }, - "approveWithMasterPassword": { - "message": "Apstiprināt ar galveno paroli" - }, "ssoIdentifierRequired": { "message": "Ir nepieciešams apvienības SSO identifikators." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Pieprasījums tika nosūtīts pārvaldītājam." }, - "youWillBeNotifiedOnceApproved": { - "message": "Tiks saņemts paziņojums, tiklīdz būs apstiprināts." - }, "troubleLoggingIn": { "message": "Neizdodas pieteikties?" }, @@ -3396,38 +3649,6 @@ "message": "Pārslēgt sakļaušanu", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Ievietot datus Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Aizsargāt LastPass datus un ievietot tos Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Saglabāt kā nešifrētu datni", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Ievietot Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Ievieto...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Dati veiksmīgi ievietoti.", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Kļūda ievietošanā. Jāpārbauda konsole, lai iegūtu vairāk informācijas.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Ievietošanas laikā atgadījās tīkla kļūda.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Aizstājdomēns" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Pašlaik izmantotais konts" }, + "bitwardenAccount": { + "message": "Bitwarden konts" + }, "availableAccounts": { "message": "Pieejamie konti" }, @@ -3979,7 +4203,10 @@ "message": "Piekļuves atslēga noņemta" }, "autofillSuggestions": { - "message": "Ieteikumi automātiskajai aizpildei" + "message": "Automātiskās aizpildes ieteikumi" + }, + "itemSuggestions": { + "message": "Ieteiktie vienumi" }, "autofillSuggestionsTip": { "message": "Saglabāt pieteikšanās vienumi, ko automātiski aizpildīt šajā vietnē" @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Ievietot starpliktuvē $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "Nav vērtību, ko ievietot starpliktuvē" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Vienuma nosaukums" }, - "cannotRemoveViewOnlyCollections": { - "message": "Nevar noņemt krājumus ar tiesībām \"Tikai skatīt\": $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Apvienība ir atspējota" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Pārkārtot tīmekļvietņu URI. Bultas uz augšu taustiņš ir izmantojams, lai pārvietotu vienumu augšup vai lejup." + }, "reorderFieldUp": { "message": "$LABEL$ pārvietots augšup, $INDEX$. no $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "Nekas nav atlasīts." }, - "movedItemsToOrg": { - "message": "Atzīmētie vienumi pārvietoti uz $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Vienumi pārvietoti uz $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Teksta Send" }, - "bitwardenNewLook": { - "message": "Bitwarden ir jauns izskats." - }, - "bitwardenNewLookDesc": { - "message": "Veikt automātisko aizpildi un meklēšanu glabātavas cilnē ir vienkāršāk un izprotamāk kā jebkad. Apskati izmaiņas!" - }, "accountActions": { "message": "Konta darbības" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "Nav nepieciešamo atļauju, lai labotu šo vienumu" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Atslēgšana ar biometriju nav pieejama, jo vispirms ir nepieciešama atslēgšana ar PIN vai paroli." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Atslēgšana ar biometriju pašlaik nav pieejama." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Atslēgšana ar biometriju nav pieejama nepareizi konfigurētu sistēmas datņu dēļ." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Atslēgšana ar biometriju nav pieejama nepareizi konfigurētu sistēmas datņu dēļ." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Atslēgšana ar biometriju nav pieejama, jo Bitwarden darbvirsmas lietotne ir aizvērta." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Atslēgšana ar biometriju nav pieejama, jo tā nav iespējota $EMAIL$ Bitwarden darbvirsmas lietotnē.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Atslēgšana ar biometriju pašlaik nav pieejama nezināma iemesla dēļ." + }, "authenticating": { "message": "Autentificē" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Ļoti plats" + }, + "sshKeyWrongPassword": { + "message": "Ievadītā parole ir nepareiza." + }, + "importSshKey": { + "message": "Ievietot" + }, + "confirmSshKeyPassword": { + "message": "Apstiprināt paroli" + }, + "enterSshKeyPasswordDesc": { + "message": "Ievadīt SSH atslēgas paroli." + }, + "enterSshKeyPassword": { + "message": "Ievadīt paroli" + }, + "invalidSshKey": { + "message": "SSH atslēga ir nederīga" + }, + "sshKeyTypeUnsupported": { + "message": "SSH atslēgas veids netiek atbalstīts" + }, + "importSshKeyFromClipboard": { + "message": "Ievietot atslēgu no starpliktuves" + }, + "sshKeyImported": { + "message": "SSH atslēga tika sekmīgi ievietota" + }, + "cannotRemoveViewOnlyCollections": { + "message": "Nevar noņemt krājumus ar tiesībām \"Tikai skatīt\": $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Lūgums atjaunināt darbvirsmas lietotni" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "Lai izmantotu atslēgšanu ar biometriju, lūgums atjaunināt darbvirsmas lietotni vai atspējot atslēgšanu ar pirkstu nospiedumu darbvirsmas iestatījumos." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index cd210c85ce1..24a096db0ef 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "പ്രാഥമിക പാസ്‌വേഡ് സൂചന (ഇഷ്ടാനുസൃതമായ)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Copy notes" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { "message": "Fill", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "പാസ്സ്‌വേഡ് സൂചനാ" - }, - "enterEmailToGetHint": { - "message": "നിങ്ങളുടെ പ്രാഥമിക പാസ്‌വേഡ് സൂചന ലഭിക്കുന്നതിന് നിങ്ങളുടെ അക്കൗണ്ട് ഇമെയിൽ വിലാസം നൽകുക." - }, "getMasterPasswordHint": { "message": "പ്രാഥമിക പാസ്‌വേഡ് സൂചന നേടുക" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "ഫോൾഡർ തിരുത്തുക" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "New folder" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Generate passphrase" }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "regeneratePassword": { "message": "പാസ്സ്‌വേഡ് വീണ്ടും സൃഷ്ടിക്കുക" }, @@ -454,22 +482,6 @@ "length": { "message": "നീളം" }, - "uppercase": { - "message": "Uppercase (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Lowercase (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Numbers (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Special characters (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Include", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "വാക്കുകളുടെ എണ്ണം" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "നിങ്ങളുടെ ബ്രൌസർ എളുപ്പമുള്ള ക്ലിപ്പ്ബോർഡ് പകർത്തൽ പിന്തുണയ്ക്കത്തില്ല. പകരം അത് സ്വമേധയാ പകർക്കുക ." }, - "verifyIdentity": { - "message": "Verify identity" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "തങ്ങളുടെ വാൾട് പൂട്ടിയിരിക്കുന്നു. തുടരുന്നതിന് നിങ്ങളുടെ പ്രാഥമിക പാസ്‌വേഡ് പരിശോധിക്കുക." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Restart registration" }, @@ -875,6 +904,9 @@ "no": { "message": "തെറ്റ്" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "ഒരു അപ്രതീക്ഷിത പിശക് സംഭവിച്ചു." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "Show cards on Tab page" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "List card items on the Tab page for easy autofill." }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "Show identities on Tab page" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "ക്ലിപ്ബോര്‍ഡ് മായ്ക്കുക", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "ശരി, ഇപ്പോൾ സംരക്ഷിക്കുക" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Ask to update existing login" }, @@ -1084,10 +1169,6 @@ "message": "ലൈറ്റ്", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized dark", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Export from" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "പ്രീമിയം വാങ്ങുക" }, - "premiumPurchaseAlert": { - "message": "നിങ്ങൾക്ക് bitwarden.com വെബ് വാൾട്ടിൽ പ്രീമിയം അംഗത്വം വാങ്ങാം. നിങ്ങൾക്ക് ഇപ്പോൾ വെബ്സൈറ്റ് സന്ദർശിക്കാൻ ആഗ്രഹമുണ്ടോ?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "എന്നെ ഓർക്കുക" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "സ്ഥിരീകരണ കോഡ് ഇമെയിൽ വഴി അയയ്ക്കുക" }, "useAnotherTwoStepMethod": { "message": "മറ്റൊരു രണ്ട് ഘട്ട ലോഗിൻ രീതി ഉപയോഗിക്കുക" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "നിങ്ങളുടെ കമ്പ്യൂട്ടറിന്റെ യു‌എസ്‌ബി പോർട്ടിലേക്ക് യുബിക്കി ഇടുക, തുടർന്ന് അതിന്റെ ബട്ടൺ അമർത്തുക." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Open new tab" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "പ്രവേശനം ലഭ്യമല്ല" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "രണ്ട്-ഘട്ട പ്രവേശനം ഓപ്ഷനുകൾ" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "നിങ്ങളുടെ രണ്ട്-ഘടക ദാതാക്കളിലേക്കുള്ള ആക്‌സസ്സ് നഷ്‌ടപ്പെട്ടോ? നിങ്ങളുടെ അക്കൗണ്ടിൽ നിന്ന് രണ്ട്-ഘടക ദാതാക്കളെ പ്രവർത്തനരഹിതമാക്കാൻ നിങ്ങളുടെ റിക്കവറി കോഡ് ഉപയോഗിക്കുക." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "സ്വയം ഹോസ്റ്റുചെയ്‌ത എൻവിയോണ്മെന്റ്" }, - "selfHostedEnvironmentFooter": { - "message": "തങ്ങളുടെ പരിസരത്ത് ചെയ്യുന്ന ബിറ്റ് വാർഡൻ ഇൻസ്റ്റാളേഷന്റെ അടിസ്ഥാന URL വ്യക്തമാക്കുക." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "ഇഷ്‌ടാനുസൃത എൻവിയോണ്മെന്റ്" }, - "customEnvironmentFooter": { - "message": "വിപുലമായ ഉപയോക്താക്കൾക്കായി. ഓരോ സേവനത്തിന്റെയും അടിസ്ഥാന URL നിങ്ങൾക്ക് സ്വതന്ത്രമായി വ്യക്തമാക്കാൻ കഴിയും." - }, "baseUrl": { "message": "സെർവർ URL" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "അടുക്കാൻ വലിച്ചിടുക" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "വാചകം" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Username generator" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "Use this password" }, @@ -2063,12 +2163,24 @@ "message": "to create a strong unique password", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "വാൾട് ടൈം ഔട്ട് ആക്ഷൻ" }, "vaultTimeoutAction1": { "message": "Timeout action" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "പൂട്ടുക", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Settings have been edited" - }, - "environmentEditedClick": { - "message": "Click here" - }, - "environmentEditedReset": { - "message": "to reset to pre-configured settings" - }, "serverVersion": { "message": "Server version" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Log in with device" }, - "loginWithDeviceEnabledInfo": { - "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" - }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "View all log in options" }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" - }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "You will be notified once the request is approved" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3396,38 +3649,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Active account" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Available accounts" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "No values to copy" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organization is deactivated" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "You have not selected anything." }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra wide" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index d156e6d6458..9a49998d3d9 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "मुख्य पासवर्डचा संकेत (पर्यायी)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Copy notes" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { "message": "Fill", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "पासवर्ड संकेत" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "मुख्य पासवर्ड संकेत मिळवा" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "फोल्डर संपादित करा" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "New folder" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Generate passphrase" }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "regeneratePassword": { "message": "पासवर्ड पुनर्जनित करा" }, @@ -454,22 +482,6 @@ "length": { "message": "लांबी" }, - "uppercase": { - "message": "Uppercase (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Lowercase (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Numbers (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Special characters (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Include", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Number of words" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Your web browser does not support easy clipboard copying. Copy it manually instead." }, - "verifyIdentity": { - "message": "ओळख सत्यापित करा" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "तुमची तिजोरीला कुलूप लावले आहे. पुढे जाण्यासाठी तुमची ओळख सत्यापित करा." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Restart registration" }, @@ -875,6 +904,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "An unexpected error has occurred." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "Show cards on Tab page" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "List card items on the Tab page for easy autofill." }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "Show identities on Tab page" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "Clear clipboard", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Save" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Ask to update existing login" }, @@ -1084,10 +1169,6 @@ "message": "Light", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized dark", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Export from" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Purchase Premium" }, - "premiumPurchaseAlert": { - "message": "You can purchase Premium membership on the bitwarden.com web vault. Do you want to visit the website now?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Open new tab" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "Login unavailable" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Lost access to all of your two-factor providers? Use your recovery code to turn off all two-factor providers from your account." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Self-hosted environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Custom environment" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "Server URL" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Drag to sort" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Text" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Username generator" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "Use this password" }, @@ -2063,12 +2163,24 @@ "message": "to create a strong unique password", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "Vault timeout action" }, "vaultTimeoutAction1": { "message": "Timeout action" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "Lock", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Settings have been edited" - }, - "environmentEditedClick": { - "message": "Click here" - }, - "environmentEditedReset": { - "message": "to reset to pre-configured settings" - }, "serverVersion": { "message": "Server version" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Log in with device" }, - "loginWithDeviceEnabledInfo": { - "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" - }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "View all log in options" }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" - }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "You will be notified once the request is approved" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3396,38 +3649,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Active account" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Available accounts" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "No values to copy" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organization is deactivated" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "You have not selected anything." }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra wide" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index 6ab3755c8f4..c9c29611deb 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Master password hint (optional)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Copy notes" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { "message": "Fill", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Edit folder" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "New folder" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Generate passphrase" }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "regeneratePassword": { "message": "Regenerate password" }, @@ -454,22 +482,6 @@ "length": { "message": "Length" }, - "uppercase": { - "message": "Uppercase (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Lowercase (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Numbers (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Special characters (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Include", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Number of words" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Your web browser does not support easy clipboard copying. Copy it manually instead." }, - "verifyIdentity": { - "message": "Verify identity" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "Your vault is locked. Verify your identity to continue." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Restart registration" }, @@ -875,6 +904,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "An unexpected error has occurred." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "Show cards on Tab page" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "List card items on the Tab page for easy autofill." }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "Show identities on Tab page" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "Clear clipboard", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Save" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Ask to update existing login" }, @@ -1084,10 +1169,6 @@ "message": "Light", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized dark", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Export from" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Purchase Premium" }, - "premiumPurchaseAlert": { - "message": "You can purchase Premium membership on the bitwarden.com web vault. Do you want to visit the website now?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Open new tab" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "Login unavailable" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Lost access to all of your two-factor providers? Use your recovery code to turn off all two-factor providers from your account." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Self-hosted environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Custom environment" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "Server URL" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Drag to sort" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Text" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Username generator" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "Use this password" }, @@ -2063,12 +2163,24 @@ "message": "to create a strong unique password", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "Vault timeout action" }, "vaultTimeoutAction1": { "message": "Timeout action" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "Lock", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Settings have been edited" - }, - "environmentEditedClick": { - "message": "Click here" - }, - "environmentEditedReset": { - "message": "to reset to pre-configured settings" - }, "serverVersion": { "message": "Server version" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Log in with device" }, - "loginWithDeviceEnabledInfo": { - "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" - }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "View all log in options" }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" - }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "You will be notified once the request is approved" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3396,38 +3649,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Active account" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Available accounts" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "No values to copy" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organization is deactivated" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "You have not selected anything." }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra wide" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 16469051a0c..0ff4ed9486a 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -7,7 +7,7 @@ "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "message": "Hjemme, på jobben eller på farten sikrer Bitwarden enkelt alle dine passord, passnøkler og sensitiv informasjon", "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { @@ -20,19 +20,19 @@ "message": "Opprett en konto" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Er du ny til Bitwarden?" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Logg inn med passnøkkel" }, "useSingleSignOn": { "message": "Use single sign-on" }, "welcomeBack": { - "message": "Welcome back" + "message": "Velkommen tilbake" }, "setAStrongPassword": { - "message": "Set a strong password" + "message": "Velg et sterkt passord" }, "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" @@ -80,11 +80,20 @@ "masterPassHint": { "message": "Et hint for hovedpassordet (valgfritt)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { - "message": "Join organization" + "message": "Bli med i organisasjonen" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "Bli med i $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -138,31 +147,31 @@ "message": "Kopier sikkerhetskoden" }, "copyName": { - "message": "Copy name" + "message": "Kopiér navn" }, "copyCompany": { - "message": "Copy company" + "message": "Kopiér firma" }, "copySSN": { - "message": "Copy Social Security number" + "message": "Kopiér fødselsnummer" }, "copyPassportNumber": { - "message": "Copy passport number" + "message": "Kopiér passnummer" }, "copyLicenseNumber": { - "message": "Copy license number" + "message": "Kopiér lisensnummer" }, "copyPrivateKey": { - "message": "Copy private key" + "message": "Kopiér privat nøkkel" }, "copyPublicKey": { - "message": "Copy public key" + "message": "Kopiér offentlig nøkkel" }, "copyFingerprint": { - "message": "Copy fingerprint" + "message": "Kopiér fingeravtrykk" }, "copyCustomField": { - "message": "Copy $FIELD$", + "message": "Kopiér $FIELD$", "placeholders": { "field": { "content": "$1", @@ -171,13 +180,17 @@ } }, "copyWebsite": { - "message": "Copy website" + "message": "Kopiér nettsted" }, "copyNotes": { - "message": "Copy notes" + "message": "Kopiér notater" + }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" }, "fill": { - "message": "Fill", + "message": "Fyll", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." }, "autoFill": { @@ -193,10 +206,10 @@ "message": "Auto-utfyll identitet" }, "fillVerificationCode": { - "message": "Fill verification code" + "message": "Fyll inn verifiseringskode" }, "fillVerificationCodeAria": { - "message": "Fill Verification Code", + "message": "Fyll inn verifiseringskode", "description": "Aria label for the heading displayed the inline menu for totp code autofill" }, "generatePasswordCopied": { @@ -239,23 +252,17 @@ "message": "Legg til en gjenstand" }, "accountEmail": { - "message": "Account email" + "message": "Kontoens E-postadresse" }, "requestHint": { - "message": "Request hint" + "message": "Be om et hint" }, "requestPasswordHint": { - "message": "Request password hint" + "message": "Be om passordhint" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Passordhint" - }, - "enterEmailToGetHint": { - "message": "Skriv inn e-postadressen din for å motta hintet til hovedpassordet." - }, "getMasterPasswordHint": { "message": "Få et hint om superpassordet" }, @@ -281,13 +288,13 @@ "message": "Endre hovedpassordet" }, "continueToWebApp": { - "message": "Continue to web app?" + "message": "Vil du fortsette til nettappen?" }, "continueToWebAppDesc": { "message": "Explore more features of your Bitwarden account on the web app." }, "continueToHelpCenter": { - "message": "Continue to Help Center?" + "message": "Vil du fortsette til Hjelpesenteret?" }, "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." @@ -299,7 +306,7 @@ "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." }, "changeMasterPasswordOnWebConfirmation": { - "message": "You can change your master password on the Bitwarden web app." + "message": "Du kan endre hovedpassordet ditt i Bitwardens nettapp." }, "fingerprintPhrase": { "message": "Fingeravtrykksfrase", @@ -316,22 +323,22 @@ "message": "Logg ut" }, "aboutBitwarden": { - "message": "About Bitwarden" + "message": "Om Bitwarden" }, "about": { "message": "Om" }, "moreFromBitwarden": { - "message": "More from Bitwarden" + "message": "Mer fra Bitwarden" }, "continueToBitwardenDotCom": { - "message": "Continue to bitwarden.com?" + "message": "Vil du fortsette til bitwarden.com?" }, "bitwardenForBusiness": { - "message": "Bitwarden for Business" + "message": "Bitwarden for bedrifter" }, "bitwardenAuthenticator": { - "message": "Bitwarden Authenticator" + "message": "Bitwarden-autentiserer" }, "continueToAuthenticatorPageDesc": { "message": "Bitwarden Authenticator allows you to store authenticator keys and generate TOTP codes for 2-step verification flows. Learn more on the bitwarden.com website" @@ -372,6 +379,15 @@ "editFolder": { "message": "Rediger mappen" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "Ny mappe" }, @@ -382,7 +398,7 @@ "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, "noFoldersAdded": { - "message": "No folders added" + "message": "Ingen mapper er lagt til" }, "createFoldersToOrganize": { "message": "Create folders to organize your vault items" @@ -431,7 +447,7 @@ "message": "Generer automatisk sterke og unike passord for dine innlogginger." }, "bitWebVaultApp": { - "message": "Bitwarden web app" + "message": "Bitwardens nett-app" }, "importItems": { "message": "Importer elementer" @@ -443,7 +459,19 @@ "message": "Generer et passord" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "Generér passordfrase" + }, + "passwordGenerated": { + "message": "Passord generert" + }, + "passphraseGenerated": { + "message": "Passordfrase generert" + }, + "usernameGenerated": { + "message": "Brukernavn generert" + }, + "emailGenerated": { + "message": "E-postadresse generert" }, "regeneratePassword": { "message": "Omgenerer et passord" @@ -454,28 +482,12 @@ "length": { "message": "Lengde" }, - "uppercase": { - "message": "Store bokstaver (A–Å)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Små bokstaver (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Tall (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Spesialtegn (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Inkluder", "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "Inkluder store bokstaver", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -483,7 +495,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "Inkluder små bokstaver", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -499,13 +511,9 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "Inkluder spesialtegn", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Antall ord" }, @@ -526,7 +534,7 @@ "message": "Minste antall spesialtegn" }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "Unngå forvekslingsbare tegn", "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { @@ -555,7 +563,7 @@ "message": "Passord" }, "totp": { - "message": "Authenticator secret" + "message": "Autentiseringsnøkkel" }, "passphrase": { "message": "Passfrase" @@ -564,19 +572,19 @@ "message": "Favoritt" }, "unfavorite": { - "message": "Unfavorite" + "message": "Fjern favorittstempel" }, "itemAddedToFavorites": { - "message": "Item added to favorites" + "message": "Gjenstand lagt til i favorittene" }, "itemRemovedFromFavorites": { - "message": "Item removed from favorites" + "message": "Gjenstand fjernet fra favorittene" }, "notes": { "message": "Notater" }, "privateNote": { - "message": "Private note" + "message": "Privat notat" }, "note": { "message": "Notat" @@ -597,10 +605,10 @@ "message": "Åpne" }, "launchWebsite": { - "message": "Launch website" + "message": "Åpne nettstedet" }, "launchWebsiteName": { - "message": "Launch website $ITEMNAME$", + "message": "Åpne nettstedet $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -621,7 +629,7 @@ "message": "Annet" }, "unlockMethods": { - "message": "Unlock options" + "message": "Opplåsingsalternativer" }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." @@ -630,10 +638,10 @@ "message": "Set up an unlock method in Settings" }, "sessionTimeoutHeader": { - "message": "Session timeout" + "message": "Tidsavbrudd for økten" }, "vaultTimeoutHeader": { - "message": "Vault timeout" + "message": "Tidsavbrudd for hvelvet" }, "otherOptions": { "message": "Andre valg" @@ -644,20 +652,26 @@ "browserNotSupportClipboard": { "message": "Nettleseren din støtter ikke kopiering til utklippstavlen på noe enkelt vis. Prøv å kopiere det manuelt i stedet." }, - "verifyIdentity": { - "message": "Bekreft identitet" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Fortsett innloggingen" }, "yourVaultIsLocked": { "message": "Hvelvet ditt er låst. Kontroller hovedpassordet ditt for å fortsette." }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "Hvelvet ditt er låst" }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "Kontoen din er låst" }, "or": { - "message": "or" + "message": "eller" }, "unlock": { "message": "Lås opp" @@ -745,7 +759,7 @@ "message": "Your master password cannot be recovered if you forget it!" }, "masterPassHintLabel": { - "message": "Master password hint" + "message": "Få et hint om hovedpassordet" }, "errorOccurred": { "message": "En feil har oppstått" @@ -779,16 +793,16 @@ "message": "Din nye konto har blitt opprettet! Du kan nå logge på." }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "Den nye kontoen din er opprettet!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "Du har blitt logget inn!" }, "youSuccessfullyLoggedIn": { - "message": "You successfully logged in" + "message": "Du har vellykket logget inn" }, "youMayCloseThisWindow": { - "message": "You may close this window" + "message": "Du kan lukke dette vinduet" }, "masterPassSent": { "message": "Vi har sendt deg en E-post med hintet til superpassordet." @@ -834,16 +848,16 @@ "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." }, "learnMoreAboutAuthenticators": { - "message": "Learn more about authenticators" + "message": "Lær mer om autentisering" }, "copyTOTP": { - "message": "Copy Authenticator key (TOTP)" + "message": "Kopier autentiseringsnøkkel (TOTP)" }, "loggedOut": { "message": "Logget av" }, "loggedOutDesc": { - "message": "You have been logged out of your account." + "message": "Du har blitt logget ut av kontoen din." }, "loginExpired": { "message": "Din innloggingsøkt har utløpt." @@ -852,19 +866,34 @@ "message": "Logg inn" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Logg inn på Bitwarden" + }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." }, "restartRegistration": { - "message": "Restart registration" + "message": "Start registreringen på nytt" }, "expiredLink": { - "message": "Expired link" + "message": "Utløpt lenke" }, "pleaseRestartRegistrationOrTryLoggingIn": { "message": "Please restart registration or try logging in." }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "Du har kanskje allerede en konto" }, "logOutConfirmation": { "message": "Er du sikker på at du vil logge av?" @@ -875,6 +904,9 @@ "no": { "message": "Nei" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "En uventet feil har oppstått." }, @@ -891,7 +923,7 @@ "message": "Make your account more secure by setting up two-step login in the Bitwarden web app." }, "twoStepLoginConfirmationTitle": { - "message": "Continue to web app?" + "message": "Vil du fortsette til nettappen?" }, "editedFolder": { "message": "Redigerte mappen" @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "Vis kort på fanesiden" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Vis kortelementer på fanesiden for lett auto-utfylling." }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "Vis identiteter på fanesiden" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "Tøm utklippstavlen", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Ja, lagre nå" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Spør om å oppdatere eksisterende innlogginger" }, @@ -1049,7 +1134,7 @@ "message": "Lås opp" }, "additionalOptions": { - "message": "Additional options" + "message": "Ekstra innstillinger" }, "enableContextMenuItem": { "message": "Vis alternativer for kontekstmeny" @@ -1084,10 +1169,6 @@ "message": "Lyst", "description": "Light color" }, - "solarizedDark": { - "message": "Solarisert mørk", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Export from" }, @@ -1113,7 +1194,7 @@ "message": "Set a file password to encrypt the export and import it to any Bitwarden account using the password for decryption." }, "exportTypeHeading": { - "message": "Export type" + "message": "Eksporttype" }, "accountRestricted": { "message": "Account restricted" @@ -1126,7 +1207,7 @@ "description": "WARNING (should stay in capitalized letters if the language permits)" }, "warningCapitalized": { - "message": "Warning", + "message": "Advarsel", "description": "Warning (should maintain locale-relevant capitalization)" }, "confirmVaultExport": { @@ -1206,7 +1287,7 @@ "message": "Fil" }, "fileToShare": { - "message": "File to share" + "message": "Filen som skal deles" }, "selectFile": { "message": "Velg en fil." @@ -1242,7 +1323,7 @@ "message": "1 GB med kryptert fillagring for filvedlegg." }, "premiumSignUpEmergency": { - "message": "Emergency access." + "message": "Nødtilgang." }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Kjøp Premium" }, - "premiumPurchaseAlert": { - "message": "Du kan kjøpe et Premium-medlemskap på bitwarden.com. Vil du besøke det nettstedet nå?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1275,7 +1353,7 @@ "message": "Takk for at du støtter Bitwarden." }, "premiumFeatures": { - "message": "Upgrade to Premium and receive:" + "message": "Oppgrader til Premium og motta:" }, "premiumPrice": { "message": "Og alt det for %price%/år!", @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Husk på meg" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send E-posten med verifiseringskoden på nytt" }, "useAnotherTwoStepMethod": { "message": "Bruk en annen 2-trinnsinnloggingsmetode" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Sett inn din YubiKey i din datamaskins USB-uttak, og så trykk på dens knapp." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Åpne ny fane" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "Autentiser WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "Innloggingen er utilgjengelig" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Alternativer for 2-trinnsinnlogging" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Har du mistet tilgang til alle dine 2-trinnsleverandører? Bruk din gjenopprettingskode til å fjerne alle 2-trinnsleverandører fra din konto." }, @@ -1413,14 +1513,11 @@ "message": "E-post" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "Skriv inn koden du har fått tilsendt på E-post." }, "selfHostedEnvironment": { "message": "Selvbetjent miljø" }, - "selfHostedEnvironmentFooter": { - "message": "Spesifiser grunn-nettadressen til din selvbetjente Bitwarden-installasjon." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Tilpasset miljø" }, - "customEnvironmentFooter": { - "message": "For avanserte brukere. Du kan bestemme grunn-nettadressen til hver tjeneste separat." - }, "baseUrl": { "message": "Tjener-nettadresse" }, @@ -1462,29 +1556,29 @@ "message": "Miljø-nettadressene har blitt lagret." }, "showAutoFillMenuOnFormFields": { - "message": "Show autofill menu on form fields", + "message": "Vis autoutfyll-menyen i tekstbokser", "description": "Represents the message for allowing the user to enable the autofill overlay" }, "autofillSuggestionsSectionTitle": { - "message": "Autofill suggestions" + "message": "Autoutfyllingsforslag" }, "showInlineMenuLabel": { - "message": "Show autofill suggestions on form fields" + "message": "Vis autoutfyll-forslag i tekstbokser" }, "showInlineMenuIdentitiesLabel": { - "message": "Display identities as suggestions" + "message": "Vis identiteter som forslag" }, "showInlineMenuCardsLabel": { - "message": "Display cards as suggestions" + "message": "Vis kort som forslag" }, "showInlineMenuOnIconSelectionLabel": { - "message": "Display suggestions when icon is selected" + "message": "Vis forslag når ikonet er valgt" }, "showInlineMenuOnFormFieldsDescAlt": { "message": "Applies to all logged in accounts." }, "turnOffBrowserBuiltInPasswordManagerSettings": { - "message": "Turn off your browser's built in password manager settings to avoid conflicts." + "message": "Skru av din nettlesers innebygde passordbehandler for å unngå konflikter." }, "turnOffBrowserBuiltInPasswordManagerSettingsLink": { "message": "Rediger nettleserinnstillingene." @@ -1514,7 +1608,7 @@ "message": "Kompromitterte eller upålitelige nettsider kan utnytte auto-utfylling når du laster inn siden." }, "learnMoreAboutAutofillOnPageLoadLinkText": { - "message": "Learn more about risks" + "message": "Lær mer om risikoer" }, "learnMoreAboutAutofill": { "message": "Lær mer om auto-utfylling" @@ -1523,7 +1617,7 @@ "message": "Standard autofyll innstilling for innloggingselementer" }, "defaultAutoFillOnPageLoadDesc": { - "message": "Etter aktivering av auto-utfylling på sidelasser, kan du aktivere eller deaktivere funksjonen for individuelle innloggingselementer. Dette er standardinnstillingen for innloggingselementer som ikke er satt opp separat." + "message": "Du kan skru av auto-utfylling ved sideinnlastinger for individuelle innloggingsgjenstander fra gjenstandens «Redigér»-visning." }, "itemAutoFillOnPageLoad": { "message": "Auto-utfyll på sideinnlastning (hvis aktivert i Alternativer)" @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Dra for å sortere" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Tekst" }, @@ -1583,7 +1680,7 @@ "message": "Boolsk verdi" }, "cfTypeCheckbox": { - "message": "Checkbox" + "message": "Avkryssingsboks" }, "cfTypeLinked": { "message": "Tilkoblet", @@ -1768,10 +1865,10 @@ "message": "Identitet" }, "typeSshKey": { - "message": "SSH key" + "message": "SSH-nøkkel" }, "newItemHeader": { - "message": "New $TYPE$", + "message": "Ny $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1780,7 +1877,7 @@ } }, "editItemHeader": { - "message": "Edit $TYPE$", + "message": "Rediger $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1789,7 +1886,7 @@ } }, "viewItemHeader": { - "message": "View $TYPE$", + "message": "Vis $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1801,10 +1898,10 @@ "message": "Passordhistorikk" }, "generatorHistory": { - "message": "Generator history" + "message": "Generatorhistorikk" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "Tøm generatorhistorikk" }, "cleargGeneratorHistoryDescription": { "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" @@ -1816,7 +1913,7 @@ "message": "Samlinger" }, "nCollections": { - "message": "$COUNT$ collections", + "message": "$COUNT$ samlinger", "placeholders": { "count": { "content": "$1", @@ -1846,7 +1943,7 @@ "message": "Sikre notiser" }, "sshKeys": { - "message": "SSH Keys" + "message": "SSH-nøkler" }, "clear": { "message": "Tøm", @@ -1872,7 +1969,7 @@ "description": "Domain name. Ex. website.com" }, "baseDomainOptionRecommended": { - "message": "Base domain (recommended)", + "message": "Grunndomene (anbefalt)", "description": "Domain name. Ex. website.com" }, "domainName": { @@ -1929,10 +2026,10 @@ "message": "Tøm historikk" }, "nothingToShow": { - "message": "Nothing to show" + "message": "Ingenting å vise" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "Du har ikke generert noe i det siste" }, "remove": { "message": "Fjern" @@ -2002,7 +2099,7 @@ "message": "Angi PIN-koden din for å låse opp Bitwarden. PIN-innstillingene tilbakestilles hvis du logger deg helt ut av programmet." }, "setYourPinCode1": { - "message": "Your PIN will be used to unlock Bitwarden instead of your master password. Your PIN will reset if you ever fully log out of Bitwarden." + "message": "PIN-koden din vil bli brukt til å låse opp Bitwarden i stedet for hovedpassordet ditt. PIN-koden din tilbakestilles hvis du noen gang logger deg helt ut av Bitwarden." }, "pinRequired": { "message": "PIN-kode er påkrevd." @@ -2011,13 +2108,13 @@ "message": "Ugyldig PIN-kode." }, "tooManyInvalidPinEntryAttemptsLoggingOut": { - "message": "Too many invalid PIN entry attempts. Logging out." + "message": "For mange ugyldige PIN-kodeforsøk. Logger ut." }, "unlockWithBiometrics": { "message": "Lås opp med biometri" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "Lås opp med hovedpassord" }, "awaitDesktop": { "message": "Venter på bekreftelse fra skrivebordsprogrammet" @@ -2029,7 +2126,7 @@ "message": "Lås med hovedpassordet når du starter nettleseren på nytt" }, "lockWithMasterPassOnRestart1": { - "message": "Require master password on browser restart" + "message": "Krev hovedpassord ved omstart av nettleseren" }, "selectOneCollection": { "message": "Du må velge minst én samling." @@ -2041,33 +2138,48 @@ "message": "Klon" }, "passwordGenerator": { - "message": "Password generator" + "message": "Passordgenerator" }, "usernameGenerator": { - "message": "Username generator" + "message": "Brukernavngenerator" + }, + "useThisEmail": { + "message": "Bruk denne E-postadressen" }, "useThisPassword": { "message": "Bruk dette passordet" }, "useThisUsername": { - "message": "Use this username" + "message": "Bruk dette brukernavnet" }, "securePasswordGenerated": { "message": "Secure password generated! Don't forget to also update your password on the website." }, "useGeneratorHelpTextPartOne": { - "message": "Use the generator", + "message": "Bruk denne generatoren", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "useGeneratorHelpTextPartTwo": { - "message": "to create a strong unique password", + "message": "for å lage et sterkt og unikt passord", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "Handling ved tidsavbrudd i hvelvet" }, "vaultTimeoutAction1": { - "message": "Timeout action" + "message": "Handling ved tidsavbrudd" + }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" }, "lock": { "message": "Lås", @@ -2096,7 +2208,7 @@ "message": "Gjenopprettet objekt" }, "alreadyHaveAccount": { - "message": "Already have an account?" + "message": "Har du allerede en konto?" }, "vaultTimeoutLogOutConfirmation": { "message": "Hvis du logger ut, fjerner du all tilgang til hvelvet ditt og krever online godkjenning etter tidsavbrudd. Er du sikker på at du vil bruke denne innstillingen?" @@ -2108,7 +2220,7 @@ "message": "Autofyll og lagre" }, "fillAndSave": { - "message": "Fill and save" + "message": "Fyll og lagre" }, "autoFillSuccessAndSavedUri": { "message": "Autoutfylt objekt og lagret URI" @@ -2117,7 +2229,7 @@ "message": "Autoutfylt element" }, "insecurePageWarning": { - "message": "Warning: This is an unsecured HTTP page, and any information you submit can potentially be seen and changed by others. This Login was originally saved on a secure (HTTPS) page." + "message": "Advarsel: Dette er en usikret HTTP-side, og all informasjon du sender inn kan potensielt bli sett og endret av andre. Denne påloggingen ble opprinnelig lagret på et sikkert (HTTPS) nettsted." }, "insecurePageWarningFillPrompt": { "message": "Ønsker du likevel å fylle ut denne innloggingen?" @@ -2195,10 +2307,10 @@ "message": "Avslutt abonnement" }, "atAnyTime": { - "message": "at any time." + "message": "når som helst." }, "byContinuingYouAgreeToThe": { - "message": "By continuing, you agree to the" + "message": "Ved å fortsette, samtykker du til" }, "and": { "message": "og" @@ -2288,7 +2400,7 @@ "message": "Please unlock this user in the desktop application and try again." }, "biometricsNotAvailableTitle": { - "message": "Biometric unlock unavailable" + "message": "Biometrisk opplåsing er utilgjengelig" }, "biometricsNotAvailableDesc": { "message": "Biometric unlock is currently unavailable. Please try again later." @@ -2324,6 +2436,12 @@ "message": "Domener", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blokkerte domener" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "Ekskluderte domener" }, @@ -2333,8 +2451,120 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Endre dette i innstillingene" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { - "message": "Website $number$ (URI)", + "message": "Nettsted $number$ (URİ)", "placeholders": { "number": { "content": "$1", @@ -2351,18 +2581,21 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, "limitSendViews": { - "message": "Limit views" + "message": "Begrens visninger" }, "limitSendViewsHint": { - "message": "No one can view this Send after the limit is reached.", + "message": "Ingen kan se denne Send-en etter at grensen er nådd.", "description": "Displayed under the limit views field on Send" }, "limitSendViewsCount": { - "message": "$ACCESSCOUNT$ views left", + "message": "$ACCESSCOUNT$ visninger igjen", "description": "Displayed under the limit views field on Send", "placeholders": { "accessCount": { @@ -2376,14 +2609,14 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDetails": { - "message": "Send details", + "message": "Send-detaljer", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTypeText": { "message": "Tekst" }, "sendTypeTextToShare": { - "message": "Text to share" + "message": "Teksten som skal deles" }, "sendTypeFile": { "message": "Fil" @@ -2393,7 +2626,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "hideTextByDefault": { - "message": "Hide text by default" + "message": "Skjul tekst som standard" }, "expired": { "message": "Utløpt" @@ -2473,7 +2706,7 @@ "message": "Egendefinert" }, "sendPasswordDescV3": { - "message": "Add an optional password for recipients to access this Send.", + "message": "Legg til et valgfritt passord for at mottakerne skal få tilgang til denne Send-en.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createSend": { @@ -2528,7 +2761,7 @@ } }, "sendLinkCopied": { - "message": "Send link copied", + "message": "Send-lenken ble kopiert", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { @@ -2536,7 +2769,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendFilePopoutDialogText": { - "message": "Pop out extension?", + "message": "Vil du sprette ut utvidelsen?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendFilePopoutDialogDesc": { @@ -2553,7 +2786,7 @@ "message": "For å velge en fil med Safari, popp ut i et nytt vindu ved å klikke på dette banneret." }, "popOut": { - "message": "Pop out" + "message": "Sprett ut" }, "sendFileCalloutHeader": { "message": "Før du starter" @@ -2574,7 +2807,7 @@ "message": "Det oppstod en feil ved lagring av slettingen og utløpsdatoene." }, "hideYourEmail": { - "message": "Hide your email address from viewers." + "message": "Skjul E-postadressen din fra seere." }, "passwordPrompt": { "message": "Forespørsel om hovedpassord på nytt" @@ -2619,7 +2852,7 @@ "message": "Velg mappe …" }, "noFoldersFound": { - "message": "No folders found", + "message": "Ingen mapper ble funnet", "description": "Used as a message within the notification bar when no folders are found" }, "orgPermissionsUpdatedMustSetPassword": { @@ -2631,7 +2864,7 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "av $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -2789,14 +3022,28 @@ "error": { "message": "Feil" }, + "decryptionError": { + "message": "Dekrypteringsfeil" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "for å unngå ytterligere datatap.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generer brukernavn" }, "generateEmail": { - "message": "Generate email" + "message": "Generér E-post" }, "spinboxBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$.", + "message": "Verdien må være mellom $MIN$ og $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2810,7 +3057,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "message": " Bruk minst $RECOMMENDED$ tegn for å generere et sterkt passord.", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2861,11 +3108,11 @@ "message": "Generer et e-postalias med en ekstern videresendingstjeneste." }, "forwarderDomainName": { - "message": "Email domain", + "message": "E-postdomene", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Choose a domain that is supported by the selected service", + "message": "Velg et domene som støttes av den valgte tjenesten", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { @@ -2883,11 +3130,11 @@ } }, "forwarderGeneratedBy": { - "message": "Generated by Bitwarden.", + "message": "Generert av Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "Nettsted: $WEBSITE$. Generert av Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -2897,7 +3144,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "Ugyldig $SERVICENAME$-API-sjetong", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -2907,7 +3154,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "Ugyldig $SERVICENAME$-API-sjetong: $ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2931,7 +3202,7 @@ } }, "forwarderNoDomain": { - "message": "Invalid $SERVICENAME$ domain.", + "message": "Ugyldig $SERVICENAME$-domene.", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -2951,7 +3222,7 @@ } }, "forwarderUnknownError": { - "message": "Unknown $SERVICENAME$ error occurred.", + "message": "Ukjent $SERVICENAME$-feil oppstod.", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Innstillingene har blitt endret" - }, - "environmentEditedClick": { - "message": "Klikk her" - }, - "environmentEditedReset": { - "message": "for å tilbakestille til forhåndskonfigurerte innstillinger" - }, "serverVersion": { "message": "Server Versjon" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Logg på med hovedpassord" }, - "loggingInAs": { - "message": "Logger på som" - }, - "notYou": { - "message": "Ikke deg?" - }, "newAroundHere": { "message": "Er du ny her?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Logg inn med enhet" }, - "loginWithDeviceEnabledInfo": { - "message": "Logg på med enhet må settes opp i Bitwarden-innstillingene. Trenger du et annet alternativ?" - }, "fingerprintPhraseHeader": { "message": "Fingeravtrykksfrase" }, @@ -3068,28 +3321,34 @@ "message": "Send varslingen på nytt" }, "viewAllLogInOptions": { - "message": "View all log in options" - }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" + "message": "Vis alle påloggingsalternativer" }, "notificationSentDevice": { "message": "Et varsel er sendt til enheten din." }, - "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDeviceAnchor": { + "message": "nett-app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "aNotificationWasSentToYourDevice": { + "message": "Et varsel ble sendt til enheten din" }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "You will be notified once the request is approved" }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "Trenger du et annet alternativ?" }, "loginInitiated": { - "message": "Login initiated" + "message": "Innlogging igangsatt" + }, + "logInRequestSent": { + "message": "Forespørsel sendt" }, "exposedMasterPassword": { "message": "Eksponert hovedpassord" @@ -3149,10 +3408,10 @@ "message": "Autofill shortcut" }, "autofillKeyboardShortcutUpdateLabel": { - "message": "Change shortcut" + "message": "Endre snarvei" }, "autofillKeyboardManagerShortcutsLabel": { - "message": "Manage shortcuts" + "message": "Behandle snarveier" }, "autofillShortcut": { "message": "Auto-utfyll tastatursnarvei" @@ -3161,7 +3420,7 @@ "message": "The autofill login shortcut is not set. Change this in the browser's settings." }, "autofillLoginShortcutText": { - "message": "The autofill login shortcut is $COMMAND$. Manage all shortcuts in the browser's settings.", + "message": "Autoutfyll-snarveien for pålogging er $COMMAND$. Håndter alle snarveiene i nettleserens innstillinger.", "placeholders": { "command": { "content": "$1", @@ -3188,7 +3447,7 @@ "message": "Device approval required. Select an approval option below:" }, "deviceApprovalRequiredV2": { - "message": "Device approval required" + "message": "Enhetsgodkjennelse kreves" }, "selectAnApprovalOptionBelow": { "message": "Select an approval option below" @@ -3205,32 +3464,29 @@ "requestAdminApproval": { "message": "Be om administratorgodkjennelse" }, - "approveWithMasterPassword": { - "message": "Godkjenn med hovedpassord" - }, "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, "creatingAccountOn": { - "message": "Creating account on" + "message": "Oppretter en konto på" }, "checkYourEmail": { - "message": "Check your email" + "message": "Sjekk E-postinnboksen din" }, "followTheLinkInTheEmailSentTo": { - "message": "Follow the link in the email sent to" + "message": "Følg lenken i E-postadressen som ble sendt til" }, "andContinueCreatingYourAccount": { "message": "and continue creating your account." }, "noEmail": { - "message": "No email?" + "message": "Ingen E-post?" }, "goBack": { "message": "Gå tilbake" }, "toEditYourEmailAddress": { - "message": "to edit your email address." + "message": "for å redigere E-postadressen din." }, "eu": { "message": "EU", @@ -3254,11 +3510,8 @@ "adminApprovalRequestSentToAdmins": { "message": "Forespørselen din har blitt sendt til administratoren din." }, - "youWillBeNotifiedOnceApproved": { - "message": "Du vil bli varslet når det er godkjent." - }, "troubleLoggingIn": { - "message": "Trouble logging in?" + "message": "Har du problemer med å logge inn?" }, "loginApproved": { "message": "Innlogging godkjent" @@ -3270,14 +3523,14 @@ "message": "Active user email not found. Logging you out." }, "deviceTrusted": { - "message": "Device trusted" + "message": "Enheten er betrodd" }, "sendsNoItemsTitle": { - "message": "No active Sends", + "message": "Ingen aktive Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "message": "Bruk Send til å dele kryptert informasjon med noen.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -3354,10 +3607,10 @@ } }, "singleFieldNeedsAttention": { - "message": "1 field needs your attention." + "message": "1 felt trenger din oppmerksomhet." }, "multipleFieldsNeedAttention": { - "message": "$COUNT$ fields need your attention.", + "message": "$COUNT$ felter trenger din oppmerksomhet.", "placeholders": { "count": { "content": "$1", @@ -3393,41 +3646,9 @@ "message": "Undermeny" }, "toggleCollapse": { - "message": "Toggle collapse", + "message": "Utvid eller klapp sammen", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Vil du importere dataene dine til Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Lagre som ukryptert fil", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Importer til Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importerer …", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Dataene ble vellykket importert!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias-domene" }, @@ -3458,7 +3679,7 @@ "description": "Screen reader and tool tip label for the overlay button" }, "bitwardenVault": { - "message": "Bitwarden autofill menu", + "message": "Bitwardens autoutfyllingsmeny", "description": "Page title in overlay" }, "unlockYourAccountToViewMatchingLogins": { @@ -3514,7 +3735,7 @@ "description": "Screen reader text (aria-label) for new login button within inline menu" }, "newCard": { - "message": "New card", + "message": "Nytt kort", "description": "Button text to display within inline menu when there are no matching items on a credit card field" }, "addNewCardItemAria": { @@ -3544,7 +3765,7 @@ "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" }, "importError": { - "message": "Import error" + "message": "Importeringsfeil" }, "importErrorDesc": { "message": "There was a problem with the data you tried to import. Please resolve the errors listed below in your source file and try again." @@ -3631,7 +3852,7 @@ "message": "Popout extension" }, "launchDuo": { - "message": "Launch Duo" + "message": "Start Duo" }, "importFormatError": { "message": "Data is not formatted correctly. Please check your import file and try again." @@ -3705,19 +3926,19 @@ "message": "Bekreft filpassord" }, "exportSuccess": { - "message": "Vault data exported" + "message": "Hvelvdataen ble eksportert" }, "typePasskey": { - "message": "Passkey" + "message": "Passnøkkel" }, "accessing": { - "message": "Accessing" + "message": "Logger inn på" }, "loggedInExclamation": { - "message": "Logged in!" + "message": "Innlogget!" }, "passkeyNotCopied": { - "message": "Passkey will not be copied" + "message": "Passkoden vil ikke bli kopiert" }, "passkeyNotCopiedAlert": { "message": "The passkey will not be copied to the cloned item. Do you want to continue cloning this item?" @@ -3741,16 +3962,16 @@ "message": "No matching logins for this site" }, "searchSavePasskeyNewLogin": { - "message": "Search or save passkey as new login" + "message": "Søk eller lagre passnøkkelen som en ny innlogging" }, "confirm": { "message": "Bekreft" }, "savePasskey": { - "message": "Save passkey" + "message": "Lagre passnøkkel" }, "savePasskeyNewLogin": { - "message": "Save passkey as new login" + "message": "Lagre passnøkkelen som en ny pålogging" }, "chooseCipherForPasskeySave": { "message": "Choose a login to save this passkey to" @@ -3759,7 +3980,7 @@ "message": "Choose a passkey to log in with" }, "passkeyItem": { - "message": "Passkey Item" + "message": "Passkode-gjenstand" }, "overwritePasskey": { "message": "Overwrite passkey?" @@ -3813,7 +4034,7 @@ "message": "Approve the login request in your authentication app or enter a one-time passcode." }, "passcode": { - "message": "Passcode" + "message": "Passkode" }, "lastPassMasterPassword": { "message": "LastPass-hovedpassord" @@ -3853,11 +4074,14 @@ "message": "Bytt kontoer" }, "switchToAccount": { - "message": "Switch to account" + "message": "Bytt til konto" }, "activeAccount": { "message": "Aktiv konto" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Tilgjengelige kontoer" }, @@ -3874,13 +4098,13 @@ "message": "låst opp" }, "server": { - "message": "server" + "message": "tjener" }, "hostedAt": { "message": "betjent hos" }, "useDeviceOrHardwareKey": { - "message": "Use your device or hardware key" + "message": "Bruk enhets- eller maskinvarenøkkel" }, "justOnce": { "message": "Kun én gang" @@ -3902,11 +4126,11 @@ "description": "Label indicating the most common import formats" }, "confirmContinueToBrowserSettingsTitle": { - "message": "Continue to browser settings?", + "message": "Vil du fortsette til nettleserinnstillingene?", "description": "Title for dialog which asks if the user wants to proceed to a relevant browser settings page" }, "confirmContinueToHelpCenter": { - "message": "Continue to Help Center?", + "message": "Vil du fortsette til Hjelpesenteret?", "description": "Title for dialog which asks if the user wants to proceed to a relevant Help Center page" }, "confirmContinueToHelpCenterPasswordManagementContent": { @@ -3926,7 +4150,7 @@ "description": "Body content for dialog which asks if the user wants to proceed to the browser's keyboard shortcut settings page" }, "overrideDefaultBrowserAutofillTitle": { - "message": "Make Bitwarden your default password manager?", + "message": "Vil du sette Bitwarden som din standard passordbehandler?", "description": "Dialog title facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutofillDescription": { @@ -3946,7 +4170,7 @@ "description": "Description for the dialog that appears when the user has not granted the extension permission to set privacy settings" }, "makeDefault": { - "message": "Make default", + "message": "Gjør det til standarden", "description": "Button text for the setting that allows overriding the default browser autofill settings" }, "saveCipherAttemptSuccess": { @@ -3954,7 +4178,7 @@ "description": "Notification message for when saving credentials has succeeded." }, "passwordSaved": { - "message": "Password saved!", + "message": "Passordet ble lagret!", "description": "Notification message for when saving credentials has succeeded." }, "updateCipherAttemptSuccess": { @@ -3962,7 +4186,7 @@ "description": "Notification message for when updating credentials has succeeded." }, "passwordUpdated": { - "message": "Password updated!", + "message": "Passordet ble oppdatert!", "description": "Notification message for when updating credentials has succeeded." }, "saveCipherAttemptFailed": { @@ -3973,19 +4197,22 @@ "message": "Suksess" }, "removePasskey": { - "message": "Remove passkey" + "message": "Fjern passordnøkkel" }, "passkeyRemoved": { "message": "Passkey removed" }, "autofillSuggestions": { - "message": "Autofill suggestions" + "message": "Autoutfyllingsforslag" + }, + "itemSuggestions": { + "message": "Foreslåtte gjenstander" }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, "yourVaultIsEmpty": { - "message": "Your vault is empty" + "message": "Hvelvet ditt er tomt" }, "noItemsMatchSearch": { "message": "No items match your search" @@ -3994,7 +4221,7 @@ "message": "Clear filters or try another search term" }, "copyInfoTitle": { - "message": "Copy info - $ITEMNAME$", + "message": "Kopiér info - $ITEMNAME$", "description": "Title for a button that opens a menu with options to copy information from an item.", "placeholders": { "itemname": { @@ -4004,7 +4231,7 @@ } }, "copyNoteTitle": { - "message": "Copy Note - $ITEMNAME$", + "message": "Kopiér notat - $ITEMNAME$", "description": "Title for a button copies a note to the clipboard.", "placeholders": { "itemname": { @@ -4014,7 +4241,7 @@ } }, "moreOptionsLabel": { - "message": "More options, $ITEMNAME$", + "message": "Flere innstillinger, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -4024,7 +4251,7 @@ } }, "moreOptionsTitle": { - "message": "More options - $ITEMNAME$", + "message": "Flere innstillinger - $ITEMNAME$", "description": "Title for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -4034,7 +4261,7 @@ } }, "viewItemTitle": { - "message": "View item - $ITEMNAME$", + "message": "Vis gjenstand - $ITEMNAME$", "description": "Title for a link that opens a view for an item.", "placeholders": { "itemname": { @@ -4044,7 +4271,7 @@ } }, "autofillTitle": { - "message": "Autofill - $ITEMNAME$", + "message": "Autoutfyll - $ITEMNAME$", "description": "Title for a button that autofills a login item.", "placeholders": { "itemname": { @@ -4053,20 +4280,34 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { - "message": "No values to copy" + "message": "Ingen verdier å kopiere" }, "assignToCollections": { - "message": "Assign to collections" + "message": "Legg til i samlinger" }, "copyEmail": { "message": "Copy email" }, "copyPhone": { - "message": "Copy phone" + "message": "Kopiér telefonnummer" }, "copyAddress": { - "message": "Copy address" + "message": "Kopiér adresse" }, "adminConsole": { "message": "Admin Console" @@ -4087,7 +4328,7 @@ "message": "Error assigning target folder." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Vis gjenstander i $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -4097,7 +4338,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Tilbake til $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -4110,7 +4351,7 @@ "message": "Ny" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Fjern $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -4120,22 +4361,13 @@ } }, "itemsWithNoFolder": { - "message": "Items with no folder" + "message": "Gjenstander uten mappe" }, "itemDetails": { - "message": "Item details" + "message": "Gjenstandens detaljer" }, "itemName": { - "message": "Item name" - }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } + "message": "Gjenstandens navn" }, "organizationIsDeactivated": { "message": "Organization is deactivated" @@ -4154,31 +4386,31 @@ "message": "Tilleggsinformasjon" }, "itemHistory": { - "message": "Item history" + "message": "Gjenstandshistorikk" }, "lastEdited": { - "message": "Last edited" + "message": "Nyligst redigert" }, "ownerYou": { - "message": "Owner: You" + "message": "Eier: Du" }, "linked": { "message": "Tilknyttet" }, "copySuccessful": { - "message": "Copy Successful" + "message": "Kopiering lyktes" }, "upload": { "message": "Last opp" }, "addAttachment": { - "message": "Add attachment" + "message": "Legg til vedlegg" }, "maxFileSizeSansPunctuation": { - "message": "Maximum file size is 500 MB" + "message": "Maksimal filstørrelse er 500 MB" }, "deleteAttachmentName": { - "message": "Delete attachment $NAME$", + "message": "Slett $NAME$-vedlegget", "placeholders": { "name": { "content": "$1", @@ -4187,7 +4419,7 @@ } }, "downloadAttachmentName": { - "message": "Download $NAME$", + "message": "Last ned $NAME$", "placeholders": { "name": { "content": "$1", @@ -4211,10 +4443,10 @@ "message": "Filter vault" }, "filterApplied": { - "message": "One filter applied" + "message": "Ett filter er benyttet" }, "filterAppliedPlural": { - "message": "$COUNT$ filters applied", + "message": "$COUNT$ filtre er benyttet", "placeholders": { "count": { "content": "$1", @@ -4223,16 +4455,16 @@ } }, "personalDetails": { - "message": "Personal details" + "message": "Personlige detaljer" }, "identification": { - "message": "Identification" + "message": "Identifikasjon" }, "contactInfo": { "message": "Kontaktinfo" }, "downloadAttachment": { - "message": "Download - $ITEMNAME$", + "message": "Last ned - $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -4241,23 +4473,23 @@ } }, "cardNumberEndsWith": { - "message": "card number ends with", + "message": "kortnummeret slutter med", "description": "Used within the inline menu to provide an aria description when users are attempting to fill a card cipher." }, "loginCredentials": { "message": "Legitimasjoner for innlogging" }, "authenticatorKey": { - "message": "Authenticator key" + "message": "Autentiseringsnøkkel" }, "autofillOptions": { - "message": "Autofill options" + "message": "Autoutfyllings-innstillinger" }, "websiteUri": { - "message": "Website (URI)" + "message": "Nettsted (URİ)" }, "websiteUriCount": { - "message": "Website (URI) $COUNT$", + "message": "Nettsted (URİ) $COUNT$", "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", "placeholders": { "count": { @@ -4267,7 +4499,7 @@ } }, "websiteAdded": { - "message": "Website added" + "message": "Nettsted lagt til" }, "addWebsite": { "message": "Legg til nettsted" @@ -4276,7 +4508,7 @@ "message": "Slett nettsted" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "Standard ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -4307,16 +4539,16 @@ "message": "Autofill on page load?" }, "cardExpiredTitle": { - "message": "Expired card" + "message": "Utløpt kort" }, "cardExpiredMessage": { "message": "If you've renewed it, update the card's information" }, "cardDetails": { - "message": "Card details" + "message": "Kortdetaljer" }, "cardBrandDetails": { - "message": "$BRAND$ details", + "message": "$BRAND$-detaljer", "placeholders": { "brand": { "content": "$1", @@ -4328,7 +4560,7 @@ "message": "Aktiver animasjoner" }, "showAnimations": { - "message": "Show animations" + "message": "Vis animasjoner" }, "addAccount": { "message": "Legg til konto" @@ -4340,15 +4572,15 @@ "message": "Data" }, "passkeys": { - "message": "Passkeys", + "message": "Passnøkler", "description": "A section header for a list of passkeys." }, "passwords": { - "message": "Passwords", + "message": "Passord", "description": "A section header for a list of passwords." }, "logInWithPasskeyAriaLabel": { - "message": "Log in with passkey", + "message": "Logg inn med passnøkkel", "description": "ARIA label for the inline menu button that logs in with a passkey." }, "assign": { @@ -4373,19 +4605,19 @@ } }, "addField": { - "message": "Add field" + "message": "Legg til felt" }, "add": { "message": "Legg til" }, "fieldType": { - "message": "Field type" + "message": "Felttype" }, "fieldLabel": { - "message": "Field label" + "message": "Feltetikett" }, "textHelpText": { - "message": "Use text fields for data like security questions" + "message": "Bruk tekstfelter for data som sikkerhetsspørsmål" }, "hiddenHelpText": { "message": "Use hidden fields for sensitive data like a password" @@ -4403,7 +4635,7 @@ "message": "Rediger felt" }, "editFieldLabel": { - "message": "Edit $LABEL$", + "message": "Rediger $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4412,7 +4644,7 @@ } }, "deleteCustomField": { - "message": "Delete $LABEL$", + "message": "Slett $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4421,7 +4653,7 @@ } }, "fieldAdded": { - "message": "$LABEL$ added", + "message": "$LABEL$ er lagt til", "placeholders": { "label": { "content": "$1", @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4456,7 +4691,7 @@ } }, "selectCollectionsToAssign": { - "message": "Select collections to assign" + "message": "Velg samlinger å tilordne" }, "personalItemTransferWarningSingular": { "message": "1 item will be permanently transferred to the selected organization. You will no longer own this item." @@ -4496,19 +4731,10 @@ "message": "Successfully assigned collections" }, "nothingSelected": { - "message": "You have not selected anything." - }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } + "message": "Du har ikke valgt noe." }, "itemsMovedToOrg": { - "message": "Items moved to $ORGNAME$", + "message": "Gjenstandene ble flyttet til $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -4517,7 +4743,7 @@ } }, "itemMovedToOrg": { - "message": "Item moved to $ORGNAME$", + "message": "Gjenstanden ble flyttet til $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -4543,7 +4769,7 @@ } }, "itemLocation": { - "message": "Item Location" + "message": "Gjenstandens plassering" }, "fileSend": { "message": "File Send" @@ -4557,20 +4783,14 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { - "message": "Account actions" + "message": "Kontohandlinger" }, "showNumberOfAutofillSuggestions": { "message": "Show number of login autofill suggestions on extension icon" }, "showQuickCopyActions": { - "message": "Show quick copy actions on Vault" + "message": "Vis hurtigkopieringshandlinger i hvelvet" }, "systemDefault": { "message": "Systemforvalg" @@ -4579,16 +4799,16 @@ "message": "Enterprise policy requirements have been applied to this setting" }, "sshPrivateKey": { - "message": "Private key" + "message": "Privat nøkkel" }, "sshPublicKey": { - "message": "Public key" + "message": "Offentlig nøkkel" }, "sshFingerprint": { - "message": "Fingerprint" + "message": "Fingeravtrykk" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "Nøkkeltype" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -4606,31 +4826,31 @@ "message": "Prøv igjen" }, "vaultCustomTimeoutMinimum": { - "message": "Minimum custom timeout is 1 minute." + "message": "Minste egendefinerte tidsavbrudd er 1 minutt." }, "additionalContentAvailable": { - "message": "Additional content is available" + "message": "Ytterligere innhold er tilgjengelig" }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, "showCharacterCount": { - "message": "Show character count" + "message": "Vis tegntelleren" }, "hideCharacterCount": { - "message": "Hide character count" + "message": "Skjul tegntelleren" }, "itemsInTrash": { - "message": "Items in trash" + "message": "Gjenstander i papirkurven" }, "noItemsInTrash": { - "message": "No items in trash" + "message": "Ingen gjenstander i papirkurven" }, "noItemsInTrashDesc": { - "message": "Items you delete will appear here and be permanently deleted after 30 days" + "message": "Gjenstander du sletter, vises her og slettes permanent etter 30 dager" }, "trashWarning": { - "message": "Items that have been in trash more than 30 days will automatically be deleted" + "message": "Gjenstander som har ligget i papirkurven i mer enn 30 dager, blir automatisk slettet" }, "restore": { "message": "Gjenopprett" @@ -4641,23 +4861,50 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrisk opplåsing er utilgjengelig for øyeblikket." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Autentiserer" }, "fillGeneratedPassword": { - "message": "Fill generated password", + "message": "Fyll inn generert passord", "description": "Heading for the password generator within the inline menu" }, "passwordRegenerated": { - "message": "Password regenerated", + "message": "Passord ble generert på nytt", "description": "Notification message for when a password has been regenerated" }, "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "message": "Vil du lagre påloggingen i Bitwarden?", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { - "message": "Space", + "message": "Mellomrom", "description": "Represents the space key in screen reader content as a readable word" }, "tildeCharacterDescriptor": { @@ -4665,15 +4912,15 @@ "description": "Represents the ~ key in screen reader content as a readable word" }, "backtickCharacterDescriptor": { - "message": "Backtick", + "message": "Baklengs apostrof", "description": "Represents the ` key in screen reader content as a readable word" }, "exclamationCharacterDescriptor": { - "message": "Exclamation mark", + "message": "Utropstegn", "description": "Represents the ! key in screen reader content as a readable word" }, "atSignCharacterDescriptor": { - "message": "At sign", + "message": "Alfakrøll", "description": "Represents the @ key in screen reader content as a readable word" }, "hashSignCharacterDescriptor": { @@ -4681,11 +4928,11 @@ "description": "Represents the # key in screen reader content as a readable word" }, "dollarSignCharacterDescriptor": { - "message": "Dollar sign", + "message": "Dollartegn", "description": "Represents the $ key in screen reader content as a readable word" }, "percentSignCharacterDescriptor": { - "message": "Percent sign", + "message": "Prosenttegn", "description": "Represents the % key in screen reader content as a readable word" }, "caretCharacterDescriptor": { @@ -4693,7 +4940,7 @@ "description": "Represents the ^ key in screen reader content as a readable word" }, "ampersandCharacterDescriptor": { - "message": "Ampersand", + "message": "Prosenttegn", "description": "Represents the & key in screen reader content as a readable word" }, "asteriskCharacterDescriptor": { @@ -4701,23 +4948,23 @@ "description": "Represents the * key in screen reader content as a readable word" }, "parenLeftCharacterDescriptor": { - "message": "Left parenthesis", + "message": "Venstre parantes", "description": "Represents the ( key in screen reader content as a readable word" }, "parenRightCharacterDescriptor": { - "message": "Right parenthesis", + "message": "Høyre parantes", "description": "Represents the ) key in screen reader content as a readable word" }, "hyphenCharacterDescriptor": { - "message": "Underscore", + "message": "Understrek", "description": "Represents the _ key in screen reader content as a readable word" }, "underscoreCharacterDescriptor": { - "message": "Hyphen", + "message": "Bindestrek", "description": "Represents the - key in screen reader content as a readable word" }, "plusCharacterDescriptor": { - "message": "Plus", + "message": "Plusstegn", "description": "Represents the + key in screen reader content as a readable word" }, "equalsCharacterDescriptor": { @@ -4725,19 +4972,19 @@ "description": "Represents the = key in screen reader content as a readable word" }, "braceLeftCharacterDescriptor": { - "message": "Left brace", + "message": "Venstre krøllparentes", "description": "Represents the { key in screen reader content as a readable word" }, "braceRightCharacterDescriptor": { - "message": "Right brace", + "message": "Høyre krøllparentes", "description": "Represents the } key in screen reader content as a readable word" }, "bracketLeftCharacterDescriptor": { - "message": "Left bracket", + "message": "Venstre firkantparantes", "description": "Represents the [ key in screen reader content as a readable word" }, "bracketRightCharacterDescriptor": { - "message": "Right bracket", + "message": "Høyre firkantparantes", "description": "Represents the ] key in screen reader content as a readable word" }, "pipeCharacterDescriptor": { @@ -4745,69 +4992,69 @@ "description": "Represents the | key in screen reader content as a readable word" }, "backSlashCharacterDescriptor": { - "message": "Back slash", + "message": "Skråstrek bakover", "description": "Represents the back slash key in screen reader content as a readable word" }, "colonCharacterDescriptor": { - "message": "Colon", + "message": "Kolon", "description": "Represents the : key in screen reader content as a readable word" }, "semicolonCharacterDescriptor": { - "message": "Semicolon", + "message": "Semikolon", "description": "Represents the ; key in screen reader content as a readable word" }, "doubleQuoteCharacterDescriptor": { - "message": "Double quote", + "message": "Hermetegn", "description": "Represents the double quote key in screen reader content as a readable word" }, "singleQuoteCharacterDescriptor": { - "message": "Single quote", + "message": "Apostrofe", "description": "Represents the ' key in screen reader content as a readable word" }, "lessThanCharacterDescriptor": { - "message": "Less than", + "message": "Mindre enn", "description": "Represents the < key in screen reader content as a readable word" }, "greaterThanCharacterDescriptor": { - "message": "Greater than", + "message": "Større enn", "description": "Represents the > key in screen reader content as a readable word" }, "commaCharacterDescriptor": { - "message": "Comma", + "message": "Komma", "description": "Represents the , key in screen reader content as a readable word" }, "periodCharacterDescriptor": { - "message": "Period", + "message": "Tidsperiode", "description": "Represents the . key in screen reader content as a readable word" }, "questionCharacterDescriptor": { - "message": "Question mark", + "message": "Spørsmålstegn", "description": "Represents the ? key in screen reader content as a readable word" }, "forwardSlashCharacterDescriptor": { - "message": "Forward slash", + "message": "Skråstrek", "description": "Represents the / key in screen reader content as a readable word" }, "lowercaseAriaLabel": { - "message": "Lowercase" + "message": "Små bokstaver" }, "uppercaseAriaLabel": { - "message": "Uppercase" + "message": "Store bokstaver" }, "generatedPassword": { - "message": "Generated password" + "message": "Generert passord" }, "compactMode": { - "message": "Compact mode" + "message": "Kompakt modus" }, "beta": { "message": "Beta" }, "importantNotice": { - "message": "Important notice" + "message": "Viktig melding" }, "setupTwoStepLogin": { - "message": "Set up two-step login" + "message": "Sett opp 2-trinnspålogging" }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." @@ -4816,7 +5063,7 @@ "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." }, "remindMeLater": { - "message": "Remind me later" + "message": "Minn meg på det senere" }, "newDeviceVerificationNoticePageOneFormContent": { "message": "Do you have reliable access to your email, $EMAIL$?", @@ -4828,24 +5075,69 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "Nei, det gjør jeg ikke" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { "message": "Yes, I can reliably access my email" }, "turnOnTwoStepLogin": { - "message": "Turn on two-step login" + "message": "Slå på 2-trinnsinnlogging" }, "changeAcctEmail": { - "message": "Change account email" + "message": "Endre kontoens E-postadresse" }, "extensionWidth": { - "message": "Extension width" + "message": "Utvidelsens bredde" }, "wide": { - "message": "Wide" + "message": "Bred" }, "extraWide": { - "message": "Extra wide" + "message": "Ekstra bred" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Vennligst oppdater skrivebordsprogrammet ditt" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "For å bruke biometrisk opplåsing, må du oppdatere skrivebordsprogrammet eller skru av fingeravtrykksopplåsing i skrivebordsinnstillingene." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index 6ab3755c8f4..c9c29611deb 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Master password hint (optional)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Copy notes" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { "message": "Fill", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Edit folder" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "New folder" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Generate passphrase" }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "regeneratePassword": { "message": "Regenerate password" }, @@ -454,22 +482,6 @@ "length": { "message": "Length" }, - "uppercase": { - "message": "Uppercase (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Lowercase (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Numbers (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Special characters (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Include", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Number of words" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Your web browser does not support easy clipboard copying. Copy it manually instead." }, - "verifyIdentity": { - "message": "Verify identity" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "Your vault is locked. Verify your identity to continue." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Restart registration" }, @@ -875,6 +904,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "An unexpected error has occurred." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "Show cards on Tab page" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "List card items on the Tab page for easy autofill." }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "Show identities on Tab page" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "Clear clipboard", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Save" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Ask to update existing login" }, @@ -1084,10 +1169,6 @@ "message": "Light", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized dark", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Export from" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Purchase Premium" }, - "premiumPurchaseAlert": { - "message": "You can purchase Premium membership on the bitwarden.com web vault. Do you want to visit the website now?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Open new tab" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "Login unavailable" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Lost access to all of your two-factor providers? Use your recovery code to turn off all two-factor providers from your account." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Self-hosted environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Custom environment" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "Server URL" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Drag to sort" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Text" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Username generator" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "Use this password" }, @@ -2063,12 +2163,24 @@ "message": "to create a strong unique password", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "Vault timeout action" }, "vaultTimeoutAction1": { "message": "Timeout action" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "Lock", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Settings have been edited" - }, - "environmentEditedClick": { - "message": "Click here" - }, - "environmentEditedReset": { - "message": "to reset to pre-configured settings" - }, "serverVersion": { "message": "Server version" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Log in with device" }, - "loginWithDeviceEnabledInfo": { - "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" - }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "View all log in options" }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" - }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "You will be notified once the request is approved" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3396,38 +3649,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Active account" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Available accounts" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "No values to copy" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organization is deactivated" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "You have not selected anything." }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra wide" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index d167ba220c1..1c2b09ca3e3 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -7,7 +7,7 @@ "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Thuis, op werk of onderweg. Bitwarden beveiligt makkelijk all je wachtwoorden, passkeys en gevoelige informatie", + "message": "Thuis, op werk of onderweg. Bitwarden beveiligt makkelijk al je wachtwoorden, passkeys en gevoelige informatie", "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { @@ -38,7 +38,7 @@ "message": "Rond het aanmaken van je account af met het instellen van een wachtwoord" }, "enterpriseSingleSignOn": { - "message": "Single sign-on voor bedrijven" + "message": "Enterprise Single Sign-On" }, "cancel": { "message": "Annuleren" @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Hoofdwachtwoordhint (optioneel)" }, + "passwordStrengthScore": { + "message": "Wachtwoordsterkte score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Lid van organisatie worden" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Notities kopiëren" }, + "copy": { + "message": "Kopiëren", + "description": "Copy to clipboard" + }, "fill": { "message": "Invullen", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Als je het e-mailadres van je account invult, versturen we je je wachtwoordhint" }, - "passwordHint": { - "message": "Wachtwoordhint" - }, - "enterEmailToGetHint": { - "message": "Voer het e-mailadres van je account in om je hoofdwachtwoordhint te ontvangen." - }, "getMasterPasswordHint": { "message": "Hoofdwachtwoordhint opvragen" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Map bewerken" }, + "editFolderWithName": { + "message": "Map bewerken: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "Nieuwe map" }, @@ -409,7 +425,7 @@ "message": "Ontdek Bitwarden community-forums" }, "contactSupport": { - "message": "Contacteer Bitwarden support" + "message": "Contacteer Bitwarden ondersteuning" }, "sync": { "message": "Synchroniseren" @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Wachtwoordzin genereren" }, + "passwordGenerated": { + "message": "Wachtwoord gegenereerd" + }, + "passphraseGenerated": { + "message": "Wachtwoorden gegenereerd" + }, + "usernameGenerated": { + "message": "Gebruikersnaam gegenereerd" + }, + "emailGenerated": { + "message": "E-mail gegenereerd" + }, "regeneratePassword": { "message": "Wachtwoord opnieuw genereren" }, @@ -454,22 +482,6 @@ "length": { "message": "Lengte" }, - "uppercase": { - "message": "Hoofdletters (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Kleine letters (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Cijfers (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Speciale tekens (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Toevoegen", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Speciale tekens toevoegen", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Aantal woorden" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Je webbrowser ondersteunt kopiëren naar plakbord niet. Kopieer handmatig." }, - "verifyIdentity": { - "message": "Identiteit verifiëren" + "verifyYourIdentity": { + "message": "Verifieer je identiteit" + }, + "weDontRecognizeThisDevice": { + "message": "We herkennen dit apparaat niet. Voer de code in die naar je e-mail is verzonden om je identiteit te verifiëren." + }, + "continueLoggingIn": { + "message": "Doorgaan met inloggen" }, "yourVaultIsLocked": { "message": "Je kluis is vergrendeld. Bevestig je identiteit om door te gaan." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Inloggen op Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Voer de code in die naar je e-mailadres is verstuurd" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Voer de code uit je authenticatie-app in" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Druk op je YubiKey om te verifiëren" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Jouw account vereist Duo tweestapslogin. Volg de onderstaande stappen om het inloggen te voltooien." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Volg de onderstaande stappen om het inloggen af te ronden." + }, "restartRegistration": { "message": "Registratie herstarten" }, @@ -875,6 +904,9 @@ "no": { "message": "Nee" }, + "location": { + "message": "Locatie" + }, "unexpectedError": { "message": "Er is een onverwachte fout opgetreden." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Vraag om een item toe te voegen als het niet is gevonden is je kluis. Dit geld voor alle ingelogde accounts." }, - "showCardsInVaultView": { - "message": "Kaarten als Autofill-suggesties in de kluisweergave weergeven" + "showCardsInVaultViewV2": { + "message": "Kaarten altijd als auto-invullen suggesties in de kluisweergave weergeven" }, "showCardsCurrentTab": { "message": "Kaarten weergeven op tabpagina" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Kaartenitems weergeven op de tabpagina voor gemakkelijk automatisch invullen." }, - "showIdentitiesInVaultView": { - "message": "Identiteiten als Autofill-suggesties in de kluisweergave weergeven" + "showIdentitiesInVaultViewV2": { + "message": "Identiteiten altijd als auto-invullen suggesties in de kluisweergave weergeven" }, "showIdentitiesCurrentTab": { "message": "Identiteiten weergeven op tabpagina" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Klik op items om automatisch in te vullen op de kluisweergave" }, + "clickToAutofill": { + "message": "Klik items in de automatisch invullen suggestie om in te vullen" + }, "clearClipboard": { "message": "Klembord wissen", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Ja, nu opslaan" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ opgeslagen in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ bijgewerkt in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Opslaan als nieuwe login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Login bijwerken", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Login opslaan?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Bestaande login bijwerken?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login opgeslagen", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login bijgewerkt", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Fout bij opslaan", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh nee! We konden dit niet opslaan. Probeer de gegevens handmatig in te voeren.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Vraag om bijwerken bestaande login" }, @@ -1084,10 +1169,6 @@ "message": "Licht", "description": "Light color" }, - "solarizedDark": { - "message": "Overbelicht donker", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Exporteren vanuit" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Premium aanschaffen" }, - "premiumPurchaseAlert": { - "message": "Je kunt een Premium-abonnement aanschaffen in de webkluis op bitwarden.com. Wil je de website nu bezoeken?" - }, "premiumPurchaseAlertV2": { "message": "Je kunt Premium via je accountinstellingen in de Bitwarden-webapp kopen." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Mijn gegevens onthouden" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "30 dagen niet opnieuw vragen op dit apparaat" + }, "sendVerificationCodeEmailAgain": { "message": "E-mail met verificatiecode opnieuw versturen" }, "useAnotherTwoStepMethod": { "message": "Gebruik een andere methode voor tweestapsaanmelding" }, + "selectAnotherMethod": { + "message": "Kies een andere methode", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Gebruik je herstelcode" + }, "insertYubiKey": { "message": "Plaats je YubiKey in de USB-poort van je computer en druk op de knop." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Nieuwe tab openen" }, + "openInNewTab": { + "message": "Openen in nieuwe tab" + }, "webAuthnAuthenticate": { "message": "Authenticeer WebAuthn" }, + "readSecurityKey": { + "message": "Beveiligingssleutel lezen" + }, + "awaitingSecurityKeyInteraction": { + "message": "Wacht op interactie met beveiligingssleutel…" + }, "loginUnavailable": { "message": "Login niet beschikbaar" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Opties voor tweestapsaanmelding" }, + "selectTwoStepLoginMethod": { + "message": "Kies methode voor tweestapslogin" + }, "recoveryCodeDesc": { "message": "Ben je de toegang tot al je tweestapsaanbieders verloren? Gebruik dan je herstelcode om alle tweestapsaanbieders op je account uit te schakelen." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Zelfgehoste omgeving" }, - "selfHostedEnvironmentFooter": { - "message": "Geef de basis-URL van jouw zelfgehoste Bitwarden-installatie." - }, "selfHostedBaseUrlHint": { "message": "Specificeer de basis-URL van je zelfgehoste Bitwarden-installatie. Bijvoorbeeld: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Aangepaste omgeving" }, - "customEnvironmentFooter": { - "message": "Voor gevorderde gebruikers. Je kunt de basis-URL van elke dienst afzonderlijk instellen." - }, "baseUrl": { "message": "Server-URL" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Slepen om te sorteren" }, + "dragToReorder": { + "message": "Sleep om te herschikken" + }, "cfTypeText": { "message": "Tekst" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Gebruikersnaamgenerator" }, + "useThisEmail": { + "message": "Dit e-mailadres gebruiken" + }, "useThisPassword": { "message": "Dit wachtwoord gebruiken" }, @@ -2063,12 +2163,24 @@ "message": "om een sterk uniek wachtwoord te maken", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Kluis aanpassingen" + }, "vaultTimeoutAction": { "message": "Actie bij time-out" }, "vaultTimeoutAction1": { "message": "Time-out actie" }, + "newCustomizationOptionsCalloutTitle": { + "message": "Nieuwe aanpassingsopties" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Pas je kluis ervaring aan met snelle kopieeracties, compacte modus en meer!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "Alle weergave-instellingen bekijken" + }, "lock": { "message": "Vergrendelen", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domeinen", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Geblokkeerde domeinen" + }, + "learnMoreAboutBlockedDomains": { + "message": "Meer informatie over geblokkeerde domeinen" + }, "excludedDomains": { "message": "Uitgesloten domeinen" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden zal voor deze domeinen niet vragen om de wachtwoorden op te slaan voor alle ingelogde accounts. Je moet de pagina verversen om de wijzigingen op te slaan." }, + "blockedDomainsDesc": { + "message": "Autofill en andere gerelateerde functies worden niet aangeboden voor deze websites. Vernieuw de pagina om de wijzigingen toe te passen." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is geblokkeerd voor deze website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Dit aanpassen in instellingen" + }, + "change": { + "message": "Wijzigen" + }, + "changeButtonTitle": { + "message": "Wachtwoord wijzigen - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "Wachtwoorden in gevaar" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ vraagt je om één wachtwoord te wijzigen omdat deze een risico vormt.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ vraagt je om de $COUNT$ wachtwoorden te wijzigen omdat ze een risico vormen.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Je organisaties vragen je de $COUNT$ wachtwoorden te wijzigen omdat ze een risico vormen.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Eén risicovol wachtwoord beoordelen en wijzigen" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "$COUNT$ risicovolle wachtwoorden beoordelen en wijzigen", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Risicovolle wachtwoorden sneller wijzigen" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Werk je instellingen bij voor het snel automatisch invullen van je wachtwoorden en genereren van nieuwe" + }, + "reviewAtRiskLogins": { + "message": "Risicovolle logins bekijken" + }, + "reviewAtRiskPasswords": { + "message": "Risicovolle wachtwoorden bekijken" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "De wachtwoorden van je organisatie zijn in gevaar omdat ze zwak, hergebruikt en/of gelekt zijn.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Voorbeeld van een lijst van risicovolle logins" + }, + "generatePasswordSlideDesc": { + "message": "Genereer snel een sterk, uniek wachtwoord met het automatisch invulmenu van Bitwarden op de risicovolle website.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Voorbeeld van het automatisch invulmenu van Bitwarden met een gegenereerd wachtwoord" + }, + "updateInBitwarden": { + "message": "Bijwerken in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden vraagt je vervolgens het wachtwoord bij te werken in de wachtwoordbeheerder.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Voorbeeld van een Bitwarden melding die de gebruiker aanspoort tot het bijwerken van de login" + }, + "turnOnAutofill": { + "message": "Automatisch invullen inschakelen" + }, + "turnedOnAutofill": { + "message": "Automatisch invullen ingeschakeld" + }, + "dismiss": { + "message": "Sluiten" + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Wijzigingen in geblokkeerde domeinen opgeslagen" + }, "excludedDomainsSavedSuccess": { "message": "Uitgesloten domeinwijzigingen opgeslagen" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Fout" }, + "decryptionError": { + "message": "Ontsleutelingsfout" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden kon de onderstaande kluisitem(s) niet ontsleutelen." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Neem contact op met de klantenservice", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "om extra dataverlies te voorkomen.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Gebruikersnamen genereren" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ heeft je aanvraag geweigerd. Neem contact op met je serviceprovider voor hulp.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ heeft je aanvraag geweigerd: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Kan $SERVICENAME$ gemaskeerde e-mailaccount-ID niet verkrijgen.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Instellingen zijn bijgewerkt" - }, - "environmentEditedClick": { - "message": "Klik hier" - }, - "environmentEditedReset": { - "message": "om terug te gaan naar vooraf geconfigureerde instellingen" - }, "serverVersion": { "message": "Serverversie" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Inloggen met je hoofdwachtwoord" }, - "loggingInAs": { - "message": "Inloggen als" - }, - "notYou": { - "message": "Ben jij dit niet?" - }, "newAroundHere": { "message": "Nieuw hier?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Inloggen met apparaat" }, - "loginWithDeviceEnabledInfo": { - "message": "Inloggen met apparaat moet aangezet worden in de instellingen van de Bitwarden app. Nood aan een andere optie?" - }, "fingerprintPhraseHeader": { "message": "Vingerafdrukzin" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "Alle inlogopties bekijken" }, - "viewAllLoginOptionsV1": { - "message": "Alle inlogopties weergeven" - }, "notificationSentDevice": { "message": "Er is een melding naar je apparaat verzonden." }, + "notificationSentDevicePart1": { + "message": "Ontgrendel Bitwarden op je apparaat of op de" + }, + "notificationSentDeviceAnchor": { + "message": "webapp" + }, + "notificationSentDevicePart2": { + "message": "Zorg ervoor dat de vingerafdrukzin overeenkomt met de onderstaande voor je deze goedkeurt." + }, "aNotificationWasSentToYourDevice": { "message": "Er is een melding naar je apparaat verzonden" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Zorg ervoor dat je kluis is ontgrendeld en de vingerafdrukzin hetzelfde is op het andere apparaat" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "Je krijgt een melding zodra de aanvraag is goedgekeurd" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Inloggen gestart" }, + "logInRequestSent": { + "message": "Verzoek verzonden" + }, "exposedMasterPassword": { "message": "Gelekt hoofdwachtwoord" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Goedkeuring van beheerder vragen" }, - "approveWithMasterPassword": { - "message": "Goedkeuren met hoofdwachtwoord" - }, "ssoIdentifierRequired": { "message": "Organisatie SSO-identificatie vereist." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Je verzoek is naar je beheerder verstuurd." }, - "youWillBeNotifiedOnceApproved": { - "message": "Je krijgt een melding zodra je bent goedgekeurd." - }, "troubleLoggingIn": { "message": "Problemen met inloggen?" }, @@ -3396,38 +3649,6 @@ "message": "In-/Uitklappen", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Gegevens importeren naar Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Je LastPass-gegevens beschermen en naar Bitwarden importeren?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Als niet-versleuteld bestand opslaan", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Naar Bitwarden importeren", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importeren…", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Gegevens succesvol geïmporteerd!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Fout bij importeren. Controleer console voor meer informatie.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Netwerkfout opgetreden tijdens het importeren.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Aliasdomein" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Actief account" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Beschikbare accounts" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Suggesties automatisch invullen" }, + "itemSuggestions": { + "message": "Voorgestelde items" + }, "autofillSuggestionsTip": { "message": "Inlogitem opslaan voor automatisch invullen op deze site" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "$FIELD$, $VALUE$ kopiëren", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "Geen waarden om te kopiëren" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Itemnaam" }, - "cannotRemoveViewOnlyCollections": { - "message": "Je kunt verzamelingen niet verwijderen met alleen rechten voor weergeven: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organisatie is gedeactiveerd" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Website URI herschikken. Gebruik de pijltjestoets om het item omhoog of omlaag te verplaatsen." + }, "reorderFieldUp": { "message": "$LABEL$ is naar boven verplaatst, positie $INDEX$ van $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "Je hebt niets geselecteerd." }, - "movedItemsToOrg": { - "message": "Geselecteerde items verplaatst naar $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items verplaatst naar $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Tekst-Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden heeft een nieuw uiterlijk!" - }, - "bitwardenNewLookDesc": { - "message": "Automatisch invullen en zoeken is makkelijker en intuïtiever dan ooit vanaf het tabblad Kluis. Kijk rond!" - }, "accountActions": { "message": "Accountacties" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "Je hebt geen toestemming om dit item te bewerken" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrisch ontgrendelen is niet beschikbaar omdat pincode of wachtwoordontgrendeling eerst vereist is." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrisch ontgrendelen is momenteel niet beschikbaar." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrisch ontgrendelen is niet beschikbaar vanwege verkeerd geconfigureerde systeembestanden." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrisch ontgrendelen is niet beschikbaar vanwege verkeerd geconfigureerde systeembestanden." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometrisch ontgrendelen is niet beschikbaar omdat de Bitwarden-desktopapp is afgesloten." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometrisch ontgrendelen is niet beschikbaar omdat het niet is ingeschakeld voor $EMAIL$ in de Bitwarden-desktopapp.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrisch ontgrendelen is momenteel niet beschikbaar om een onbekende reden." + }, "authenticating": { "message": "Aan het inloggen" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra breed" + }, + "sshKeyWrongPassword": { + "message": "Het wachtwoord dat je hebt ingevoerd is onjuist." + }, + "importSshKey": { + "message": "Importeren" + }, + "confirmSshKeyPassword": { + "message": "Wachtwoord bevestigen" + }, + "enterSshKeyPasswordDesc": { + "message": "Voer het wachtwoord voor de SSH sleutel in." + }, + "enterSshKeyPassword": { + "message": "Wachtwoord invoeren" + }, + "invalidSshKey": { + "message": "De SSH-sleutel is ongeldig" + }, + "sshKeyTypeUnsupported": { + "message": "Het type SSH-sleutel is niet ondersteund" + }, + "importSshKeyFromClipboard": { + "message": "Sleutel importeren van klembord" + }, + "sshKeyImported": { + "message": "SSH-sleutel succesvol geïmporteerd" + }, + "cannotRemoveViewOnlyCollections": { + "message": "Je kunt verzamelingen niet verwijderen met alleen rechten voor weergeven: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Werk je desktopapplicatie bij" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "Als je biometrische gegevens wilt gebruiken, moet je de desktopapplicatie bijwerken of vingerafdrukontgrendeling uitschakelen in de instellingen van de desktopapplicatie." + }, + "changeAtRiskPassword": { + "message": "Risicovol wachtwoord wijzigen" } } diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index 6ab3755c8f4..c9c29611deb 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Master password hint (optional)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Copy notes" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { "message": "Fill", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Edit folder" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "New folder" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Generate passphrase" }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "regeneratePassword": { "message": "Regenerate password" }, @@ -454,22 +482,6 @@ "length": { "message": "Length" }, - "uppercase": { - "message": "Uppercase (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Lowercase (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Numbers (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Special characters (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Include", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Number of words" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Your web browser does not support easy clipboard copying. Copy it manually instead." }, - "verifyIdentity": { - "message": "Verify identity" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "Your vault is locked. Verify your identity to continue." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Restart registration" }, @@ -875,6 +904,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "An unexpected error has occurred." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "Show cards on Tab page" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "List card items on the Tab page for easy autofill." }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "Show identities on Tab page" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "Clear clipboard", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Save" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Ask to update existing login" }, @@ -1084,10 +1169,6 @@ "message": "Light", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized dark", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Export from" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Purchase Premium" }, - "premiumPurchaseAlert": { - "message": "You can purchase Premium membership on the bitwarden.com web vault. Do you want to visit the website now?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Open new tab" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "Login unavailable" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Lost access to all of your two-factor providers? Use your recovery code to turn off all two-factor providers from your account." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Self-hosted environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Custom environment" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "Server URL" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Drag to sort" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Text" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Username generator" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "Use this password" }, @@ -2063,12 +2163,24 @@ "message": "to create a strong unique password", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "Vault timeout action" }, "vaultTimeoutAction1": { "message": "Timeout action" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "Lock", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Settings have been edited" - }, - "environmentEditedClick": { - "message": "Click here" - }, - "environmentEditedReset": { - "message": "to reset to pre-configured settings" - }, "serverVersion": { "message": "Server version" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Log in with device" }, - "loginWithDeviceEnabledInfo": { - "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" - }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "View all log in options" }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" - }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "You will be notified once the request is approved" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3396,38 +3649,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Active account" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Available accounts" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "No values to copy" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organization is deactivated" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "You have not selected anything." }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra wide" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index 6ab3755c8f4..c9c29611deb 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Master password hint (optional)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Copy notes" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { "message": "Fill", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Edit folder" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "New folder" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Generate passphrase" }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "regeneratePassword": { "message": "Regenerate password" }, @@ -454,22 +482,6 @@ "length": { "message": "Length" }, - "uppercase": { - "message": "Uppercase (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Lowercase (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Numbers (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Special characters (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Include", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Number of words" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Your web browser does not support easy clipboard copying. Copy it manually instead." }, - "verifyIdentity": { - "message": "Verify identity" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "Your vault is locked. Verify your identity to continue." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Restart registration" }, @@ -875,6 +904,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "An unexpected error has occurred." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "Show cards on Tab page" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "List card items on the Tab page for easy autofill." }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "Show identities on Tab page" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "Clear clipboard", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Save" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Ask to update existing login" }, @@ -1084,10 +1169,6 @@ "message": "Light", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized dark", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Export from" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Purchase Premium" }, - "premiumPurchaseAlert": { - "message": "You can purchase Premium membership on the bitwarden.com web vault. Do you want to visit the website now?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Open new tab" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "Login unavailable" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Lost access to all of your two-factor providers? Use your recovery code to turn off all two-factor providers from your account." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Self-hosted environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Custom environment" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "Server URL" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Drag to sort" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Text" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Username generator" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "Use this password" }, @@ -2063,12 +2163,24 @@ "message": "to create a strong unique password", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "Vault timeout action" }, "vaultTimeoutAction1": { "message": "Timeout action" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "Lock", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Settings have been edited" - }, - "environmentEditedClick": { - "message": "Click here" - }, - "environmentEditedReset": { - "message": "to reset to pre-configured settings" - }, "serverVersion": { "message": "Server version" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Log in with device" }, - "loginWithDeviceEnabledInfo": { - "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" - }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "View all log in options" }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" - }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "You will be notified once the request is approved" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3396,38 +3649,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Active account" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Available accounts" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "No values to copy" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organization is deactivated" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "You have not selected anything." }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra wide" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index a429059ea7d..f4c1303bd18 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -20,10 +20,10 @@ "message": "Utwórz konto" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Nowy użytkownik Bitwarden?" }, "logInWithPasskey": { - "message": "Zaloguj się używając passkey" + "message": "Zaloguj się używając klucza dostępu" }, "useSingleSignOn": { "message": "Użyj jednokrotnego logowania" @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Podpowiedź do hasła głównego (opcjonalnie)" }, + "passwordStrengthScore": { + "message": "Siła hasła: $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Dołącz do organizacji" }, @@ -120,7 +129,7 @@ "message": "Kopiuj hasło" }, "copyPassphrase": { - "message": "Kopiuj frazę bezpieczeństwa" + "message": "Skopiuj hasło wyrazowe" }, "copyNote": { "message": "Kopiuj notatkę" @@ -147,7 +156,7 @@ "message": "Kopiuj numer PESEL" }, "copyPassportNumber": { - "message": "Kopiuj numer paszportu" + "message": "Skopiuj numer paszportu" }, "copyLicenseNumber": { "message": "Kopiuj numer licencji" @@ -176,6 +185,10 @@ "copyNotes": { "message": "Kopiuj notatki" }, + "copy": { + "message": "Kopiuj", + "description": "Copy to clipboard" + }, "fill": { "message": "Wypełnij", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Wprowadź adres e-mail swojego konta, a podpowiedź hasła zostanie wysłana do Ciebie" }, - "passwordHint": { - "message": "Podpowiedź do hasła" - }, - "enterEmailToGetHint": { - "message": "Wpisz adres e-mail powiązany z kontem, aby otrzymać podpowiedź do hasła głównego." - }, "getMasterPasswordHint": { "message": "Uzyskaj podpowiedź do hasła głównego" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Edytuj folder" }, + "editFolderWithName": { + "message": "Edytuj folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "Nowy folder" }, @@ -443,7 +459,19 @@ "message": "Wygeneruj hasło" }, "generatePassphrase": { - "message": "Wygenruj frazę zabezpieczającą" + "message": "Wygeneruj hasło wyrazowe" + }, + "passwordGenerated": { + "message": "Hasło zostało wygenerowane" + }, + "passphraseGenerated": { + "message": "Hasło wyrazowe zostało wygenerowane" + }, + "usernameGenerated": { + "message": "Nazwa użytkownika została wygenerowana" + }, + "emailGenerated": { + "message": "E-mail został wygenerowany" }, "regeneratePassword": { "message": "Wygeneruj ponownie hasło" @@ -454,22 +482,6 @@ "length": { "message": "Długość" }, - "uppercase": { - "message": "Wielkie litery (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Małe litery (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Cyfry (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Znaki specjalne (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Uwzględnij", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Uwzględnij znaki specjalne", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Liczba słów" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Przeglądarka nie obsługuje łatwego kopiowania schowka. Skopiuj element ręcznie." }, - "verifyIdentity": { - "message": "Zweryfikuj tożsamość" + "verifyYourIdentity": { + "message": "Potwierdź swoją tożsamość" + }, + "weDontRecognizeThisDevice": { + "message": "Nie rozpoznajemy tego urządzenia. Wpisz kod wysłany na Twój e-mail, aby zweryfikować tożsamość." + }, + "continueLoggingIn": { + "message": "Kontynuuj logowanie" }, "yourVaultIsLocked": { "message": "Sejf jest zablokowany. Zweryfikuj swoją tożsamość, aby kontynuować." @@ -788,7 +802,7 @@ "message": "Zalogowałeś się pomyślnie" }, "youMayCloseThisWindow": { - "message": "Możesz zamknąć to okno." + "message": "Możesz zamknąć to okno" }, "masterPassSent": { "message": "Wysłaliśmy Tobie wiadomość e-mail z podpowiedzią do hasła głównego." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Zaloguj się do Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Wpisz kod wysłany na Twój adres e-mail" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Wpisz kod z aplikacji uwierzytelniającej" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Naciśnij YubiKey aby uwierzytelnić" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Logowanie dwustopniowe Duo jest wymagane dla twojego konta. Wykonaj poniższe kroki, by dokończyć logowanie" + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Wykonaj poniższe kroki, by dokończyć logowanie" + }, "restartRegistration": { "message": "Zrestartuj rejestrację" }, @@ -875,6 +904,9 @@ "no": { "message": "Nie" }, + "location": { + "message": "Lokalizacja" + }, "unexpectedError": { "message": "Wystąpił nieoczekiwany błąd." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Poproś o dodanie elementu, jeśli nie zostanie znaleziony w Twoim sejfie. Dotyczy wszystkich zalogowanych kont." }, - "showCardsInVaultView": { - "message": "Pokaż karty jako sugestie autouzupełniania w widoku sejfu" + "showCardsInVaultViewV2": { + "message": "Zawsze pokazuj karty jako sugestie autouzupełniania w widoku sejfu" }, "showCardsCurrentTab": { "message": "Pokaż karty na stronie głównej" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Pokaż elementy karty na stronie głównej, aby ułatwić autouzupełnianie." }, - "showIdentitiesInVaultView": { - "message": "Pokaż tożsamości jako sugestie autouzupełniania w widoku sejfu" + "showIdentitiesInVaultViewV2": { + "message": "Zawsze pokazuj tożsamości jako sugestie autouzupełniania w widoku sejfu" }, "showIdentitiesCurrentTab": { "message": "Pokaż tożsamości na stronie głównej" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Kliknij na dane logowania, aby autouzupełnić w widoku Sejfu" }, + "clickToAutofill": { + "message": "Kliknij elementy w sugestii autouzupełniania, aby wypełnić" + }, "clearClipboard": { "message": "Wyczyść schowek", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Zapisz" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ został zapisany w Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ został zaktualizowany w Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Zapisz jako nowy login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Zaktualizuj dane logowania", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Zapisać dane logowania?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Zaktualizować istniejące dane logowania?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Dane logowania zapisane", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Dane logowania zostały zaktualizowane", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Błąd podczas zapisywania", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "O nie! Nie mogliśmy zapisać tego. Spróbuj wprowadzić szczegóły ręcznie.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Poproś o aktualizację istniejących danych logowania" }, @@ -1031,10 +1116,10 @@ "message": "Poproś o aktualizację hasła, gdy zmiana zostanie wykryta na stronie. Dotyczy wszystkich zalogowanych kont." }, "enableUsePasskeys": { - "message": "Pytaj o zapisywanie i używanie passkey" + "message": "Pytaj o zapisywanie i używanie kluczy dostępu" }, "usePasskeysDesc": { - "message": "Pytaj o zapisywanie nowych passkey albo danych logowania z passkey w Twoim sejfie. Dotyczy wszystkich zalogowanych kont." + "message": "Pytaj o zapisywanie nowych kluczy dostępu albo danych logowania z kluczy w Twoim sejfie. Dotyczy wszystkich zalogowanych kont." }, "notificationChangeDesc": { "message": "Czy chcesz zaktualizować to hasło w Bitwarden?" @@ -1055,7 +1140,7 @@ "message": "Pokaż opcje menu kontekstowego" }, "contextMenuItemDesc": { - "message": "Użyj drugiego kliknięcia, aby uzyskać dostęp do generowania haseł i pasujących danych logowania do witryny. " + "message": "Użyj drugiego kliknięcia, aby uzyskać dostęp do generowania haseł i pasujących danych logowania do witryny." }, "contextMenuItemDescAlt": { "message": "Użyj drugiego kliknięcia, aby uzyskać dostęp do generowania haseł i pasujących danych logowania do witryny. Dotyczy wszystkich zalogowanych kont." @@ -1084,10 +1169,6 @@ "message": "Jasny", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized Dark", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Eksportuj z" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Kup konto Premium" }, - "premiumPurchaseAlert": { - "message": "Konto Premium możesz zakupić na stronie sejfu bitwarden.com. Czy chcesz otworzyć tę stronę?" - }, "premiumPurchaseAlertV2": { "message": "Możesz kupić Premium w ustawieniach konta w aplikacji internetowej Bitwarden." }, @@ -1320,7 +1398,7 @@ "message": "Limit czasu uwierzytelniania" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "Upłynął limit czasu uwierzytelniania. Uruchom ponownie proces logowania." }, "enterVerificationCodeEmail": { "message": "Wpisz 6-cyfrowy kod weryfikacyjny, który został przesłany na adres $EMAIL$.", @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Zapamiętaj mnie" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Nie pytaj ponownie na tym urządzeniu przez 30 dni" + }, "sendVerificationCodeEmailAgain": { "message": "Wyślij ponownie wiadomość z kodem weryfikacyjnym" }, "useAnotherTwoStepMethod": { "message": "Użyj innej metody logowania dwustopniowego" }, + "selectAnotherMethod": { + "message": "Wybierz inną metodę", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Użyj kodu odzyskiwania" + }, "insertYubiKey": { "message": "Włóż klucz YubiKey do portu USB komputera, a następnie dotknij jego przycisku." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Otwórz nową kartę" }, + "openInNewTab": { + "message": "Otwórz w nowej karcie" + }, "webAuthnAuthenticate": { "message": "Uwierzytelnianie WebAuthn" }, + "readSecurityKey": { + "message": "Odczytaj klucz bezpieczeństwa" + }, + "awaitingSecurityKeyInteraction": { + "message": "Oczekiwanie na interakcję z kluczem bezpieczeństwa..." + }, "loginUnavailable": { "message": "Logowanie jest niedostępne" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Opcje logowania dwustopniowego" }, + "selectTwoStepLoginMethod": { + "message": "Wybierz metodę logowania dwustopniowego" + }, "recoveryCodeDesc": { "message": "Utraciłeś dostęp do wszystkich swoich mechanizmów dwustopniowego logowania? Użyj kodów odzyskiwania, aby wyłączyć dwustopniowe logowanie na Twoim koncie." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Samodzielnie hostowane środowisko" }, - "selfHostedEnvironmentFooter": { - "message": "Wpisz podstawowy adres URL hostowanej instalacji Bitwarden." - }, "selfHostedBaseUrlHint": { "message": "Określ bazowy adres URL swojej instalacji Bitwarden. Przykład: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Niestandardowe środowisko" }, - "customEnvironmentFooter": { - "message": "Dla zaawansowanych użytkowników. Możesz wpisać podstawowy adres URL niezależnie dla każdej usługi." - }, "baseUrl": { "message": "Adres URL serwera" }, @@ -1478,7 +1572,7 @@ "message": "Pokazuj karty jako sugestie" }, "showInlineMenuOnIconSelectionLabel": { - "message": "Wyświetlaj sugestie kiedy ikona jest zaznaczona" + "message": "Wyświetlaj sugestie, kiedy ikona jest zaznaczona" }, "showInlineMenuOnFormFieldsDescAlt": { "message": "Dotyczy wszystkich zalogowanych kont." @@ -1523,7 +1617,7 @@ "message": "Domyślne ustawienie autouzupełniania" }, "defaultAutoFillOnPageLoadDesc": { - "message": "Po włączeniu autouzupełnianiu po załadowaniu strony, możesz włączyć lub wyłączyć tę funkcję dla poszczególnych wpisów." + "message": "Po włączeniu autouzupełnianiu po załadowaniu strony możesz włączyć lub wyłączyć tę funkcję dla poszczególnych wpisów." }, "itemAutoFillOnPageLoad": { "message": "Automatycznie uzupełniaj po załadowaniu strony (jeśli włączono w opcjach)" @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Przeciągnij, aby posortować" }, + "dragToReorder": { + "message": "Przeciągnij, aby zmienić kolejność" + }, "cfTypeText": { "message": "Tekst" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Generator nazw użytkownika" }, + "useThisEmail": { + "message": "Użyj tego adresu e-mail" + }, "useThisPassword": { "message": "Użyj tego hasła" }, @@ -2063,12 +2163,24 @@ "message": ", aby utworzyć mocne unikalne hasło", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Dostosowanie sejfu" + }, "vaultTimeoutAction": { "message": "Sposób blokowania sejfu" }, "vaultTimeoutAction1": { "message": "Akcja po przekroczeniu limitu czasu" }, + "newCustomizationOptionsCalloutTitle": { + "message": "Nowe opcje dostosowywania" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Dostosuj swój sejf dzięki akcjom szybkiego kopiowania, trybowi kompaktowemu i więcej!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "Zobacz wszystkie ustawienia wyglądu" + }, "lock": { "message": "Zablokuj", "description": "Verb form: to make secure or inaccessible by" @@ -2114,7 +2226,7 @@ "message": "URI został zapisany i automatycznie uzupełniony" }, "autoFillSuccess": { - "message": "Element został automatycznie uzupełniony" + "message": "Element został automatycznie uzupełniony " }, "insecurePageWarning": { "message": "Ostrzeżenie: Jest to niezabezpieczona strona HTTP i wszelkie przekazane informacje mogą być potencjalnie widoczne i zmienione przez innych. Ten login został pierwotnie zapisany na stronie bezpiecznej (HTTPS)." @@ -2123,7 +2235,7 @@ "message": "Nadal chcesz uzupełnić ten login?" }, "autofillIframeWarning": { - "message": "Formularz jest hostowany przez inną domenę niż zapisany adres URI dla tego loginu. Wybierz OK, aby i tak automatycznie wypełnić lub anuluj aby zatrzymać." + "message": "Formularz jest hostowany przez inną domenę niż zapisany adres URI dla tego loginu. Wybierz OK, aby i tak automatycznie wypełnić lub anuluj, aby zatrzymać." }, "autofillIframeWarningTip": { "message": "Aby zapobiec temu ostrzeżeniu w przyszłości, zapisz ten URI, $HOSTNAME$, dla tej witryny.", @@ -2267,7 +2379,7 @@ "message": "Klucz biometryczny jest niepoprawny" }, "nativeMessagingWrongUserKeyDesc": { - "message": "Odblokowanie biometryczne nie powiodło się. Sekretny klucz biometryczny nie odblokował sejfu. Spróbuj skonfigurować biometrię ponownie." + "message": "Odblokowanie biometryczne się nie powiodło. Sekretny klucz biometryczny nie odblokował sejfu. Spróbuj skonfigurować biometrię ponownie." }, "biometricsNotEnabledTitle": { "message": "Dane biometryczne są wyłączone" @@ -2324,6 +2436,12 @@ "message": "Domeny", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Zablokowane domeny" + }, + "learnMoreAboutBlockedDomains": { + "message": "Dowiedz się więcej o zablokowanych domenach" + }, "excludedDomains": { "message": "Wykluczone domeny" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Aplikacja Bitwarden nie będzie proponować zapisywania danych logowania dla tych domen dla wszystkich zalogowanych kont. Musisz odświeżyć stronę, aby zastosowywać zmiany." }, + "blockedDomainsDesc": { + "message": "Autouzupełnianie i inne powiązane funkcje nie będą oferowane dla tych stron. Aby zmiany zaczęły obowiązywać, musisz odświeżyć stronę." + }, + "autofillBlockedNoticeV2": { + "message": "Autouzupełnianie jest zablokowane dla tej witryny." + }, + "autofillBlockedNoticeGuidance": { + "message": "Zmień to w ustawieniach" + }, + "change": { + "message": "Zmień" + }, + "changeButtonTitle": { + "message": "Zmień hasło - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "Zagrożone hasła" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ prosi o zmianę jednego hasła, ponieważ jest ono zagrożone.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ prosi o zmianę $COUNT$ haseł, ponieważ są one zagrożone.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Twoje organizacje proszą o zmianę $COUNT$ haseł, ponieważ są one zagrożone.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Przejrzyj i zmień jedno zagrożone hasło" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Przejrzyj i zmień $COUNT$ zagrożonych haseł ", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Zmień zagrożone hasła szybciej" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Zaktualizuj swoje ustawienia, aby szybko autouzupełniać hasła i generować nowe" + }, + "reviewAtRiskLogins": { + "message": "Przejrzyj zagrożone loginy" + }, + "reviewAtRiskPasswords": { + "message": "Przejrzyj zagrożone hasła" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Twoje hasła organizacji są zagrożone, ponieważ są słabe, ponownie używane i/lub narażone.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Ilustracja listy loginów, które są zagrożone" + }, + "generatePasswordSlideDesc": { + "message": "Szybko wygeneruj silne, unikalne hasło z menu autouzupełniania Bitwarden na stronie narażonej na ryzyko.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Ilustracja menu autouzupełniania Bitwarden pokazująca wygenerowane hasło" + }, + "updateInBitwarden": { + "message": "Aktualizacja w Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden poprosi Cię o aktualizację hasła w menedżerze haseł.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Ilustracja powiadomienia Bitwardena, która prosi użytkownika o aktualizację logowania" + }, + "turnOnAutofill": { + "message": "Włącz autouzupełnienie" + }, + "turnedOnAutofill": { + "message": "Włączono autouzupełnianie" + }, + "dismiss": { + "message": "Odrzuć" + }, "websiteItemLabel": { "message": "Strona internetowa $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Zmiany w zablokowanych domenach zapisane" + }, "excludedDomainsSavedSuccess": { "message": "Zmiany w wykluczonych domenach zapisane" }, @@ -2540,7 +2773,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendFilePopoutDialogDesc": { - "message": "To create a file Send, you need to pop out the extension to a new window.", + "message": "Aby utworzyć plik Send, musisz wysunąć rozszerzenie do nowego okna.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLinuxChromiumFileWarning": { @@ -2553,7 +2786,7 @@ "message": "Aby wybrać plik za pomocą przeglądarki Safari, otwórz rozszerzenie w nowym oknie." }, "popOut": { - "message": "Pop out" + "message": "Odepnij" }, "sendFileCalloutHeader": { "message": "Zanim zaczniesz" @@ -2789,6 +3022,20 @@ "error": { "message": "Błąd" }, + "decryptionError": { + "message": "Błąd odszyfrowywania" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nie mógł odszyfrować elementów sejfu wymienionych poniżej." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Skontaktuj się z działem obsługi klienta,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "aby uniknąć dalszej utraty danych.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Wygeneruj nazwę użytkownika" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ odrzucił Twoje żądanie. Skontaktuj się z dostawcą usług w celu uzyskania pomocy.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ odrzucił Twoje żądanie: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Nie można uzyskać ID maskowanego konta e-mail dla $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Ustawienia zostały zmienione" - }, - "environmentEditedClick": { - "message": "Kliknij tutaj" - }, - "environmentEditedReset": { - "message": "aby zresetować do wstępnie skonfigurowanych ustawień" - }, "serverVersion": { "message": "Wersja serwera" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Logowanie hasłem głównym" }, - "loggingInAs": { - "message": "Logowanie jako" - }, - "notYou": { - "message": "To nie Ty?" - }, "newAroundHere": { "message": "Nowy użytkownik?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Zaloguj się za pomocą urządzenia" }, - "loginWithDeviceEnabledInfo": { - "message": "Logowanie za pomocą urządzenia musi być włączone w ustawieniach aplikacji Bitwarden. Potrzebujesz innej opcji?" - }, "fingerprintPhraseHeader": { "message": "Unikalny identyfikator konta" }, @@ -3070,20 +3323,23 @@ "viewAllLogInOptions": { "message": "Zobacz wszystkie sposoby logowania" }, - "viewAllLoginOptionsV1": { - "message": "Zobacz wszystkie sposoby logowania" - }, "notificationSentDevice": { "message": "Powiadomienie zostało wysłane na urządzenie." }, + "notificationSentDevicePart1": { + "message": "Odblokuj Bitwarden na swoim urządzeniu lub w" + }, + "notificationSentDeviceAnchor": { + "message": "aplikacji internetowej" + }, + "notificationSentDevicePart2": { + "message": "Upewnij się, że fraza odcisku palca zgadza się z tą poniżej, zanim zatwierdzisz." + }, "aNotificationWasSentToYourDevice": { "message": "Powiadomienie zostało wysłane na twoje urządzenie" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { - "message": "You will be notified once the request is approved" + "message": "Zostaniesz powiadomiony po zatwierdzeniu prośby" }, "needAnotherOptionV1": { "message": "Potrzebujesz innego sposobu?" @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Logowanie rozpoczęte" }, + "logInRequestSent": { + "message": "Żądanie wysłane" + }, "exposedMasterPassword": { "message": "Ujawnione hasło główne" }, @@ -3122,7 +3381,7 @@ } }, "autofillPageLoadPolicyActivated": { - "message": "Twoja organizacji włączyła autouzupełnianie podczas wczytywania strony." + "message": "Twoja organizacja włączyła autouzupełnianie podczas wczytywania strony." }, "howToAutofill": { "message": "Jak autouzupełniać" @@ -3197,7 +3456,7 @@ "message": "Zapamiętaj to urządzenie" }, "uncheckIfPublicDevice": { - "message": "Odznacz jeśli używasz publicznego urządzenia" + "message": "Odznacz, jeśli używasz publicznego urządzenia" }, "approveFromYourOtherDevice": { "message": "Zatwierdź z innego twojego urządzenia" @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Poproś administratora o zatwierdzenie" }, - "approveWithMasterPassword": { - "message": "Zatwierdź przy użyciu hasła głównego" - }, "ssoIdentifierRequired": { "message": "Identyfikator organizacji jest wymagany." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Twoja prośba została wysłana do Twojego administratora." }, - "youWillBeNotifiedOnceApproved": { - "message": "Zostaniesz powiadomiony po zatwierdzeniu." - }, "troubleLoggingIn": { "message": "Problem z zalogowaniem?" }, @@ -3396,43 +3649,11 @@ "message": "Zwiń/rozwiń", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Zaimportować Twoje dane do Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Ochronić Twoje dane LastPass i zaimportować do Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Zapisz jako niezaszyfrowany plik", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Importuj do Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importowanie...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Dane pomyślnie zaimportowane!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Błąd podczas importowania. Sprawdź konsolę, aby uzyskać szczegółowe informacje.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Wystąpił błąd sieci podczas importu.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Domena aliasu" }, "passwordRepromptDisabledAutofillOnPageLoad": { - "message": "Elementy z pytaniem o hasło głównege nie mogą być automatycznie wypełniane przy wczytywaniu strony. Automatyczne wypełnianie po wczytywania strony zostało wyłączone.", + "message": "Elementy z pytaniem o hasło główne nie mogą być autouzupełniane przy wczytywaniu strony. Autouzupełnianie podczas wczytywania strony zostało wyłączone.", "description": "Toast message for describing that master password re-prompt cannot be autofilled on page load." }, "autofillOnPageLoadSetToDefault": { @@ -3478,7 +3699,7 @@ "description": "Screen reader text (aria-label) for unlock account button in overlay" }, "totpCodeAria": { - "message": "Time-based One-Time Password Verification Code", + "message": "Kod weryfikacyjny jednorazowego hasła oparty na czasie", "description": "Aria label for the totp code displayed in the inline menu for autofill" }, "totpSecondsSpanAria": { @@ -3708,7 +3929,7 @@ "message": "Dane sejfu zostały wyeksportowane" }, "typePasskey": { - "message": "Passkey" + "message": "Klucz dostępu" }, "accessing": { "message": "Uzyskiwanie dostępu" @@ -3717,22 +3938,22 @@ "message": "Zalogowano!" }, "passkeyNotCopied": { - "message": "Passkey nie zostanie skopiowany" + "message": "Klucz dostępu nie zostanie skopiowany" }, "passkeyNotCopiedAlert": { - "message": "Passkey nie zostanie skopiowane do sklonowanego elementu. Czy chcesz kontynuować klonowanie tego elementu?" + "message": "Klucz dostępu nie zostanie skopiowany do sklonowanego elementu. Czy chcesz kontynuować klonowanie tego elementu?" }, "passkeyFeatureIsNotImplementedForAccountsWithoutMasterPassword": { "message": "Weryfikacja jest wymagana przez stronę inicjującą. Ta funkcja nie jest jeszcze zaimplementowana dla kont bez hasła głównego." }, "logInWithPasskeyQuestion": { - "message": "Zaloguj się za pomocą passkey?" + "message": "Zalogować za pomocą klucza dostępu?" }, "passkeyAlreadyExists": { - "message": "Passkey już istnieje dla tej aplikacji." + "message": "Klucz dostępu już istnieje dla tej aplikacji." }, "noPasskeysFoundForThisApplication": { - "message": "Nie znaleziono passkey'a dla tej aplikacji." + "message": "Nie znaleziono klucza dostępu dla tej aplikacji." }, "noMatchingPasskeyLogin": { "message": "Nie masz pasujących danych logowania do tej witryny." @@ -3741,37 +3962,37 @@ "message": "Brak pasujących loginów dla tej witryny" }, "searchSavePasskeyNewLogin": { - "message": "Wyszukaj alb zapisz passkey jako nowy login" + "message": "Wyszukaj albo zapisz klucz dostępu jako nowy login" }, "confirm": { "message": "Potwierdź" }, "savePasskey": { - "message": "Zapisz passkey" + "message": "Zapisz klucz dostępu" }, "savePasskeyNewLogin": { - "message": "Zapisz passkey jako nowe dane logowania" + "message": "Zapisz klucz dostępu jako nowe dane logowania" }, "chooseCipherForPasskeySave": { - "message": "Wybierz dane logowania do których przypisać passkey" + "message": "Wybierz dane logowania, do których przypisać klucz dostępu" }, "chooseCipherForPasskeyAuth": { - "message": "Wybierz passkey żeby się zalogować" + "message": "Wybierz klucz dostępu, żeby się zalogować" }, "passkeyItem": { - "message": "Element Passkey" + "message": "Element klucza dostępu" }, "overwritePasskey": { - "message": "Zastąpić passkey?" + "message": "Zastąpić klucz dostępu?" }, "overwritePasskeyAlert": { - "message": "Ten element zawiera już passkey. Czy na pewno chcesz nadpisać bieżący passkey?" + "message": "Ten element zawiera już klucz dostępu. Czy na pewno chcesz nadpisać bieżący klucza dostępu?" }, "featureNotSupported": { "message": "Funkcja nie jest jeszcze obsługiwana" }, "yourPasskeyIsLocked": { - "message": "Wymagane uwierzytelnienie aby używać passkey. Sprawdź swoją tożsamość, aby kontynuować." + "message": "Wymagane uwierzytelnienie, aby używać klucza dostępu. Sprawdź swoją tożsamość, aby kontynuować." }, "multifactorAuthenticationCancelled": { "message": "Uwierzytelnianie wieloskładnikowe zostało anulowane" @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Aktywne konto" }, + "bitwardenAccount": { + "message": "Konto Bitwarden" + }, "availableAccounts": { "message": "Dostępne konta" }, @@ -3973,13 +4197,16 @@ "message": "Sukces" }, "removePasskey": { - "message": "Usuń passkey" + "message": "Usuń klucz dostępu" }, "passkeyRemoved": { - "message": "Passkey został usunięty" + "message": "Klucz dostępu został usunięty" }, "autofillSuggestions": { - "message": "Sugestie autouzupełnienia" + "message": "Sugestie autouzupełniania" + }, + "itemSuggestions": { + "message": "Sugerowane elementy" }, "autofillSuggestionsTip": { "message": "Zapisz element logowania dla tej witryny, aby automatycznie wypełnić" @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Kopiuj $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "Brak wartości do skopiowania" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Nazwa elementu" }, - "cannotRemoveViewOnlyCollections": { - "message": "Nie można usunąć kolekcji z uprawnieniami tylko do przeglądania: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organizacja jest wyłączona" }, @@ -4340,7 +4572,7 @@ "message": "Dane" }, "passkeys": { - "message": "Passkeys", + "message": "Klucze dostępu", "description": "A section header for a list of passkeys." }, "passwords": { @@ -4348,7 +4580,7 @@ "description": "A section header for a list of passwords." }, "logInWithPasskeyAriaLabel": { - "message": "Zaloguj się za pomocą passkey", + "message": "Zaloguj się za pomocą klucza dostępu", "description": "ARIA label for the inline menu button that logs in with a passkey." }, "assign": { @@ -4430,7 +4662,7 @@ } }, "reorderToggleButton": { - "message": "Zmień kolejność $LABEL$. Użyj klawiszy że strzałkami aby przenieść element w górę lub w dół.", + "message": "Zmień kolejność $LABEL$. Użyj klawiszy ze strzałkami, aby przenieść element w górę lub w dół.", "placeholders": { "label": { "content": "$1", @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Zmień kolejność URI strony. Użyj klawiszy ze strzałkami, aby przenieść element w górę lub w dół." + }, "reorderFieldUp": { "message": "$LABEL$ przeniósł się w górę, pozycja $INDEX$ z $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "Nie zaznaczyłeś żadnych elementów." }, - "movedItemsToOrg": { - "message": "Zaznaczone elementy zostały przeniesione do $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Elementy przeniesione do $ORGNAME$", "placeholders": { @@ -4546,22 +4772,16 @@ "message": "Lokalizacja elementu" }, "fileSend": { - "message": "File Send" + "message": "Wysyłka pliku" }, "fileSends": { - "message": "File Sends" + "message": "Wysyłki plików" }, "textSend": { - "message": "Text Send" + "message": "Wysyłka tekstu" }, "textSends": { - "message": "Text Sends" - }, - "bitwardenNewLook": { - "message": "Bitwarden ma nowy wygląd!" - }, - "bitwardenNewLookDesc": { - "message": "Auto wypełnianie i szukanie na zakładce sejfu jest teraz prostsze i bardziej intuicyjne. Rozejrzyj się tam!" + "message": "Wysyłki tekstów" }, "accountActions": { "message": "Akcje konta" @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "Nie masz uprawnień do edycji tego elementu" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Odblokowanie odciskiem palca jest niedostępne, ponieważ najpierw wymagane jest odblokowanie kodem PIN lub hasłem." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Odblokowanie biometryczne jest obecnie niedostępne." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Odblokowanie biometryczne jest niedostępne z powodu nieprawidłowej konfiguracji plików systemowych." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Odblokowanie biometryczne jest niedostępne z powodu nieprawidłowej konfiguracji plików systemowych." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Odblokowanie odciskiem palca jest niedostępne, ponieważ aplikacja desktopowa Bitwarden jest zamknięta." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Odblokowanie biometryczne jest niedostępne, ponieważ nie jest włączone dla $EMAIL$ w aplikacji desktopowej Bitwarden.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Odblokowanie biometryczne jest obecnie niedostępne z nieznanego powodu." + }, "authenticating": { "message": "Uwierzytelnianie" }, @@ -4665,7 +4912,7 @@ "description": "Represents the ~ key in screen reader content as a readable word" }, "backtickCharacterDescriptor": { - "message": "Backtick", + "message": "Grawis", "description": "Represents the ` key in screen reader content as a readable word" }, "exclamationCharacterDescriptor": { @@ -4689,7 +4936,7 @@ "description": "Represents the % key in screen reader content as a readable word" }, "caretCharacterDescriptor": { - "message": "Caret", + "message": "Daszek", "description": "Represents the ^ key in screen reader content as a readable word" }, "ampersandCharacterDescriptor": { @@ -4745,7 +4992,7 @@ "description": "Represents the | key in screen reader content as a readable word" }, "backSlashCharacterDescriptor": { - "message": "Back slash", + "message": "Ukośnik wsteczny", "description": "Represents the back slash key in screen reader content as a readable word" }, "colonCharacterDescriptor": { @@ -4785,7 +5032,7 @@ "description": "Represents the ? key in screen reader content as a readable word" }, "forwardSlashCharacterDescriptor": { - "message": "Forward slash", + "message": "Ukośnik prawy", "description": "Represents the / key in screen reader content as a readable word" }, "lowercaseAriaLabel": { @@ -4804,22 +5051,22 @@ "message": "Beta" }, "importantNotice": { - "message": "Important notice" + "message": "Ważna informacja" }, "setupTwoStepLogin": { - "message": "Set up two-step login" + "message": "Skonfiguruj dwustopniowe logowanie" }, "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + "message": "Bitwarden wyśle kod na Twój adres e-mail w celu zweryfikowania logowania z nowych urządzeń, począwszy od lutego 2025 r." }, "newDeviceVerificationNoticeContentPage2": { - "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + "message": "Możesz skonfigurować dwustopniowe logowanie jako alternatywny sposób ochrony konta lub zmienić swój adres e-mail, do którego masz dostęp." }, "remindMeLater": { - "message": "Remind me later" + "message": "Przypomnij mi później" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", + "message": "Czy masz pewny dostęp do swojego adresu e-mail, $EMAIL$?", "placeholders": { "email": { "content": "$1", @@ -4828,16 +5075,16 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "Nie, nie mam" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" + "message": "Tak, mam pewny dostęp do mojego adresu e-mail" }, "turnOnTwoStepLogin": { - "message": "Turn on two-step login" + "message": "Włącz dwustopniowe logowanie" }, "changeAcctEmail": { - "message": "Change account email" + "message": "Zmień adres e-mail konta" }, "extensionWidth": { "message": "Szerokość rozszerzenia" @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Bardzo szerokie" + }, + "sshKeyWrongPassword": { + "message": "Wprowadzone hasło jest nieprawidłowe." + }, + "importSshKey": { + "message": "Importuj" + }, + "confirmSshKeyPassword": { + "message": "Potwierdź hasło" + }, + "enterSshKeyPasswordDesc": { + "message": "Wprowadź hasło dla klucza SSH." + }, + "enterSshKeyPassword": { + "message": "Wprowadź hasło" + }, + "invalidSshKey": { + "message": "Klucz SSH jest nieprawidłowy" + }, + "sshKeyTypeUnsupported": { + "message": "Typ klucza SSH nie jest obsługiwany" + }, + "importSshKeyFromClipboard": { + "message": "Importuj klucz ze schowka" + }, + "sshKeyImported": { + "message": "Klucz SSH zaimportowano pomyślnie" + }, + "cannotRemoveViewOnlyCollections": { + "message": "Nie można usunąć kolekcji z uprawnieniami tylko do przeglądania: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Zaktualizuj aplikację na komputer" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "Aby używać odblokowywania biometrycznego, zaktualizuj aplikację na komputerze lub wyłącz odblokowywanie odciskiem palca w ustawieniach aplikacji na komputerze." + }, + "changeAtRiskPassword": { + "message": "Zmień hasło zagrożone" } } diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index cd0c9979103..0b5d569f443 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Dica de Senha Mestra (opcional)" }, + "passwordStrengthScore": { + "message": "Pontos fortes da senha: $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Juntar-se à organização" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Copiar Notas" }, + "copy": { + "message": "Copiar", + "description": "Copy to clipboard" + }, "fill": { "message": "Preencher", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Digite o endereço de seu correio eletrônico da sua conta e sua dica da senha será enviada para você" }, - "passwordHint": { - "message": "Dica da Senha" - }, - "enterEmailToGetHint": { - "message": "Insira o seu endereço de e-mail para receber a dica da sua senha mestra." - }, "getMasterPasswordHint": { "message": "Obter dica da senha mestra" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Editar Pasta" }, + "editFolderWithName": { + "message": "Editar pasta: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "Nova pasta" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Gerar frase secreta" }, + "passwordGenerated": { + "message": "Senha gerada" + }, + "passphraseGenerated": { + "message": "Senha gerada" + }, + "usernameGenerated": { + "message": "Nome de usuário gerado" + }, + "emailGenerated": { + "message": "E-mail gerado" + }, "regeneratePassword": { "message": "Gerar Nova Senha" }, @@ -454,22 +482,6 @@ "length": { "message": "Comprimento" }, - "uppercase": { - "message": "Maiúsculas (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Minúsculas (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Números (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Caracteres especiais (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Incluir", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Incluir caracteres especiais", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Número de Palavras" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "O seu navegador web não suporta cópia para a área de transferência. Em alternativa, copie manualmente." }, - "verifyIdentity": { - "message": "Verificar Identidade" + "verifyYourIdentity": { + "message": "Verifique a sua identidade" + }, + "weDontRecognizeThisDevice": { + "message": "Não reconhecemos este dispositivo. Digite o código enviado por e-mail para verificar a sua identidade." + }, + "continueLoggingIn": { + "message": "Manter sessão" }, "yourVaultIsLocked": { "message": "Seu cofre está trancado. Verifique sua identidade para continuar." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Inicie a sessão no Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Digite o código enviado por e-mail" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Digite o código a partir do seu autenticador" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Insira sua YubiKey para autenticar" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Siga os passos abaixo para finalizar o login." + }, "restartRegistration": { "message": "Reiniciar registro" }, @@ -875,6 +904,9 @@ "no": { "message": "Não" }, + "location": { + "message": "Localização" + }, "unexpectedError": { "message": "Ocorreu um erro inesperado." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Pedir para adicionar um item se um não for encontrado no seu cofre. Aplica-se a todas as contas logadas." }, - "showCardsInVaultView": { - "message": "Mostrar cartões como sugestões de preenchimento automático na exibição do Cofre" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "Mostrar cartões em páginas com guias." @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Exibir itens de cartão em páginas com abas para simplificar o preenchimento automático" }, - "showIdentitiesInVaultView": { - "message": "Mostrar identifica como sugestões de preenchimento automático na exibição do Cofre" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "Exibir Identidades na Aba Atual" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "Limpar Área de Transferência", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Salvar" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ salvo no Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ atualizado no Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Salvar como nova sessão", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Atualizar sessão", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Salvar sessão?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Atualizar a sessão atual?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Sessão salva", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Sessão atualizada", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Erro ao salvar", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Ops! Não foi possível salvar isso. Tente digitar os detalhes manualmente.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Pedir para atualizar os dados de login existentes" }, @@ -1084,10 +1169,6 @@ "message": "Claro", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized (escuro)", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Exportar de" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Comprar Premium" }, - "premiumPurchaseAlert": { - "message": "Você pode comprar a assinatura premium no cofre web em bitwarden.com. Você deseja visitar o site agora?" - }, "premiumPurchaseAlertV2": { "message": "Você pode comprar Premium nas configurações de sua conta no aplicativo web do Bitwarden." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Lembrar de mim" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Não perguntar novamente neste dispositivo por 30 dias" + }, "sendVerificationCodeEmailAgain": { "message": "Enviar código de verificação para o e-mail novamente" }, "useAnotherTwoStepMethod": { "message": "Utilizar outro método de verificação em duas etapas" }, + "selectAnotherMethod": { + "message": "Escolher outro método", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use seu código de recuperação" + }, "insertYubiKey": { "message": "Insira a sua YubiKey na porta USB do seu computador, e depois toque no botão da mesma." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Abrir nova aba" }, + "openInNewTab": { + "message": "Abrir numa nova aba" + }, "webAuthnAuthenticate": { "message": "Autenticar WebAuthn" }, + "readSecurityKey": { + "message": "Ler chave de segurança" + }, + "awaitingSecurityKeyInteraction": { + "message": "Aguardando interação com a chave de segurança..." + }, "loginUnavailable": { "message": "Sessão Indisponível" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Opções de Login em Duas Etapas" }, + "selectTwoStepLoginMethod": { + "message": "Escolher iniciar sessão em duas etapas" + }, "recoveryCodeDesc": { "message": "Perdeu o acesso a todos os seus provedores de duas etapas? Utilize o seu código de recuperação para desativar todos os provedores de duas etapas da sua conta." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Ambiente Auto-hospedado" }, - "selfHostedEnvironmentFooter": { - "message": "Especifique a URL de base da sua instalação local do Bitwarden." - }, "selfHostedBaseUrlHint": { "message": "Especifique a URL de base da sua instalação local do Bitwarden. Exemplo: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Ambiente Personalizado" }, - "customEnvironmentFooter": { - "message": "Para usuários avançados. Você pode especificar a URL de base de cada serviço independentemente." - }, "baseUrl": { "message": "URL do Servidor" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Arrastar para ordenar" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Texto" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Gerador de usuário" }, + "useThisEmail": { + "message": "Usar este e-mail" + }, "useThisPassword": { "message": "Use esta senha" }, @@ -2063,12 +2163,24 @@ "message": "para criar uma senha única e forte", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Personalização do cofre" + }, "vaultTimeoutAction": { "message": "Ação de Tempo Limite do Cofre" }, "vaultTimeoutAction1": { "message": "Ação do tempo" }, + "newCustomizationOptionsCalloutTitle": { + "message": "Novas opções de personalização" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "Ver todas as configurações de aparência" + }, "lock": { "message": "Bloquear", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domínios", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Domínios bloqueados" + }, + "learnMoreAboutBlockedDomains": { + "message": "Saiba mais sobre domínios bloqueados" + }, "excludedDomains": { "message": "Domínios Excluídos" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "O Bitwarden não irá pedir para salvar os detalhes de credencial para estes domínios. Você deve atualizar a página para que as alterações entrem em vigor." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Alterar" + }, + "changeButtonTitle": { + "message": "Alterar senha - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "Senhas em risco" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Atualizar no Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Ativar preenchimento automático" + }, + "turnedOnAutofill": { + "message": "Desativar preenchimento automático" + }, + "dismiss": { + "message": "Dispensar" + }, "websiteItemLabel": { "message": "Site $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Mudanças de domínios excluídos salvas" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Erro" }, + "decryptionError": { + "message": "Erro ao descriptografar" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "O Bitwarden não pode descriptografar o(s) item(ns) do cofre listado abaixo." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Gerar Usuário" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Não foi possível obter a máscara do ID da conta de email $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "As configurações foram editadas" - }, - "environmentEditedClick": { - "message": "Clique aqui" - }, - "environmentEditedReset": { - "message": "para redefinir para as configurações pré-configuradas" - }, "serverVersion": { "message": "Versão do servidor" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Entrar com a senha mestra" }, - "loggingInAs": { - "message": "Logar como" - }, - "notYou": { - "message": "Não é você?" - }, "newAroundHere": { "message": "Novo por aqui?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Fazer login com dispositivo" }, - "loginWithDeviceEnabledInfo": { - "message": "Login com dispositivo deve ser habilitado nas configurações do aplicativo móvel do Bitwarden. Necessita de outra opção?" - }, "fingerprintPhraseHeader": { "message": "Frase de impressão digital" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "Visualizar todas as opções de login" }, - "viewAllLoginOptionsV1": { - "message": "Visualizar todas as opções de login" - }, "notificationSentDevice": { "message": "Uma notificação foi enviada para seu dispositivo." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "Uma notificação foi enviada para o seu dispositivo" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Certifique-se que sua conta esteja desbloqueada e que a frase de identificação corresponda à do outro dispositivo" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "Você será notificado assim que a requisição for aprovada" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Login iniciado" }, + "logInRequestSent": { + "message": "Pedido enviado" + }, "exposedMasterPassword": { "message": "Senha mestra comprometida" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Solicitar aprovação do administrador" }, - "approveWithMasterPassword": { - "message": "Aprovar com senha mestra" - }, "ssoIdentifierRequired": { "message": "Identificador SSO da organização é necessário." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Seu pedido foi enviado para seu administrador." }, - "youWillBeNotifiedOnceApproved": { - "message": "Será notificado assim que for aprovado." - }, "troubleLoggingIn": { "message": "Problemas em efetuar login?" }, @@ -3396,38 +3649,6 @@ "message": "Alternar colapso", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Importar seus dados para o Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Proteja seus dados do LastPass e importe para o Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Salvar como arquivo não criptografado", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Importar para o Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importando...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Dados importados com sucesso!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Erro ao importar. Verifique o console para detalhes.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Erro de rede encontrado durante a importação.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias do domínio" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Conta ativa" }, + "bitwardenAccount": { + "message": "Conta Bitwarden" + }, "availableAccounts": { "message": "Contas disponíveis" }, @@ -3979,7 +4203,10 @@ "message": "Chave de acesso removida" }, "autofillSuggestions": { - "message": "Sugestões de autopreenchimento" + "message": "Sugestões de preenchimento automático" + }, + "itemSuggestions": { + "message": "Itens sugeridos" }, "autofillSuggestionsTip": { "message": "Salvar um item de login para este site autopreenchimento" @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copiar $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "Não há valores para copiar" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Nome do item" }, - "cannotRemoveViewOnlyCollections": { - "message": "Você não pode remover coleções com permissões de Somente leitura: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "A organização está desativada" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ se moveu para cima, posição $INDEX$ de $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "Você selecionou nada." }, - "movedItemsToOrg": { - "message": "Itens selecionados movidos para $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Itens movidos para $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Texto enviado" }, - "bitwardenNewLook": { - "message": "Bitwarden tem uma nova aparência!" - }, - "bitwardenNewLookDesc": { - "message": "É mais fácil e mais intuitivo do que nunca autopreenchimento e pesquise na guia Cofre. Dê uma olhada ao redor!" - }, "accountActions": { "message": "Ações da conta" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "Você não tem permissão para editar este arquivo" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Autenticando" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra Grande" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "Você não pode remover coleções com permissões de Somente leitura: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index def50289ae6..c1f1f51609c 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -62,7 +62,7 @@ "message": "Uma dica da palavra-passe mestra pode ajudá-lo a lembrar-se da sua palavra-passe, caso se esqueça dela." }, "masterPassHintText": { - "message": "Se se esquecer da sua palavra-passe, a dica da palavra-passe pode ser enviada para o seu e-mail. Máximo de $CURRENT$/$MAXIMUM$ caracteres.", + "message": "Se se esquecer da sua palavra-passe, a dica da palavra-passe pode ser enviada para o seu e-mail. Máximo de $CURRENT$/$MAXIMUM$ carateres.", "placeholders": { "current": { "content": "$1", @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Dica da palavra-passe mestra (opcional)" }, + "passwordStrengthScore": { + "message": "Pontuação da força da palavra-passe: $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Aderir à organização" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Copiar notas" }, + "copy": { + "message": "Copiar", + "description": "Copy to clipboard" + }, "fill": { "message": "Preencher", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Introduza o endereço de e-mail da sua conta e ser-lhe-á enviada a sua dica da palavra-passe" }, - "passwordHint": { - "message": "Dica da palavra-passe" - }, - "enterEmailToGetHint": { - "message": "Introduza o endereço de e-mail da sua conta para receber a dica da sua palavra-passe mestra." - }, "getMasterPasswordHint": { "message": "Obter a dica da palavra-passe mestra" }, @@ -296,7 +303,7 @@ "message": "Continuar para a loja de extensões do navegador?" }, "continueToBrowserExtensionStoreDesc": { - "message": "Ajude outras pessoas a descobrir se o Bitwarden lhes é adequado. Visite a loja de extensões do seu navegador e deixe uma avaliação agora." + "message": "Ajude outras pessoas a descobrir se o Bitwarden lhes é adequado. Visite a loja de extensões do seu navegador e deixe uma classificação agora." }, "changeMasterPasswordOnWebConfirmation": { "message": "Pode alterar a sua palavra-passe mestra na aplicação Web Bitwarden." @@ -340,7 +347,7 @@ "message": "Gestor de Segredos Bitwarden" }, "continueToSecretsManagerPageDesc": { - "message": "Armazene, gira e partilhe segredos de programador de forma segura com o Gestor de Segredos Bitwarden. Saiba mais no site bitwarden.com." + "message": "Armazene, faça a gestão e partilhe de forma segura os segredos dos programadores com o Gestor de Segredos Bitwarden. Saiba mais no site bitwarden.com." }, "passwordlessDotDev": { "message": "Passwordless.dev" @@ -372,6 +379,15 @@ "editFolder": { "message": "Editar pasta" }, + "editFolderWithName": { + "message": "Editar pasta: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "Nova pasta" }, @@ -379,7 +395,7 @@ "message": "Nome da pasta" }, "folderHintText": { - "message": "Aninhe uma pasta adicionando o nome da pasta principal seguido de um \"/\". Exemplo: Redes Sociais/Fóruns" + "message": "Crie uma subpasta adicionando o nome da pasta principal seguido de um \"/\". Exemplo: Redes Sociais/Fóruns" }, "noFoldersAdded": { "message": "Nenhuma pasta adicionada" @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Gerar frase de acesso" }, + "passwordGenerated": { + "message": "Palavra-passe gerada" + }, + "passphraseGenerated": { + "message": "Frase de acesso gerada" + }, + "usernameGenerated": { + "message": "Nome de utilizador gerado" + }, + "emailGenerated": { + "message": "E-mail gerado" + }, "regeneratePassword": { "message": "Regenerar palavra-passe" }, @@ -454,28 +482,12 @@ "length": { "message": "Comprimento" }, - "uppercase": { - "message": "Maiúsculas (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Minúsculas (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Números (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Caracteres especiais (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Incluir", "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Incluir caracteres em maiúsculas", + "message": "Incluir carateres em maiúsculas", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -483,7 +495,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Incluir caracteres em minúsculas", + "message": "Incluir carateres em minúsculas", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -499,13 +511,9 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Incluir caracteres especiais", + "message": "Incluir carateres especiais", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Número de palavras" }, @@ -523,10 +531,10 @@ "message": "Mínimo de números" }, "minSpecial": { - "message": "Mínimo de caracteres especiais" + "message": "Mínimo de carateres especiais" }, "avoidAmbiguous": { - "message": "Evitar caracteres ambíguos", + "message": "Evitar carateres ambíguos", "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { @@ -639,13 +647,19 @@ "message": "Outras opções" }, "rateExtension": { - "message": "Avaliar a extensão" + "message": "Classificar a extensão" }, "browserNotSupportClipboard": { "message": "O seu navegador Web não suporta a cópia fácil da área de transferência. Em vez disso, copie manualmente." }, - "verifyIdentity": { - "message": "Verificar identidade" + "verifyYourIdentity": { + "message": "Verifique a sua identidade" + }, + "weDontRecognizeThisDevice": { + "message": "Não reconhecemos este dispositivo. Introduza o código enviado para o seu e-mail para verificar a sua identidade." + }, + "continueLoggingIn": { + "message": "Continuar a iniciar sessão" }, "yourVaultIsLocked": { "message": "O seu cofre está bloqueado. Verifique a sua identidade para continuar." @@ -763,7 +777,7 @@ "message": "É necessário reescrever a palavra-passe mestra." }, "masterPasswordMinlength": { - "message": "A palavra-passe mestra deve ter pelo menos $VALUE$ caracteres.", + "message": "A palavra-passe mestra deve ter pelo menos $VALUE$ carateres.", "description": "The Master Password must be at least a specific number of characters long.", "placeholders": { "value": { @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Iniciar sessão no Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Introduza o código enviado para o seu e-mail" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Introduza o código da sua app de autenticação" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Prima a sua YubiKey para se autenticar" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "A verificação de dois passos do Duo é necessária para a sua conta. Siga os passos abaixo para concluir o início de sessão." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Siga os passos abaixo para concluir o início de sessão." + }, "restartRegistration": { "message": "Reiniciar registo" }, @@ -875,6 +904,9 @@ "no": { "message": "Não" }, + "location": { + "message": "Localização" + }, "unexpectedError": { "message": "Ocorreu um erro inesperado." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Pedir para adicionar um item se não for encontrado um no seu cofre. Aplica-se a todas as contas com sessão iniciada." }, - "showCardsInVaultView": { - "message": "Mostrar cartões como sugestões de preenchimento automático na vista do cofre" + "showCardsInVaultViewV2": { + "message": "Mostrar sempre cartões como sugestões de preenchimento automático na vista do cofre" }, "showCardsCurrentTab": { "message": "Mostrar cartões na página Separador" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Listar itens de cartões na página Separador para facilitar o preenchimento automático." }, - "showIdentitiesInVaultView": { - "message": "Mostrar identidades como sugestões de preenchimento automático na vista do cofre" + "showIdentitiesInVaultViewV2": { + "message": "Mostrar sempre identidades como sugestões de preenchimento automático na vista do cofre" }, "showIdentitiesCurrentTab": { "message": "Mostrar identidades na página Separador" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Clique nos itens para preencher automaticamente na vista do cofre" }, + "clickToAutofill": { + "message": "Clique nos itens da sugestão de preenchimento automático para preencher" + }, "clearClipboard": { "message": "Limpar área de transferência", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Guardar" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ guardado no Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ atualizado no Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Guardar como nova credencial", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Atualizar credencial", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Guardar credencial?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Atualizar credencial existente?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Credencial guardada", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Credencial atualizada", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Erro ao guardar", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh não! Não conseguimos guardar isto. Tente introduzir os detalhes manualmente.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Pedir para atualizar credencial existente" }, @@ -1084,10 +1169,6 @@ "message": "Claro", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized (escuro)", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Exportar de" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Adquirir Premium" }, - "premiumPurchaseAlert": { - "message": "Pode adquirir uma subscrição Premium no cofre web em bitwarden.com. Pretende visitar o site agora?" - }, "premiumPurchaseAlertV2": { "message": "Pode adquirir o Premium a partir das definições da sua conta na aplicação Web Bitwarden." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Memorizar" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Não voltar a perguntar neste dispositivo durante 30 dias" + }, "sendVerificationCodeEmailAgain": { "message": "Enviar e-mail com o código de verificação novamente" }, "useAnotherTwoStepMethod": { "message": "Utilizar outro método de verificação de dois passos" }, + "selectAnotherMethod": { + "message": "Selecionar outro método", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Utilize o seu código de recuperação" + }, "insertYubiKey": { "message": "Introduza a sua YubiKey na porta USB do seu computador, depois toque no botão da mesma." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Abrir novo separador" }, + "openInNewTab": { + "message": "Abrir num novo separador" + }, "webAuthnAuthenticate": { "message": "Autenticar o WebAuthn" }, + "readSecurityKey": { + "message": "Ler chave de segurança" + }, + "awaitingSecurityKeyInteraction": { + "message": "A aguardar interação da chave de segurança..." + }, "loginUnavailable": { "message": "Início de sessão indisponível" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Opções de verificação de dois passos" }, + "selectTwoStepLoginMethod": { + "message": "Selecionar método de verificação de dois passos" + }, "recoveryCodeDesc": { "message": "Perdeu o acesso a todos os seus fornecedores de verificação de dois passos? Utilize o seu código de recuperação para desativar todos os fornecedores de verificação de dois passos da sua conta." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Ambiente auto-hospedado" }, - "selfHostedEnvironmentFooter": { - "message": "Especifique o URL de base da sua instalação Bitwarden hospedada no local." - }, "selfHostedBaseUrlHint": { "message": "Especifique o URL de base da sua instalação Bitwarden hospedada no local. Exemplo: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Ambiente personalizado" }, - "customEnvironmentFooter": { - "message": "Para utilizadores avançados. Pode especificar o URL de base de cada serviço de forma independente." - }, "baseUrl": { "message": "URL do servidor" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Arraste para ordenar" }, + "dragToReorder": { + "message": "Arraste para reordenar" + }, "cfTypeText": { "message": "Texto" }, @@ -1583,7 +1680,7 @@ "message": "Booleano" }, "cfTypeCheckbox": { - "message": "Checkbox" + "message": "Caixa de verificação" }, "cfTypeLinked": { "message": "Associado", @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Gerador de nomes de utilizador" }, + "useThisEmail": { + "message": "Utilizar este e-mail" + }, "useThisPassword": { "message": "Utilizar esta palavra-passe" }, @@ -2063,12 +2163,24 @@ "message": "para criar uma palavra-passe forte e única", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Personalização do cofre" + }, "vaultTimeoutAction": { "message": "Ação de tempo limite do cofre" }, "vaultTimeoutAction1": { "message": "Ação de tempo limite" }, + "newCustomizationOptionsCalloutTitle": { + "message": "Novas opções de personalização" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Personalize a sua experiência no cofre com ações de cópia rápida, modo compacto e muito mais!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "Ver todas as definições de Aspeto" + }, "lock": { "message": "Bloquear", "description": "Verb form: to make secure or inaccessible by" @@ -2168,16 +2280,16 @@ } }, "policyInEffectUppercase": { - "message": "Contém um ou mais caracteres em maiúsculas" + "message": "Contém um ou mais carateres em maiúsculas" }, "policyInEffectLowercase": { - "message": "Contém um ou mais caracteres em minúsculas" + "message": "Contém um ou mais carateres em minúsculas" }, "policyInEffectNumbers": { "message": "Contém um ou mais números" }, "policyInEffectSpecial": { - "message": "Contém um ou mais dos seguintes caracteres especiais $CHARS$", + "message": "Contém um ou mais dos seguintes carateres especiais $CHARS$", "placeholders": { "chars": { "content": "$1", @@ -2324,6 +2436,12 @@ "message": "Domínios", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Domínios bloqueados" + }, + "learnMoreAboutBlockedDomains": { + "message": "Saiba mais sobre domínios bloqueados" + }, "excludedDomains": { "message": "Domínios excluídos" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "O Bitwarden não pedirá para guardar os detalhes de início de sessão destes domínios para todas as contas com sessão iniciada. É necessário atualizar a página para que as alterações tenham efeito." }, + "blockedDomainsDesc": { + "message": "O preenchimento automático e outras funcionalidades relacionadas não serão disponibilizados para estes sites. É necessário atualizar a página para que as alterações tenham efeito." + }, + "autofillBlockedNoticeV2": { + "message": "O preenchimento automático está bloqueado para este site." + }, + "autofillBlockedNoticeGuidance": { + "message": "Alterar esta opção nas definições" + }, + "change": { + "message": "Alterar" + }, + "changeButtonTitle": { + "message": "Alterar palavra-passe - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "Palavras-passe em risco" + }, + "atRiskPasswordDescSingleOrg": { + "message": "A $ORGANIZATION$ pede-lhe que altere uma palavra-passe por estarem em risco.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "A $ORGANIZATION$ pede-lhe que altere as $COUNT$ palavras-passe por estarem em risco.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "As suas organizações pedem-lhe que altere as $COUNT$ palavras-passe por estarem em risco.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Rever e alterar uma palavra-passe em risco" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Rever e alterar as $COUNT$ palavras-passe em risco", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Alterar mais rapidamente as palavras-passe em risco" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Atualize as suas definições para poder preencher automaticamente as suas palavras-passe e gerar novas palavras-passe" + }, + "reviewAtRiskLogins": { + "message": "Rever credenciais em risco" + }, + "reviewAtRiskPasswords": { + "message": "Rever palavras-passe em risco" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "As palavras-passe da sua organização estão em risco porque são fracas, reutilizadas e/ou expostas.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Ilustração de uma lista de credenciais que estão em risco" + }, + "generatePasswordSlideDesc": { + "message": "Gira rapidamente uma palavra-passe forte e única com o menu de preenchimento automático do Bitwarden no site em risco.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Ilustração do menu de preenchimento automático do Bitwarden com uma palavra-passe gerada" + }, + "updateInBitwarden": { + "message": "Atualização no Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "O Bitwarden pedir-lhe-á então para atualizar a palavra-passe no gestor de palavras-passe.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Ilustração de uma notificação do Bitwarden a pedir ao utilizador que atualize a credencial" + }, + "turnOnAutofill": { + "message": "Ativar o preenchimento automático" + }, + "turnedOnAutofill": { + "message": "Preenchimento automático ativado" + }, + "dismiss": { + "message": "Dispensar" + }, "websiteItemLabel": { "message": "Site $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Alterações do domínio bloqueado guardadas" + }, "excludedDomainsSavedSuccess": { "message": "Alterações do domínio excluído guardadas" }, @@ -2757,7 +2990,7 @@ "message": "Saiu da organização." }, "toggleCharacterCount": { - "message": "Mostrar/ocultar contagem de caracteres" + "message": "Mostrar/ocultar contagem de carateres" }, "sessionTimeout": { "message": "A sua sessão expirou. Por favor, volte atrás e tente iniciar sessão novamente." @@ -2789,6 +3022,20 @@ "error": { "message": "Erro" }, + "decryptionError": { + "message": "Erro de desencriptação" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "O Bitwarden não conseguiu desencriptar o(s) item(ns) do cofre listado(s) abaixo." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contacte o serviço de apoio ao cliente", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "para evitar perdas adicionais de dados.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Gerar nome de utilizador" }, @@ -2810,7 +3057,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " Utilize $RECOMMENDED$ caracteres ou mais para gerar uma palavra-passe forte.", + "message": " Utilize $RECOMMENDED$ carateres ou mais para gerar uma palavra-passe forte.", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ recusou o seu pedido. Por favor, contacte o seu fornecedor de serviços para obter assistência.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ recusou o seu pedido: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Não foi possível obter o ID da conta de e-mail mascarada de $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "As definições foram editadas" - }, - "environmentEditedClick": { - "message": "Clique aqui" - }, - "environmentEditedReset": { - "message": "para voltar às definições predefinidas" - }, "serverVersion": { "message": "Versão do servidor" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Iniciar sessão com a palavra-passe mestra" }, - "loggingInAs": { - "message": "A iniciar sessão como" - }, - "notYou": { - "message": "Utilizador incorreto?" - }, "newAroundHere": { "message": "É novo por cá?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Iniciar sessão com o dispositivo" }, - "loginWithDeviceEnabledInfo": { - "message": "O início de sessão com o dispositivo deve ser ativado nas definições da aplicação Bitwarden. Precisa de outra opção?" - }, "fingerprintPhraseHeader": { "message": "Frase de impressão digital" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "Ver todas as opções de início de sessão" }, - "viewAllLoginOptionsV1": { - "message": "Ver todas as opções de início de sessão" - }, "notificationSentDevice": { "message": "Foi enviada uma notificação para o seu dispositivo." }, + "notificationSentDevicePart1": { + "message": "Desbloqueie o Bitwarden no seu dispositivo ou no " + }, + "notificationSentDeviceAnchor": { + "message": "aplicação web" + }, + "notificationSentDevicePart2": { + "message": "Certifique-se de que a frase da impressão digital corresponde à frase abaixo indicada antes de a aprovar." + }, "aNotificationWasSentToYourDevice": { "message": "Foi enviada uma notificação para o seu dispositivo" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Certifique-se de que a sua conta está desbloqueada e que a frase de impressão digital corresponde à do outro dispositivo" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "Será notificado quando o pedido for aprovado" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "A preparar o início de sessão" }, + "logInRequestSent": { + "message": "Pedido enviado" + }, "exposedMasterPassword": { "message": "Palavra-passe mestra exposta" }, @@ -3113,7 +3372,7 @@ "message": "A sua palavra-passe mestra não pode ser recuperada se a esquecer!" }, "characterMinimum": { - "message": "$LENGTH$ caracteres no mínimo", + "message": "$LENGTH$ carateres no mínimo", "placeholders": { "length": { "content": "$1", @@ -3161,7 +3420,7 @@ "message": "O atalho de preenchimento automático de credenciais não está definido. Altere-o nas definições do navegador." }, "autofillLoginShortcutText": { - "message": "O atalho de preenchimento automático de credenciais é $COMMAND$. Gira todos os atalhos nas definidções do navegador.", + "message": "O atalho de preenchimento automático de credenciais é $COMMAND$. Organize todos os atalhos nas definições do navegador.", "placeholders": { "command": { "content": "$1", @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Pedir aprovação do administrador" }, - "approveWithMasterPassword": { - "message": "Aprovar com a palavra-passe mestra" - }, "ssoIdentifierRequired": { "message": "É necessário o identificador de SSO da organização." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "O seu pedido foi enviado ao seu administrador." }, - "youWillBeNotifiedOnceApproved": { - "message": "Será notificado quando for aprovado." - }, "troubleLoggingIn": { "message": "Problemas a iniciar sessão?" }, @@ -3290,7 +3543,7 @@ "message": "Procurar" }, "inputMinLength": { - "message": "O campo deve ter pelo menos $COUNT$ caracteres.", + "message": "O campo deve ter pelo menos $COUNT$ carateres.", "placeholders": { "count": { "content": "$1", @@ -3299,7 +3552,7 @@ } }, "inputMaxLength": { - "message": "O campo não pode exceder os $COUNT$ caracteres de comprimento.", + "message": "O campo não pode exceder os $COUNT$ carateres de comprimento.", "placeholders": { "count": { "content": "$1", @@ -3308,7 +3561,7 @@ } }, "inputForbiddenCharacters": { - "message": "Não são permitidos os seguintes caracteres: $CHARACTERS$", + "message": "Não são permitidos os seguintes carateres: $CHARACTERS$", "placeholders": { "characters": { "content": "$1", @@ -3317,7 +3570,7 @@ } }, "inputMinValue": { - "message": "O valor do campo tem de ser, pelo menos, $MIN$ caracteres.", + "message": "O valor do campo tem de ser, pelo menos, $MIN$ carateres.", "placeholders": { "min": { "content": "$1", @@ -3326,7 +3579,7 @@ } }, "inputMaxValue": { - "message": "O valor do campo não pode exceder os $MAX$ caracteres.", + "message": "O valor do campo não pode exceder os $MAX$ carateres.", "placeholders": { "max": { "content": "$1", @@ -3396,38 +3649,6 @@ "message": "Alternar colapso", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Importar os seus dados para o Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Proteger os seus dados LastPass e importar para o Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Guardar como ficheiro não encriptado", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Importar para o Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "A importar...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Dados importados com sucesso!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Erro de importação. Verifique a consola para obter detalhes.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Erro de rede encontrado durante a importação.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias de domínio" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Conta ativa" }, + "bitwardenAccount": { + "message": "Conta Bitwarden" + }, "availableAccounts": { "message": "Contas disponíveis" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Sugestões de preenchimento automático" }, + "itemSuggestions": { + "message": "Itens sugeridos" + }, "autofillSuggestionsTip": { "message": "Guarde uma credencial deste site para preenchimento automático" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copiar $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "Não há valores a copiar" }, @@ -4078,7 +4319,7 @@ "message": "Notificações" }, "appearance": { - "message": "Aparência" + "message": "Aspeto" }, "errorAssigningTargetCollection": { "message": "Erro ao atribuir a coleção de destino." @@ -4128,15 +4369,6 @@ "itemName": { "message": "Nome do item" }, - "cannotRemoveViewOnlyCollections": { - "message": "Não é possível remover coleções com permissões de Apenas visualização: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "A organização está desativada" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reordenar o URI do site. Utilize a tecla de seta para mover o item para cima ou para baixo." + }, "reorderFieldUp": { "message": "$LABEL$ movido para cima, posição $INDEX$ de $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "Não selecionou nada." }, - "movedItemsToOrg": { - "message": "Itens selecionados movidos para $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Itens movidos para $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Sends de texto" }, - "bitwardenNewLook": { - "message": "O Bitwarden tem um novo visual!" - }, - "bitwardenNewLookDesc": { - "message": "É mais fácil e mais intuitivo do que nunca preencher automaticamente e pesquisar a partir do separador Cofre. Dê uma vista de olhos!" - }, "accountActions": { "message": "Ações da conta" }, @@ -4615,10 +4835,10 @@ "message": "Ficheiro guardado no dispositivo. Gira-o a partir das transferências do seu dispositivo." }, "showCharacterCount": { - "message": "Mostrar contagem de caracteres" + "message": "Mostrar contagem de carateres" }, "hideCharacterCount": { - "message": "Ocultar contagem de caracteres" + "message": "Ocultar contagem de carateres" }, "itemsInTrash": { "message": "Itens no lixo" @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "Não tem permissão para editar este item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "O desbloqueio biométrico não está disponível porque o desbloqueio por PIN ou palavra-passe é necessário primeiro." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "O desbloqueio biométrico está atualmente indisponível." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "O desbloqueio biométrico não está disponível devido a ficheiros de sistema mal configurados." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "O desbloqueio biométrico não está disponível devido a ficheiros de sistema mal configurados." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "O desbloqueio biométrico não está disponível porque a app para computador Bitwarden está fechada." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "O desbloqueio biométrico não está disponível porque não está ativado para $EMAIL$ na app Bitwarden para computador.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "O desbloqueio biométrico está atualmente indisponível por um motivo desconhecido." + }, "authenticating": { "message": "A autenticar" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Muito ampla" + }, + "sshKeyWrongPassword": { + "message": "A palavra-passe que introduziu está incorreta." + }, + "importSshKey": { + "message": "Importar" + }, + "confirmSshKeyPassword": { + "message": "Confirmar palavra-passe" + }, + "enterSshKeyPasswordDesc": { + "message": "Introduza a palavra-passe para a chave SSH." + }, + "enterSshKeyPassword": { + "message": "Introduzir palavra-passe" + }, + "invalidSshKey": { + "message": "A chave SSH é inválida" + }, + "sshKeyTypeUnsupported": { + "message": "O tipo de chave SSH não é suportado" + }, + "importSshKeyFromClipboard": { + "message": "Importar chave da área de transferência" + }, + "sshKeyImported": { + "message": "Chave SSH importada com sucesso" + }, + "cannotRemoveViewOnlyCollections": { + "message": "Não é possível remover coleções com permissões de Apenas visualização: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Por favor, atualize a sua aplicação para computador" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "Para utilizar o desbloqueio biométrico, atualize a sua aplicação para computador ou desative o desbloqueio por impressão digital nas definições dessa mesma app." + }, + "changeAtRiskPassword": { + "message": "Alterar palavra-passe em risco" } } diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index 58c0a313f69..5ce73eb2e49 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Indiciu pentru parola principală (opțional)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Alăturați-vă organizației" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Copiază notițele" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { "message": "Fill", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Introduceți adresa de e-mail a contului și indiciul pentru parolă va fi trimis pe email" }, - "passwordHint": { - "message": "Indiciu parolă" - }, - "enterEmailToGetHint": { - "message": "Adresa de e-mail a contului pentru primirea indiciului parolei principale." - }, "getMasterPasswordHint": { "message": "Obținere indiciu parolă principală" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Editare dosar" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "Folder nou" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Generate passphrase" }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "regeneratePassword": { "message": "Regenerare parolă" }, @@ -454,22 +482,6 @@ "length": { "message": "Lungime" }, - "uppercase": { - "message": "Litere mari (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Litere mici (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Numere (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Caractere speciale (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Include", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Număr de cuvinte" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Browserul dvs. nu acceptă copierea în clipboard. Transcrieți datele manual." }, - "verifyIdentity": { - "message": "Verificare identitate" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "Seiful dvs. este blocat. Verificați-vă identitatea pentru a continua." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Reporniți înregistrarea" }, @@ -875,6 +904,9 @@ "no": { "message": "Nu" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "A survenit o eroare neașteptată." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "Afișați cardurile pe pagina Filă" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Listați elementele cardului pe pagina Filă pentru a facilita completarea automată." }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "Afișați identitățile pe pagina Filă" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "Golire clipboard", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Salvare" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Solicitați actualizarea autentificării existente" }, @@ -1084,10 +1169,6 @@ "message": "Luminos", "description": "Light color" }, - "solarizedDark": { - "message": "Întuneric solarizat", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Export from" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Achiziționare abonament Premium" }, - "premiumPurchaseAlert": { - "message": "Puteți achiziționa un abonament Premium pe website-ul bitwarden.com. Doriți să vizitați site-ul acum?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Memorare autentificare" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Retrimitere e-mail cu codul de verificare" }, "useAnotherTwoStepMethod": { "message": "Utilizare de metodă diferită de autentificare în două etape" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Introduceți YubiKey în portul USB al calculatorului apoi apăsați butonul acestuia." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Deschideți o filă nouă" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "Autentificare WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "Autentificare indisponibilă" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Opțiuni de autentificare în două etape" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Ați pierdut accesul la toți furnizorii de autentificare în două etape? Utilizați codul de recuperare pentru a dezactiva toți acești furnizori din contul dvs." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Mediu autogăzduit" }, - "selfHostedEnvironmentFooter": { - "message": "Specificați URL-ul de bază al implementări Bitwarden găzduită local." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Mediu personalizat" }, - "customEnvironmentFooter": { - "message": "Pentru utilizatorii avansați. Puteți specifica URL-ul de bază al fiecărui serviciu în mod independent." - }, "baseUrl": { "message": "URL server" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Tragere pentru sortare" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Text" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Username generator" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "Use this password" }, @@ -2063,12 +2163,24 @@ "message": "to create a strong unique password", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "Acțiune la expirarea seifului" }, "vaultTimeoutAction1": { "message": "Timeout action" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "Blocare", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "Domenii excluse" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Eroare" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generare nume de utilizator" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Setările au fost editate" - }, - "environmentEditedClick": { - "message": "Faceți clic aici" - }, - "environmentEditedReset": { - "message": "pentru a restabili setările preconfigurate" - }, "serverVersion": { "message": "Versiune server" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Autentificați-vă cu parola principală" }, - "loggingInAs": { - "message": "Autentificare ca" - }, - "notYou": { - "message": "Nu sunteți dvs.?" - }, "newAroundHere": { "message": "Sunteți nou pe aici?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Conectați-vă cu dispozitivul" }, - "loginWithDeviceEnabledInfo": { - "message": "Conectarea cu dispozitivul trebuie să fie configurată în setările aplicației Bitwarden. Aveți nevoie de o altă opțiune?" - }, "fingerprintPhraseHeader": { "message": "Fraza amprentă" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "View all log in options" }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" - }, "notificationSentDevice": { "message": "O notificare a fost trimisă pe dispozitivul dvs." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "You will be notified once the request is approved" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Conectare inițiată" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Parolă principală compromisă" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Cereți aprobarea administratorului" }, - "approveWithMasterPassword": { - "message": "Aprobați cu parola principală" - }, "ssoIdentifierRequired": { "message": "Identificatorul SSO al organizației este necesar." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Cererea dvs. a fost trimisă administratorului." }, - "youWillBeNotifiedOnceApproved": { - "message": "Veți primi o notificare după aprobare." - }, "troubleLoggingIn": { "message": "Aveți probleme la logare?" }, @@ -3396,38 +3649,6 @@ "message": "Comutare restrângere", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Active account" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Available accounts" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "No values to copy" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organization is deactivated" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "You have not selected anything." }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra wide" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index acdee563ceb..11d8de4acbe 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Подсказка к мастер-паролю (необяз.)" }, + "passwordStrengthScore": { + "message": "Оценка надежности пароля $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Присоединиться к организации" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Скопировать заметки" }, + "copy": { + "message": "Скопировать", + "description": "Copy to clipboard" + }, "fill": { "message": "Заполнить", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Введите email вашего аккаунта, и вам будет отправлена подсказка для пароля" }, - "passwordHint": { - "message": "Подсказка к паролю" - }, - "enterEmailToGetHint": { - "message": "Введите email аккаунта для получения подсказки к мастер-паролю." - }, "getMasterPasswordHint": { "message": "Получить подсказку к мастер-паролю" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Изменить папку" }, + "editFolderWithName": { + "message": "Редактировать папку: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "Новый папка" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Создать парольную фразу" }, + "passwordGenerated": { + "message": "Пароль создан" + }, + "passphraseGenerated": { + "message": "Парольная фраза создана" + }, + "usernameGenerated": { + "message": "Имя пользователя создано" + }, + "emailGenerated": { + "message": "Email создан" + }, "regeneratePassword": { "message": "Создать новый пароль" }, @@ -454,22 +482,6 @@ "length": { "message": "Длина" }, - "uppercase": { - "message": "Прописные буквы (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Строчные буквы (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Цифры (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Специальные символы (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Включить", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Включить специальные символы", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Количество слов" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Ваш браузер не поддерживает копирование данных в буфер обмена. Скопируйте вручную." }, - "verifyIdentity": { - "message": "Подтвердить личность" + "verifyYourIdentity": { + "message": "Подтвердите вашу личность" + }, + "weDontRecognizeThisDevice": { + "message": "Мы не распознали это устройство. Введите код, отправленный на ваш email, чтобы подтвердить вашу личность." + }, + "continueLoggingIn": { + "message": "Продолжить вход" }, "yourVaultIsLocked": { "message": "Ваше хранилище заблокировано. Подтвердите свою личность, чтобы продолжить" @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Войти в Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Введите код, отправленный на ваш email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Введите код из приложения-аутентификатора" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Нажмите на YubiKey для аутентификации" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Для вашего аккаунта требуется двухэтапная аутентификация Duo. Выполните следующие действия, чтобы завершить авторизацию." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Следуйте указаниям ниже, чтобы завершить авторизацию." + }, "restartRegistration": { "message": "Перезапустить регистрацию" }, @@ -875,6 +904,9 @@ "no": { "message": "Нет" }, + "location": { + "message": "Местоположение" + }, "unexpectedError": { "message": "Произошла непредвиденная ошибка." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Запрос на добавление элемента, если он отсутствует в вашем хранилище. Применяется ко всем авторизованным аккаунтам." }, - "showCardsInVaultView": { - "message": "Показывать карты как предложение автозаполнения при просмотре Хранилище" + "showCardsInVaultViewV2": { + "message": "Всегда показывать карты как предложения автозаполнения при просмотре хранилища" }, "showCardsCurrentTab": { "message": "Показывать карты на вкладке" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Карты будут отображены на вкладке для удобного автозаполнения." }, - "showIdentitiesInVaultView": { - "message": "Показывать личности как предложение автозаполнения при просмотре Хранилище" + "showIdentitiesInVaultViewV2": { + "message": "Всегда показывать личности как предложения автозаполнения при просмотре хранилища" }, "showIdentitiesCurrentTab": { "message": "Показывать Личности на вкладке" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Кликните элементы для автозаполнения в режиме просмотра хранилища" }, + "clickToAutofill": { + "message": "Выберите элементы в предложении автозаполнения для вставки" + }, "clearClipboard": { "message": "Очистить буфер обмена", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Сохранить" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ сохранен в Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ обновлен в Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Сохранить как новый логин", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Обновить логин", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Сохранить логин?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Обновить существующий логин?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Логин сохранен", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Логин обновлен", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Ошибка при сохранении", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "О нет! Мы не смогли сохранить это. Попробуйте ввести данные вручную.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Спрашивать при обновлении существующего логина" }, @@ -1084,10 +1169,6 @@ "message": "Светлая", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized dark", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Экспорт из" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Купить Премиум" }, - "premiumPurchaseAlert": { - "message": "Вы можете купить Премиум на bitwarden.com. Перейти на сайт сейчас?" - }, "premiumPurchaseAlertV2": { "message": "Премиум можно приобрести в настройках аккаунта в веб-версии Bitwarden." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Запомнить меня" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Не спрашивать на этом устройстве в течение 30 дней" + }, "sendVerificationCodeEmailAgain": { "message": "Отправить код подтверждения еще раз" }, "useAnotherTwoStepMethod": { "message": "Использовать другой метод двухэтапной аутентификации" }, + "selectAnotherMethod": { + "message": "Выбрать другой способ", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Использовать код восстановления" + }, "insertYubiKey": { "message": "Вставьте свой YubiKey в USB-порт компьютера и нажмите его кнопку." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Открыть новую вкладку" }, + "openInNewTab": { + "message": "Открыть в новой вкладке" + }, "webAuthnAuthenticate": { "message": "Аутентификация WebAutn" }, + "readSecurityKey": { + "message": "Считать ключ безопасности" + }, + "awaitingSecurityKeyInteraction": { + "message": "Ожидание взаимодействия с ключом безопасности..." + }, "loginUnavailable": { "message": "Вход недоступен" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Настройки двухэтапной аутентификации" }, + "selectTwoStepLoginMethod": { + "message": "Выбрать другой метод двухэтапной аутентификации" + }, "recoveryCodeDesc": { "message": "Потеряли доступ ко всем вариантам двухэтапной аутентификации? Используйте код восстановления, чтобы отключить двухэтапную аутентификацию для вашей учетной записи." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Окружение пользовательского хостинга" }, - "selfHostedEnvironmentFooter": { - "message": "Укажите URL Bitwarden на вашем сервере." - }, "selfHostedBaseUrlHint": { "message": "Укажите базовый URL вашего локального хостинга Bitwarden. Пример: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Пользовательское окружение" }, - "customEnvironmentFooter": { - "message": "Для опытных пользователей. Можно указать URL отдельно для каждой службы." - }, "baseUrl": { "message": "URL сервера" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Перетащите для сортировки" }, + "dragToReorder": { + "message": "Перетащите для изменения порядка" + }, "cfTypeText": { "message": "Текстовое" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Генератор имени пользователя" }, + "useThisEmail": { + "message": "Использовать этот email" + }, "useThisPassword": { "message": "Использовать этот пароль" }, @@ -2063,12 +2163,24 @@ "message": "для создания надежного уникального пароля", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Настройка хранилища" + }, "vaultTimeoutAction": { "message": "Действие по тайм-ауту хранилища" }, "vaultTimeoutAction1": { "message": "Тайм-аут действия" }, + "newCustomizationOptionsCalloutTitle": { + "message": "Новые возможности настроек" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Настройте работу с хранилищем с помощью действий быстрого копирования, компактного режима и многого другого!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "Посмотреть все настройки внешнего вида" + }, "lock": { "message": "Блокировка", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Домены", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Заблокированные домены" + }, + "learnMoreAboutBlockedDomains": { + "message": "Узнайте больше о заблокированных доменах" + }, "excludedDomains": { "message": "Исключенные домены" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden не будет предлагать сохранение логинов для этих доменов для всех авторизованных аккаунтов. Для вступления изменений в силу необходимо обновить страницу." }, + "blockedDomainsDesc": { + "message": "Автозаполнение и другие связанные с ним функции не будут предлагаться для этих сайтов. Чтобы изменения вступили в силу, необходимо обновить страницу." + }, + "autofillBlockedNoticeV2": { + "message": "Автозаполнение для этого сайта заблокировано." + }, + "autofillBlockedNoticeGuidance": { + "message": "Измените это в настройках" + }, + "change": { + "message": "Изменить" + }, + "changeButtonTitle": { + "message": "Изменить пароль - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "Пароли, подверженные риску" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ запрашивает смену одного пароля, так как он подвержен риску.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ запрашивает смену $COUNT$ паролей, так как они подвержены риску.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Ваша организация запрашивает смену $COUNT$ паролей, так как они подвержены риску.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Проверить и изменить один пароль, подверженный риску" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Проверить и изменить $COUNT$ паролей, подверженных риску", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Меняйте пароли, подверженные риску, быстрее" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Обновите настройки, чтобы можно было быстро автоматически заполнять пароли и генерировать новые" + }, + "reviewAtRiskLogins": { + "message": "Обзор логинов, подверженных риску" + }, + "reviewAtRiskPasswords": { + "message": "Обзор паролей, подверженных риску" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Пароли вашей организации подвержены риску, потому что они слабые, повторно используются и/или раскрыты.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Иллюстрация списка логинов, которые подвержены риску" + }, + "generatePasswordSlideDesc": { + "message": "Быстро сгенерируйте надежный уникальный пароль с помощью меню автозаполнения Bitwarden на сайте, подверженном риску.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Иллюстрация меню автозаполнения Bitwarden, отображающего сгенерированный пароль" + }, + "updateInBitwarden": { + "message": "Обновить в Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "После этого Bitwarden предложит вам обновить пароль в менеджере паролей.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Иллюстрация уведомления Bitwarden, предлагающего пользователю обновить логин" + }, + "turnOnAutofill": { + "message": "Включить автозаполнение" + }, + "turnedOnAutofill": { + "message": "Автозаполнение включено" + }, + "dismiss": { + "message": "Отклонить" + }, "websiteItemLabel": { "message": "Сайт $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Изменения в заблокированном домене сохранены" + }, "excludedDomainsSavedSuccess": { "message": "Изменения в исключенном домене сохранены" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Ошибка" }, + "decryptionError": { + "message": "Ошибка расшифровки" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden не удалось расшифровать элемент(ы) хранилища, перечисленные ниже." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Обратитесь в службу поддержки,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "чтобы избежать дополнительной потери данных.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Создать имя пользователя" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ отклонил(а) ваш запрос. Обратитесь за помощью к своему провайдеру.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ отклонил(а) ваш запрос: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Не удалось получить скрытый идентификатор email аккаунта $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Настройки были изменены" - }, - "environmentEditedClick": { - "message": "Нажмите здесь" - }, - "environmentEditedReset": { - "message": "для сброса к предварительно настроенным параметрам" - }, "serverVersion": { "message": "Версия сервера" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Войти с мастер-паролем" }, - "loggingInAs": { - "message": "Войти как" - }, - "notYou": { - "message": "Не вы?" - }, "newAroundHere": { "message": "Вы здесь впервые?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Войти с помощью устройства" }, - "loginWithDeviceEnabledInfo": { - "message": "Вход с устройства должен быть настроен в настройках мобильного приложения Bitwarden. Нужен другой вариант?" - }, "fingerprintPhraseHeader": { "message": "Фраза отпечатка" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "Посмотреть все варианты авторизации" }, - "viewAllLoginOptionsV1": { - "message": "Посмотреть все варианты авторизации" - }, "notificationSentDevice": { "message": "На ваше устройство отправлено уведомление." }, + "notificationSentDevicePart1": { + "message": "Разблокируйте Bitwarden на своем устройстве или" + }, + "notificationSentDeviceAnchor": { + "message": "веб-приложении" + }, + "notificationSentDevicePart2": { + "message": "Перед одобрением убедитесь, что фраза отпечатка совпадает с приведенной ниже." + }, "aNotificationWasSentToYourDevice": { "message": "На ваше устройство было отправлено уведомление" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Убедитесь, что ваш аккаунт разблокирован и фраза отпечатка совпадает с фразой на другом устройстве" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "Вы получите уведомление, когда запрос будет одобрен" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Вход инициирован" }, + "logInRequestSent": { + "message": "Запрос отправлен" + }, "exposedMasterPassword": { "message": "Мастер-пароль скомпрометирован" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Запросить одобрение администратора" }, - "approveWithMasterPassword": { - "message": "Одобрить с мастер-паролем" - }, "ssoIdentifierRequired": { "message": "Требуется идентификатор SSO организации." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Ваш запрос был отправлен администратору." }, - "youWillBeNotifiedOnceApproved": { - "message": "Вас уведомят об одобрении." - }, "troubleLoggingIn": { "message": "Не удалось войти?" }, @@ -3396,38 +3649,6 @@ "message": "Свернуть/развернуть", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Импортировать данные в Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Защитить данные LastPass и импортировать их в Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Сохранить как незашифрованный файл", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Импортировать в Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Импорт...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Данные успешно импортированы!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Ошибка импорта. Проверьте консоль для получения подробной информации.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Во время импорта возникла сетевая ошибка.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Псевдоним домена" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Активный аккаунт" }, + "bitwardenAccount": { + "message": "Аккаунт Bitwarden" + }, "availableAccounts": { "message": "Доступные аккаунты" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Предложения по автозаполнению" }, + "itemSuggestions": { + "message": "Предлагаемые элементы" + }, "autofillSuggestionsTip": { "message": "Сохранить логин для этого сайта для автозаполнения" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Скопировать $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "Нет значений для копирования" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Название элемента" }, - "cannotRemoveViewOnlyCollections": { - "message": "Вы не можете удалить коллекции с правами только на просмотр: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Организация деактивирована" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Изменить порядок URI. Используйте клавиши курсора для перемещения элемента вверх или вниз." + }, "reorderFieldUp": { "message": "$LABEL$ перемещено вверх, позиция $INDEX$ $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "Вы ничего не выбрали." }, - "movedItemsToOrg": { - "message": "Выбранные элементы перемещены в $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Элементы перемещены в $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Текстовая Send" }, - "bitwardenNewLook": { - "message": "У Bitwarden новый облик!" - }, - "bitwardenNewLookDesc": { - "message": "Теперь автозаполнение и поиск на вкладке Хранилище стали проще и интуитивно понятнее, чем когда-либо. Осмотритесь!" - }, "accountActions": { "message": "Действия аккаунта" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "У вас нет разрешения на редактирование этого элемента" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Биометрическая разблокировка недоступна, поскольку сначала требуется разблокировка с помощью PIN-кода или пароля." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Биометрическая разблокировка в настоящее время недоступна." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Биометрическая разблокировка недоступна из-за неправильно настроенных системных файлов." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Биометрическая разблокировка недоступна из-за неправильно настроенных системных файлов." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Биометрическая разблокировка недоступна, поскольку Bitwarden для компьютера закрыт." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Биометрическая разблокировка недоступна, потому что она не включена для $EMAIL$ в приложении Bitwarden для компьютера.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Биометрическая разблокировка в настоящее время недоступна по неизвестной причине." + }, "authenticating": { "message": "Аутентификация" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Очень широкое" + }, + "sshKeyWrongPassword": { + "message": "Введенный пароль неверен." + }, + "importSshKey": { + "message": "Импорт" + }, + "confirmSshKeyPassword": { + "message": "Подтвердите пароль" + }, + "enterSshKeyPasswordDesc": { + "message": "Введите пароль для ключа SSH." + }, + "enterSshKeyPassword": { + "message": "Введите пароль" + }, + "invalidSshKey": { + "message": "Ключ SSH недействителен" + }, + "sshKeyTypeUnsupported": { + "message": "Тип ключа SSH не поддерживается" + }, + "importSshKeyFromClipboard": { + "message": "Импорт ключа из буфера обмена" + }, + "sshKeyImported": { + "message": "Ключ SSH успешно импортирован" + }, + "cannotRemoveViewOnlyCollections": { + "message": "Вы не можете удалить коллекции с правами только на просмотр: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Пожалуйста, обновите приложение для компьютера" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "Чтобы использовать биометрическую разблокировку, обновите приложение для компьютера или отключите разблокировку по отпечатку пальца в настройках компьютера." + }, + "changeAtRiskPassword": { + "message": "Изменить пароль, подверженный риску" } } diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index b25c2fd30d5..721d16a2eee 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "ප්රධාන මුරපදය ඉඟියක් (විකල්ප)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Copy notes" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { "message": "Fill", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "මුරපදය ඉඟිය" - }, - "enterEmailToGetHint": { - "message": "ඔබගේ ප්රධාන මුරපදය ඉඟියක් ලබා ගැනීමට ඔබගේ ගිණුම ඊ-තැපැල් ලිපිනය ඇතුලත් කරන්න." - }, "getMasterPasswordHint": { "message": "ප්රධාන මුරපදය ඉඟියක් ලබා ගන්න" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "බහාලුම සංස්කරණය" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "New folder" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Generate passphrase" }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "regeneratePassword": { "message": "මුරපදය ප්රතිජනනය" }, @@ -454,22 +482,6 @@ "length": { "message": "දිග" }, - "uppercase": { - "message": "Uppercase (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Lowercase (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Numbers (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Special characters (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Include", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "වචන ගණන" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "ඔබේ වෙබ් බ්රව්සරය පහසු පසුරු පුවරුවක් පිටපත් කිරීමට සහාය නොදක්වයි. ඒ වෙනුවට එය අතින් පිටපත් කරන්න." }, - "verifyIdentity": { - "message": "අනන්යතාවය සත්යාපනය කරන්න" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "ඔබේ සුරක්ෂිතාගාරය අගුළු දමා ඇත. දිගටම කරගෙන යාමට ඔබේ අනන්යතාවය සත්යාපනය කරන්න." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Restart registration" }, @@ -875,6 +904,9 @@ "no": { "message": "නැත" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "අනපේක්ෂිත දෝෂයක් සිදුවී ඇත." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "Show cards on Tab page" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "List card items on the Tab page for easy autofill." }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "Show identities on Tab page" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "පසුරු පුවරුවට පැහැදිලි", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "සුරකින්න" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Ask to update existing login" }, @@ -1084,10 +1169,6 @@ "message": "ආලෝකය", "description": "Light color" }, - "solarizedDark": { - "message": "අඳුරු අඳුරු", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Export from" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "වාරික මිලදී" }, - "premiumPurchaseAlert": { - "message": "ඔබට bitwarden.com වෙබ් සුරක්ෂිතාගාරයේ වාරික සාමාජිකත්වය මිලදී ගත හැකිය. ඔබට දැන් වෙබ් අඩවියට පිවිසීමට අවශ්යද?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "මාව මතක තබා ගන්න" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "සත්යාපන කේතය නැවත විද්යුත් තැපෑල යවන්න" }, "useAnotherTwoStepMethod": { "message": "තවත් පියවර දෙකක පිවිසුම් ක්රමයක් භාවිතා කරන්න" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "ඔබේ පරිගණකයේ USB පෝට් එකට ඔබගේ YuBiKey ඇතුල් කරන්න, ඉන්පසු එහි බොත්තම ස්පර්ශ කරන්න." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "නව ටැබය විවෘත කරන්න" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "සත්‍යවත් වෙබ් සත්‍යවත් කරන්න" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "ලොගින් වන්න ලබාගත නොහැක" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "ද්වි-පියවර ලොගින් වන්න විකල්ප" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "ඔබගේ ද්වි-සාධක සපයන්නන් සියලු ප්රවේශ අහිමි? ඔබගේ ගිණුමෙන් සියලුම ද්වි-සාධක සපයන්නන් අක්රීය කිරීමට ඔබගේ ප්රතිසාධන කේතය භාවිතා කරන්න." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "ස්වයං සත්කාරක පරිසරය" }, - "selfHostedEnvironmentFooter": { - "message": "බිට්වර්ඩන් ස්ථාපනය සත්කාරකත්වය දරනු ලබන ඔබගේ පරිශ්රයේ මූලික URL එක සඳහන් කරන්න." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "අභිරුචි පරිසරය" }, - "customEnvironmentFooter": { - "message": "උසස් පරිශීලකයින් සඳහා. එක් එක් සේවාවෙහි මූලික URL එක ස්වාධීනව සඳහන් කළ හැකිය." - }, "baseUrl": { "message": "සේවාදායකය URL" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "වර්ග කිරීමට ඇද දමන්න" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "පෙළ" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Username generator" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "Use this password" }, @@ -2063,12 +2163,24 @@ "message": "to create a strong unique password", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "සුරක්ෂිතාගාරය කාලය ක්රියාකාරී" }, "vaultTimeoutAction1": { "message": "Timeout action" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "අගුල", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "බැහැර වසම්" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Settings have been edited" - }, - "environmentEditedClick": { - "message": "Click here" - }, - "environmentEditedReset": { - "message": "to reset to pre-configured settings" - }, "serverVersion": { "message": "Server version" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Log in with device" }, - "loginWithDeviceEnabledInfo": { - "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" - }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "View all log in options" }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" - }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "You will be notified once the request is approved" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3396,38 +3649,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Active account" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Available accounts" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "No values to copy" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organization is deactivated" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "You have not selected anything." }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra wide" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index 5d11227f003..897db9f3176 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Nápoveď k hlavnému heslu (voliteľné)" }, + "passwordStrengthScore": { + "message": "Sila hesla $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Pripojte sa k organizácii" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Kopírovať poznámky" }, + "copy": { + "message": "Kopírovať", + "description": "Copy to clipboard" + }, "fill": { "message": "Vyplniť", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Zadajte e-mailovú adresu účtu a zašleme vám nápoveď k heslu" }, - "passwordHint": { - "message": "Nápoveď k heslu" - }, - "enterEmailToGetHint": { - "message": "Zadajte emailovú adresu na zaslanie nápovede pre vaše hlavné heslo." - }, "getMasterPasswordHint": { "message": "Získať nápoveď k hlavnému heslu" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Upraviť priečinok" }, + "editFolderWithName": { + "message": "Upraviť priečinok: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "Nový priečinok" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Generovať prístupovú frázu" }, + "passwordGenerated": { + "message": "Heslo vygenerované" + }, + "passphraseGenerated": { + "message": "Prístupová fráza vygenerovaná" + }, + "usernameGenerated": { + "message": "Používateľské meno vygenerované" + }, + "emailGenerated": { + "message": "E-mail vygenoravný" + }, "regeneratePassword": { "message": "Vygenerovať nové heslo" }, @@ -454,22 +482,6 @@ "length": { "message": "Dĺžka" }, - "uppercase": { - "message": "Veľké písmená (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Malé písmená (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Čísla (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Špeciálne znaky (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Zahrnúť", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Zahrnúť špeciálne znaky", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Počet slov" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Váš webový prehliadač nepodporuje automatické kopírovanie do schránky. Kopírujte manuálne." }, - "verifyIdentity": { - "message": "Overiť identitu" + "verifyYourIdentity": { + "message": "Overte svoju totožnosť" + }, + "weDontRecognizeThisDevice": { + "message": "Toto zariadenie nepoznáme. Na overenie vašej totožnosti zadajte kód, ktorý bol zaslaný na váš e-mail." + }, + "continueLoggingIn": { + "message": "Pokračovať v prihlasovaní" }, "yourVaultIsLocked": { "message": "Váš trezor je uzamknutý. Ak chcete pokračovať, overte svoju identitu." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Prihlásenie do Bitwardenu" }, + "enterTheCodeSentToYourEmail": { + "message": "Zadajte kód zaslaný na váš e-mail" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Zadajte kód z overovacej aplikácie" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Stlačte YubiKey na overenie" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Pre váš účet sa vyžaduje dvojstupňové prihlásenie Duo. Na dokončenie prihlásenie nasledujte pokyny." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Na dokončenie prihlásenia postupujte podľa pokynov." + }, "restartRegistration": { "message": "Zopakovať registráciu" }, @@ -875,6 +904,9 @@ "no": { "message": "Nie" }, + "location": { + "message": "Poloha" + }, "unexpectedError": { "message": "Vyskytla sa neočakávaná chyba." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Požiada o pridanie položky, ak sa v trezore nenachádza. Platí pre všetky prihlásené účty." }, - "showCardsInVaultView": { - "message": "Zobraziť karty ako návrhy automatického vypĺňania v zobrazení trezora" + "showCardsInVaultViewV2": { + "message": "Vždy zobraziť karty ako návrhy automatického vypĺňania v zobrazení trezora" }, "showCardsCurrentTab": { "message": "Zobraziť karty na stránke \"Aktuálna karta\"" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Zoznam položiek karty na stránke \"Aktuálna karta\" na jednoduché automatické vyplnenie." }, - "showIdentitiesInVaultView": { - "message": "Zobraziť identity ako návrhy automatického vypĺňania v zobrazení trezora" + "showIdentitiesInVaultViewV2": { + "message": "Vždy zobraziť identity ako návrhy automatického vypĺňania v zobrazení trezora" }, "showIdentitiesCurrentTab": { "message": "Zobraziť identity na stránke \"Aktuálna karta\"" @@ -1005,7 +1037,10 @@ "message": "Zoznam položiek identity na stránke \"Aktuálna karta\" na jednoduché automatické vypĺňanie." }, "clickToAutofillOnVault": { - "message": "Kliknutím na položku v trezore automaticky vyplniť" + "message": "Kliknutím na položky v trezore automaticky vyplniť" + }, + "clickToAutofill": { + "message": "Kliknutím na položky v ponuke automatického vypĺňania vyplniť" }, "clearClipboard": { "message": "Vymazať schránku", @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Uložiť" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ uložené do Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ aktualizované v Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Uložiť ako nové prihlasovacie údaje", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Aktualizovať prihlasovacie údaje", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Uložiť prihlasovacie údaje?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Aktualizovať existujúce prihlasovacie údaje?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Prihlasovacie údaje uložené", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Prihlasovacie údaje aktualizované", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Chyba pri ukladaní", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Ale nie! Nepodarilo sa nám to uložiť. Skúste zadať údaje manuálne.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Požiadať o aktualizáciu existujúceho prihlasovania" }, @@ -1084,10 +1169,6 @@ "message": "Svetlý", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized –⁠ tmavý", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Exportovať z" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Zakúpiť Prémiový účet" }, - "premiumPurchaseAlert": { - "message": "Svoje prémiové členstvo si môžete zakúpiť vo webovom trezore bitwarden.com. Chcete navštíviť túto stránku teraz?" - }, "premiumPurchaseAlertV2": { "message": "Prémiové členstvo si môžete zakúpiť v nastaveniach svojho účtu vo webovej aplikácii Bitwarden." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Zapamätať si ma" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Nepýtať sa znova na tomto zariadení 30 dní" + }, "sendVerificationCodeEmailAgain": { "message": "Znovu zaslať overovací kód emailom" }, "useAnotherTwoStepMethod": { "message": "Použiť inú dvojstupňovú metódu prihlásenia" }, + "selectAnotherMethod": { + "message": "Vyberte iný spôsob", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Použiť obnovovací kód" + }, "insertYubiKey": { "message": "Vložte váš YubiKey do USB portu počítača a stlačte jeho tlačidlo." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Otvoriť v novej karte" }, + "openInNewTab": { + "message": "Otvoriť v novej karte" + }, "webAuthnAuthenticate": { "message": "Overiť cez WebAuthn" }, + "readSecurityKey": { + "message": "Prečítať bezpečnostný kľúč" + }, + "awaitingSecurityKeyInteraction": { + "message": "Čaká sa na interakciu s bezpečnostným kľúčom..." + }, "loginUnavailable": { "message": "Prihlásenie nie je dispozícii" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Možnosti dvojstupňového prihlásenia" }, + "selectTwoStepLoginMethod": { + "message": "Vyberte metódu dvojstupňového prihlásenia" + }, "recoveryCodeDesc": { "message": "Stratili ste prístup ku všetkým vašim dvojstupňovým poskytovateľom? Použite váš záchranný kód pre vypnutie všetkých poskytovateľov vo vašom účte." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Prostredie s vlastným hostingom" }, - "selfHostedEnvironmentFooter": { - "message": "Zadajte základnú URL adresu lokálne hosťovanej inštalácie Bitwarden." - }, "selfHostedBaseUrlHint": { "message": "Zadajte základnú URL adresu lokálne hosťovanej inštalácie Bitwarden. Napríklad: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Vlastné prostredie" }, - "customEnvironmentFooter": { - "message": "Pre pokročilých používateľov. Základnú adresu URL každej služby môžete určiť samostatne." - }, "baseUrl": { "message": "URL servera" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Zoradiť presúvaním" }, + "dragToReorder": { + "message": "Ťahaním preskupiť" + }, "cfTypeText": { "message": "Text" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Generátor používateľského mena" }, + "useThisEmail": { + "message": "Použiť tento e-mail" + }, "useThisPassword": { "message": "Použiť toto heslo" }, @@ -2063,12 +2163,24 @@ "message": "na vytvorenie silného, unikátneho hesla.", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Prispôsobenie trezora" + }, "vaultTimeoutAction": { "message": "Akcia pri vypršaní časového limitu pre trezor" }, "vaultTimeoutAction1": { "message": "Akcia pri vypršaní časového limitu" }, + "newCustomizationOptionsCalloutTitle": { + "message": "Nové možnosti prispôsobenia" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Prispôsobte si trezor pomocou akcií rýchleho kopírovania, kompaktného režimu a ďalších možností!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "Zobraziť všetky nastavenia vzhľadu" + }, "lock": { "message": "Uzamknúť", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domény", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blokované domény" + }, + "learnMoreAboutBlockedDomains": { + "message": "Viac informácií o blokovaných doménach" + }, "excludedDomains": { "message": "Vylúčené domény" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden nebude požadovať ukladanie prihlasovacích údajov pre tieto domény pre všetky prihlásené účty. Aby sa zmeny prejavili, musíte stránku obnoviť." }, + "blockedDomainsDesc": { + "message": "Automatické vypĺňanie a ďalšie súvisiace funkcie sa na týchto webových stránkach nebudú ponúkať. Aby sa zmeny prejavili, musíte stránku obnoviť." + }, + "autofillBlockedNoticeV2": { + "message": "Automatické vypĺňanie je pre túto webovú stránku zablokované." + }, + "autofillBlockedNoticeGuidance": { + "message": "Zmeňte to v nastaveniach" + }, + "change": { + "message": "Zmeniť" + }, + "changeButtonTitle": { + "message": "Zmeniť heslo - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "Rizikové heslá" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ vás žiada o zmenu hesla, pretože je ohrozené.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ vás žiada o zmenu $COUNT$ hesiel, pretože sú ohrozené.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Vaše organizácie vás žiadajú o zmenu $COUNT$ hesiel, pretože sú ohrozené.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Skontrolujte a zmeňte jedno ohrozené heslo" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Skontrolujte a zmeňte $COUNT$ ohrozené heslá", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Zmeňte rizikové heslá rýchlejšie" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Aktualizujte nastavenia, aby ste mohli rýchlo vypĺňať svoje heslá a vygenerovať nové" + }, + "reviewAtRiskLogins": { + "message": "Prehľad ohrozených prihlasovacích mien" + }, + "reviewAtRiskPasswords": { + "message": "Prehľad ohrozených hesiel" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Heslá vašej organizácie sú v ohrození, pretože sú slabé, opakovane používané a/alebo uniknuté.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Príklady zoznamu prihlásení, ktoré sú ohrozené" + }, + "generatePasswordSlideDesc": { + "message": "Rýchlo generujte silné, jedinečné heslo pomocu ponuky automatického vypĺňania Bitwardenu na ohrozených stránkach.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Príklad ponuky automatického vypĺňania Bitwardenu zobrazujúca vygenerované heslo" + }, + "updateInBitwarden": { + "message": "Aktualizovať v Bitwardene" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden vás vyzve na aktualizáciu hesla v správcovi hesiel.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Príklad upozornenia Bitwardenu na aktualizovanie prihlasovacích údajov" + }, + "turnOnAutofill": { + "message": "Zapnúť automatické vypĺňanie" + }, + "turnedOnAutofill": { + "message": "Zapnuté automatické vypĺňanie" + }, + "dismiss": { + "message": "Zrušiť" + }, "websiteItemLabel": { "message": "Webstránka $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Zmeny v blokovaných doménach boli uložené" + }, "excludedDomainsSavedSuccess": { "message": "Uložené zmeny vylúčenej domény" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Chyba" }, + "decryptionError": { + "message": "Chyba dešifrovania" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nedokázal dešifrovať nižšie uvedené položky trezoru." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktujte zákaznícku podporu,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "aby ste predišli ďalším stratám údajov.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Vygenerovať používateľské meno" }, @@ -2855,7 +3102,7 @@ "message": "Služba" }, "forwardedEmail": { - "message": "Alias preposlaného e-mailu" + "message": "Alias presmerovaného e-mailu" }, "forwardedEmailDesc": { "message": "Vytvoriť e-mailový alias pomocou externej služby preposielania." @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "Služba $SERVICENAME$ odmietla vašu žiadosť. Obráťte sa na svojho poskytovateľa služieb a požiadajte o pomoc.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "Služba $SERVICENAME$ odmietla vašu žiadosť: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Nepodarilo sa získať ID maskovaného e-mailového účtu $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Nastavenia boli upravené" - }, - "environmentEditedClick": { - "message": "Kliknite sem" - }, - "environmentEditedReset": { - "message": "na obnovenie predvolených nastavení" - }, "serverVersion": { "message": "Verzia servera" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Prihlásenie pomocou hlavného hesla" }, - "loggingInAs": { - "message": "Prihlasujete sa ako" - }, - "notYou": { - "message": "Nie ste to vy?" - }, "newAroundHere": { "message": "Ste tu nový?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Prihlásiť pomocou zariadenia" }, - "loginWithDeviceEnabledInfo": { - "message": "Prihlásenie pomocou zariadenia musí byť nastavené v nastaveniach aplikácie Bitwarden. Potrebujete inú možnosť?" - }, "fingerprintPhraseHeader": { "message": "Fráza odtlačku prsta" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "Zobraziť všetky možnosti prihlásenia" }, - "viewAllLoginOptionsV1": { - "message": "Zobraziť všetky možnosti prihlásenia" - }, "notificationSentDevice": { "message": "Do vášho zariadenia bolo odoslané upozornenie." }, + "notificationSentDevicePart1": { + "message": "Odomknúť Bitwarden vo svojom zariadení alebo vo" + }, + "notificationSentDeviceAnchor": { + "message": "webovej aplikácii" + }, + "notificationSentDevicePart2": { + "message": "Pred schválením sa uistite, že sa odtlačok prístupovej frázy zhoduje s tou uvedenou nižšie." + }, "aNotificationWasSentToYourDevice": { "message": "Do vášho zariadenia bolo odoslané upozornenie" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Uistite sa, že je váš účet odomknutý a fráza odtlačku prsta sa zhoduje s frázou na druhom zariadení" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "Po schválení žiadosti budete informovaní" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Iniciované prihlásenie" }, + "logInRequestSent": { + "message": "Požiadavka bola odoslaná" + }, "exposedMasterPassword": { "message": "Odhalené hlavné heslo" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Žiadosť o schválenie správcom" }, - "approveWithMasterPassword": { - "message": "Schváliť pomocou hlavného hesla" - }, "ssoIdentifierRequired": { "message": "Pole identifikátora SSO je povinné." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Vaša žiadosť bola odoslaná správcovi." }, - "youWillBeNotifiedOnceApproved": { - "message": "Po schválení budete informovaný." - }, "troubleLoggingIn": { "message": "Máte problémy s prihlásením?" }, @@ -3396,38 +3649,6 @@ "message": "Prepnúť zbalenie", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Importovať údaje do Bitwardenu?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Ochrániť údaje z LastPassu a importovať ich do Bitwardenu?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Uložiť ako nezašifrovaný súbor", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Importovať do Bitwardenu", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importovanie...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Import údajov prebehol úspešne!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Chyba pri importovaní. Podrobnosti nájdete v konzole.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Počas importu sa vyskytla chyba siete.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias doména" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Aktívny účet" }, + "bitwardenAccount": { + "message": "Účet Bitwarden" + }, "availableAccounts": { "message": "Dostupné účty" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Návrhy automatického vypĺňania" }, + "itemSuggestions": { + "message": "Navrhované položky" + }, "autofillSuggestionsTip": { "message": "Uložte položku prihlásenia pre tento web na automatické vyplnenie" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Kopírovať $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "Nie je čo kopírovať" }, @@ -4107,7 +4348,7 @@ } }, "new": { - "message": "Nová" + "message": "Nové" }, "removeItem": { "message": "Odstrániť $NAME$", @@ -4128,15 +4369,6 @@ "itemName": { "message": "Názov položky" }, - "cannotRemoveViewOnlyCollections": { - "message": "Zbierky, ktoré môžete len zobraziť nemôžete odstrániť: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organizácia je vypnutá" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Zmeniť poradie URI webovej stránky. Na presun položky hore alebo dole použite klávesy so šípkami." + }, "reorderFieldUp": { "message": "$LABEL$ presunuté vyššie, pozícia $INDEX$/$LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "Nič ste nevybrali." }, - "movedItemsToOrg": { - "message": "Vybraté položky boli presunuté do $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Položky presunuté do $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Textové Sendy" }, - "bitwardenNewLook": { - "message": "Bitwarden má nový vzhľad!" - }, - "bitwardenNewLookDesc": { - "message": "Automatické vypĺňanie a vyhľadávanie na karte Trezor je jednoduchšie a intuitívnejšie ako kedykoľvek predtým. Poobzerajte sa!" - }, "accountActions": { "message": "Operácie s účtom" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "Na úpravu tejto položky nemáte oprávnenie" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Odomykanie biometrickými údajmi je nedostupné pretože je najskôr potrebné odomykanie pomocou PIN alebo hesla." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Odomykanie biometrickými údajmi je momentálne nedostupné." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Odomykanie biometrickými údajmi je nedostupné v dôsledku zle nastavených systémových súborov." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Odomykanie biometrickými údajmi je nedostupné v dôsledku zle nastavených systémových súborov." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Odomykanie biometrickými údajmi je nedostupné, pretože aplikácia Bitwarden pre desktop je zatvorená." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Odomykanie biometrickými údajmi je nedostupné, pretože nie je povolené pre $EMAIL$ v aplikácii Bitwarden pre desktop.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Odomykanie biometrickými údajmi je momentálne z neznámych dôvodov nedostupné." + }, "authenticating": { "message": "Overuje sa" }, @@ -4819,7 +5066,7 @@ "message": "Pripomenúť neskôr" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "Máte spoľahlivý prístup k svojmu e-mailu, $EMAIL$?", + "message": "Máte zaručený prístup k e-mailu $EMAIL$?", "placeholders": { "email": { "content": "$1", @@ -4831,7 +5078,7 @@ "message": "Nie, nemám" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Áno, mám spoľahlivý prístup k svojmu e-mailu" + "message": "Áno, mám zaručený prístup k e-mailu" }, "turnOnTwoStepLogin": { "message": "Zapnúť dvojstupňové prihlásenie" @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra široké" + }, + "sshKeyWrongPassword": { + "message": "Zadané heslo je nesprávne." + }, + "importSshKey": { + "message": "Importovať" + }, + "confirmSshKeyPassword": { + "message": "Potvrdiť heslo" + }, + "enterSshKeyPasswordDesc": { + "message": "Zadajte heslo pre kľúč SSH." + }, + "enterSshKeyPassword": { + "message": "Zadať heslo" + }, + "invalidSshKey": { + "message": "Kľúč SSH je neplatný" + }, + "sshKeyTypeUnsupported": { + "message": "Tento typ kľúča SSH nie je podporovaný" + }, + "importSshKeyFromClipboard": { + "message": "Importovať kľúč zo schránky" + }, + "sshKeyImported": { + "message": "Kľúč SSH bol úspešne importovaný" + }, + "cannotRemoveViewOnlyCollections": { + "message": "Zbierky, ktoré môžete len zobraziť nemôžete odstrániť: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Aktualizujte desktopovú aplikáciu" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "Ak chcete používať biometrické odomykanie, aktualizujte desktopovú aplikáciu alebo vypnite odomykanie odtlačkom prsta v nastaveniach desktopovej aplikácie." + }, + "changeAtRiskPassword": { + "message": "Zmeniť rizikové heslá" } } diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index 490f991d252..f6a543ea5ea 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Namig za glavno geslo (neobvezno)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Copy notes" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { "message": "Fill", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Namig za geslo" - }, - "enterEmailToGetHint": { - "message": "Vnesite e-poštni naslov svojega računa in poslali vam bomo namig za vaše glavno geslo." - }, "getMasterPasswordHint": { "message": "Pridobi namig za glavno geslo" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Uredi mapo" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "New folder" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Generate passphrase" }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "regeneratePassword": { "message": "Ponovno ustvari geslo" }, @@ -454,22 +482,6 @@ "length": { "message": "Dolžina" }, - "uppercase": { - "message": "Velike črke (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Male črke (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Števke (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Posebni znaki (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Include", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Število besed" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Vaš brskalnik ne podpira enostavnega kopiranja na odložišče. Prosimo, kopirajte ročno." }, - "verifyIdentity": { - "message": "Preverjanje istovetnosti" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "Vaš trezor je zaklenjen. Za nadaljevanje potrdite svojo identiteto." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Restart registration" }, @@ -875,6 +904,9 @@ "no": { "message": "Ne" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "Prišlo je do nepričakovane napake." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Če predmeta ni v trezorju, ga je potrebno dodati. Velja za vse prijavljene račune." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "Prikaži kartice na strani Zavihek" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Na strani Zavihek prikaži kartice za lažje samodejno izpoljnjevanje." }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "Prikaži identitete na strani Zavihek" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "Počisti odložišče", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Da, shrani zdaj" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Predlagaj posodobitev obstoječe prijave" }, @@ -1084,10 +1169,6 @@ "message": "Svetlo", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized dark", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Export from" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Kupite premium članstvo" }, - "premiumPurchaseAlert": { - "message": "Premium članstvo lahko kupite na spletnem trezoju bitwarden.com. Želite obiskati spletno stran zdaj?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Zapomni si me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Ponovno pošlji verifikacijsko kodo na email" }, "useAnotherTwoStepMethod": { "message": "Uporabi drugi način prijave v dveh korakih" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Priključi svoj YubiKey v USB priključek, nato pa pritisni na njegovo tipko." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Odpri nov zavihek" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "Prijava ni na voljo" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Možnosti dvostopenjske prijave" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Ste izgubili dostop do vseh ponudnikov dvostopenjske prijave? Uporabite svojo kodo za obnovitev in tako onemogočite dvostopenjsko prijavo v svoj račun." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Self-hosted environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Okolje po meri" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "URL naslov strežnika" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Sortirajte z vlečenjem" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Besedilo" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Username generator" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "Use this password" }, @@ -2063,12 +2163,24 @@ "message": "to create a strong unique password", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "Dejanje ob poteku roka" }, "vaultTimeoutAction1": { "message": "Timeout action" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "Zaklepanje", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "Izključene domene" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Napaka" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Ustvari uporabniško ime" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Settings have been edited" - }, - "environmentEditedClick": { - "message": "Kliknite tukaj" - }, - "environmentEditedReset": { - "message": "to reset to pre-configured settings" - }, "serverVersion": { "message": "Verzija strežnika" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Niste vi?" - }, "newAroundHere": { "message": "New around here?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Log in with device" }, - "loginWithDeviceEnabledInfo": { - "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" - }, "fingerprintPhraseHeader": { "message": "Identifikacijsko geslo" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "View all log in options" }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" - }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "You will be notified once the request is approved" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3396,38 +3649,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Active account" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Available accounts" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "No values to copy" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organization is deactivated" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "You have not selected anything." }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra wide" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index d515c2a0c6b..3c7bef94c13 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Савет Главне Лозинке (опционо)" }, + "passwordStrengthScore": { + "message": "Снага лозинкe $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Придружи Организацију" }, @@ -159,7 +168,7 @@ "message": "Копирај јавни кључ" }, "copyFingerprint": { - "message": "Копирати отисак" + "message": "Копирај отисак прста" }, "copyCustomField": { "message": "Копирати $FIELD$", @@ -176,6 +185,10 @@ "copyNotes": { "message": "Копирати белешке" }, + "copy": { + "message": "Копирај", + "description": "Copy to clipboard" + }, "fill": { "message": "Попуни", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Унесите имејл свог налога и биће вам послат савет за лозинку" }, - "passwordHint": { - "message": "Савет лозинке" - }, - "enterEmailToGetHint": { - "message": "Унесите Ваш имејл да би добили савет за Вашу Главну Лозинку." - }, "getMasterPasswordHint": { "message": "Добити савет за Главну Лозинку" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Уреди фасциклу" }, + "editFolderWithName": { + "message": "Уредити фасциклу: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "Нова фасцикла" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Генеришите приступну фразу" }, + "passwordGenerated": { + "message": "Лозинка генерисана" + }, + "passphraseGenerated": { + "message": "Приступна фраза је генерисана" + }, + "usernameGenerated": { + "message": "Корисничко име генерисано" + }, + "emailGenerated": { + "message": "Имејл генерисан" + }, "regeneratePassword": { "message": "Поново генериши лозинку" }, @@ -454,22 +482,6 @@ "length": { "message": "Дужина" }, - "uppercase": { - "message": "Велика слова (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Мала слова (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Цифре (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Специјална слова (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Укључити", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Укључити специјална слова", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Број речи" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Ваш прегледач не подржава једноставно копирање у клипборду. Уместо тога копирајте га ручно." }, - "verifyIdentity": { - "message": "Потврдите идентитет" + "verifyYourIdentity": { + "message": "Потврдите свој идентитет" + }, + "weDontRecognizeThisDevice": { + "message": "Не препознајемо овај уређај. Унесите код послат на адресу ваше електронске поште да би сте потврдили ваш идентитет." + }, + "continueLoggingIn": { + "message": "Настави са пријављивањем" }, "yourVaultIsLocked": { "message": "Сеф је закључан. Унесите главну лозинку за наставак." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Пријавите се на Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Унесите кôд послат на ваш имејл" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Унесите кôд из апликације за аутентификацију" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Стисните Ваш YubiKey за аутентификацију" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "За ваш налог је потребан два корака. Следите наведене кораке да бисте завршили пријављивање." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Следите наведене кораке да бисте завршили пријављивање." + }, "restartRegistration": { "message": "Поново покрените регистрацију" }, @@ -875,6 +904,9 @@ "no": { "message": "Не" }, + "location": { + "message": "Локација" + }, "unexpectedError": { "message": "Дошло је до неочекиване грешке." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Затражите да додате ставку ако она није пронађена у вашем сефу. Односи се на све пријављене налоге." }, - "showCardsInVaultView": { - "message": "Прикажите картице као предлоге за ауто-попуњавање у приказу сефа" + "showCardsInVaultViewV2": { + "message": "Увек приказуј картице као препоруке аутоматског попуњавања на приказу трезора" }, "showCardsCurrentTab": { "message": "Прикажи кредитне картице на страници картице" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Прикажи ставке кредитних картица на страници картице за лакше аутоматско допуњавање." }, - "showIdentitiesInVaultView": { - "message": "Прикажите идентитете као предлоге за ауто-попуњавање у приказу сефа" + "showIdentitiesInVaultViewV2": { + "message": "Увек приказуј идентитете као препоруке аутоматског попуњавања на приказу трезора" }, "showIdentitiesCurrentTab": { "message": "Прикажи идентитете на страници" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Кликните на ставке за ауто-попуњавање у приказу сефа" }, + "clickToAutofill": { + "message": "Кликните на ставке у ауто-пуњење предлогу за попуњавање" + }, "clearClipboard": { "message": "Обриши привремену меморију", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Сачувај" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ сачуван и Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ ажурирано у Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Сачувати као нову пријаву", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Ажурирати пријаву", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Сачувати пријаву?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Ажурирајте постојећу пријаву?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Пријава сачувана", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Пријава ажурирана", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Грешка при снимању", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Ох не! Нисмо могли да то сачувамо. Покушајте да ручно унесете детаље.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Питај за ажурирање постојеће пријаве" }, @@ -1084,10 +1169,6 @@ "message": "Светла", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized црно", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Извоз од" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Купити премијум" }, - "premiumPurchaseAlert": { - "message": "Можете купити премијум претплату на bitwarden.com. Да ли желите да посетите веб сајт сада?" - }, "premiumPurchaseAlertV2": { "message": "Можете да купите Премиум у подешавањима налога у веб апликацији Bitwarden." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Запамти ме" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Не питајте поново на овом уређају 30 дана" + }, "sendVerificationCodeEmailAgain": { "message": "Поново послати верификациони код на имејл" }, "useAnotherTwoStepMethod": { "message": "Користите другу методу пријављивања у два корака" }, + "selectAnotherMethod": { + "message": "Изаберите другу методу", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Употребите шифру за опоравак" + }, "insertYubiKey": { "message": "Убаците свој YubiKey у УСБ порт рачунара, а затим додирните његово дугме." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Отвори нови језичак " }, + "openInNewTab": { + "message": "Отвори у новом језичку" + }, "webAuthnAuthenticate": { "message": "WebAutn аутентификација" }, + "readSecurityKey": { + "message": "Читај сигурносни кључ" + }, + "awaitingSecurityKeyInteraction": { + "message": "Чека се интеракција сигурносног кључа..." + }, "loginUnavailable": { "message": "Пријава недоступна" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Опције дво-коракне пријаве" }, + "selectTwoStepLoginMethod": { + "message": "Одабрати методу пријављивања у два корака" + }, "recoveryCodeDesc": { "message": "Изгубили сте приступ свим својим двофакторским добављачима? Употребите код за опоравак да онемогућите све двофакторске добављаче из налога." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Самостално окружење" }, - "selfHostedEnvironmentFooter": { - "message": "Наведите основни УРЛ ваше локалне Bitwarden инсталације." - }, "selfHostedBaseUrlHint": { "message": "Наведите основну УРЛ адресу вашег локалног хостовања Bitwarden-а. Пример: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Прилагођено окружење" }, - "customEnvironmentFooter": { - "message": "За напредне кориснике. Можете да одредите независно основни УРЛ сваког сервиса." - }, "baseUrl": { "message": "УРЛ Сервера" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Превуците за сортирање" }, + "dragToReorder": { + "message": "Превуците да бисте организовали" + }, "cfTypeText": { "message": "Текст" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Генератор корисничког имена" }, + "useThisEmail": { + "message": "Користи ову епошту" + }, "useThisPassword": { "message": "Употреби ову лозинку" }, @@ -2063,12 +2163,24 @@ "message": "да креирате јаку јединствену лозинку", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Прилагођавање сефа" + }, "vaultTimeoutAction": { "message": "Акција на тајмаут сефа" }, "vaultTimeoutAction1": { "message": "Акција тајмаута" }, + "newCustomizationOptionsCalloutTitle": { + "message": "Нове опције прилагођавања" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Прилагодите своје искуство сефа помоћу брзих акција копирања, компактног режима и још много тога!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "Погледајте сва подешавања изглед" + }, "lock": { "message": "Закључај", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Домени", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Блокирани домени" + }, + "learnMoreAboutBlockedDomains": { + "message": "Сазнајте више о блокираним доменима" + }, "excludedDomains": { "message": "Изузети домени" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden неће тражити да сачува податке за пријављивање за ове домене за све пријављене налоге. Морате освежити страницу да би промене ступиле на снагу." }, + "blockedDomainsDesc": { + "message": "Аутоматско попуњавање и сродне функције неће бити понуђене за ове веб сајтове. Морате освежити страницу да би се измене примениле." + }, + "autofillBlockedNoticeV2": { + "message": "Аутоматско попуњавање је блокирано за овај веб сајт." + }, + "autofillBlockedNoticeGuidance": { + "message": "Промените ово у подешавањима" + }, + "change": { + "message": "Промени" + }, + "changeButtonTitle": { + "message": "Промена лозинке - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "Лозинке под ризиком" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ тражи да промените једну лозинку јер је ризична.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ тражи да промените $COUNT$ лозинке пошто су под ризиком.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Ваша организација тражи да промените $COUNT$ лозинке пошто су под ризиком.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Прегледајте и промените једну лозинку за ризик" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Прегледајте и промените лозинке под ризиком: $COUNT$", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Брже промените лозинке за ризик" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Ажурирајте поставке да бисте брзо поставили лозинке и генерисати нове" + }, + "reviewAtRiskLogins": { + "message": "Прегледајте ризичне пријаве" + }, + "reviewAtRiskPasswords": { + "message": "Прегледати ризичне лозинке" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Ваше организационе лозинке су ризичне јер су слабе, поново употребљене и/или изложене.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Илустрација листе пријаве које су ризичне" + }, + "generatePasswordSlideDesc": { + "message": "Брзо генеришите снажну, јединствену лозинку са Bitwarden менијем аутопуњења за коришћење на ризичном сајту.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Илустрација Bitwarden-ског менија аутопуњења који приказују генерисану лозинку" + }, + "updateInBitwarden": { + "message": "Ажурирања у Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden ће тада затражити да ажурирате лозинку у менаџеру лозинке.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Илустрација Bitwarden-ског обавештења за ажирриање пријаве" + }, + "turnOnAutofill": { + "message": "Омогућите ауто-пуњење" + }, + "turnedOnAutofill": { + "message": "Ауто-пуњење упаљено" + }, + "dismiss": { + "message": "Одбаци" + }, "websiteItemLabel": { "message": "Сајт $number$ (УРЛ)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Измене блокираних домена су сачуване" + }, "excludedDomainsSavedSuccess": { "message": "Изузете промене домена су сачуване" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Грешка" }, + "decryptionError": { + "message": "Грешка при декрипцији" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden није могао да декриптује ставке из трезора наведене испод." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Обратите се корисничкој подршци", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "да бисте избегли додатни губитак података.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Генериши име" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ одбио ваш захтев. Обратите се свом провајдеру сервиса за помоћ.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ одбио ваш захтев: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Није могуће добити ИД налога маскираног имејла $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Поставке су уређене" - }, - "environmentEditedClick": { - "message": "Кликните овде" - }, - "environmentEditedReset": { - "message": "за рисетовање на подразумевана подешавања" - }, "serverVersion": { "message": "Верзија сервера" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Пријавите се са главном лозинком" }, - "loggingInAs": { - "message": "Пријављивање као" - }, - "notYou": { - "message": "Нисте Ви?" - }, "newAroundHere": { "message": "Нов овде?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Пријавите се са уређајем" }, - "loginWithDeviceEnabledInfo": { - "message": "Пријава помоћу уређаја мора бити подешена у подешавањима Bitwarden апликације. Потребна је друга опција?" - }, "fingerprintPhraseHeader": { "message": "Сигурносна фраза сефа" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "Погледајте сав извештај у опције" }, - "viewAllLoginOptionsV1": { - "message": "Погледајте сав извештај у опције" - }, "notificationSentDevice": { "message": "Обавештење је послато на ваш уређај." }, + "notificationSentDevicePart1": { + "message": "Откључај Bitwarden на твом уређају или на" + }, + "notificationSentDeviceAnchor": { + "message": "веб апликација" + }, + "notificationSentDevicePart2": { + "message": "Потврдите да се фраза отиска прста поклапа са овом испод пре одобравања." + }, "aNotificationWasSentToYourDevice": { "message": "Обавештење је послато на ваш уређај" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Уверите се да је ваш налог откључан и да се фраза отиска подудара на другом уређају" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "Бићете обавештени када захтев буде одобрен" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Пријава је покренута" }, + "logInRequestSent": { + "message": "Захтев је послат" + }, "exposedMasterPassword": { "message": "Изложена главна лозинка" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Затражити одобрење администратора" }, - "approveWithMasterPassword": { - "message": "Одобрити са главном лозинком" - }, "ssoIdentifierRequired": { "message": "Потребан је SSO идентификатор организације." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Ваш захтев је послат вашем администратору." }, - "youWillBeNotifiedOnceApproved": { - "message": "Бићете обавештени када буде одобрено." - }, "troubleLoggingIn": { "message": "Имате проблема са пријављивањем?" }, @@ -3396,38 +3649,6 @@ "message": "Промени проширење", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Увезите своје податке у Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Заштитите своје LastPass податке и увезите у Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Сачувати као нешифровану датотеку", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Увоз у Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Увоз...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Подаци су успешно увезени!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Грешка при увозу. Проверите конзолу за детаље.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Дошло је до грешке на мрежи током увоза.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Домен алијаса" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Активан налог" }, + "bitwardenAccount": { + "message": "Bitwarden налог" + }, "availableAccounts": { "message": "Доступни налози" }, @@ -3979,7 +4203,10 @@ "message": "Приступни кључ је уклоњен" }, "autofillSuggestions": { - "message": "Предлози за ауто-попуњавање" + "message": "Предлози аутоматског попуњавања" + }, + "itemSuggestions": { + "message": "Предложене ставке" }, "autofillSuggestionsTip": { "message": "Сачувајте ставку за пријаву за ову локацију за ауто-попуњавање" @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Копирај $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "Нема вредности за копирање" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Име ставке" }, - "cannotRemoveViewOnlyCollections": { - "message": "Не можете уклонити колекције са дозволама само за приказ: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Организација је деактивирана" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Преместити УРЛ сајта. Користите тастер са стрелицом да бисте померили ставку." + }, "reorderFieldUp": { "message": "$LABEL$ премештено на горе, позиција $INDEX$ од $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "Нисте ништа изабрали." }, - "movedItemsToOrg": { - "message": "Одабране ставке премештене у $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Ставке премештене у $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Текст „Send“" }, - "bitwardenNewLook": { - "message": "Bitwarden има нови изглед!" - }, - "bitwardenNewLookDesc": { - "message": "Лакше је и интуитивније него икада да се аутоматски попуњава и тражи са картице Сефа. Проверите!" - }, "accountActions": { "message": "Акције везане за налог" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "Немате дозволу да уређујете ову ставку" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Биометријско откључавање није доступно јер је пре тога потребно унети ПИН или лозинку за откључавање." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Биометријско откључавање тренутно није доступно." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Биометријско откључавање није доступно због лоше подешених системских датотека." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Биометријско откључавање није доступно због лоше подешених системских датотека." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Биометријско откључавање није доступно јер је Bitwarden апликација на рачунару угашена." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Биометријско откључавање није доступно јер није омогућено за $EMAIL$ у Bitwarden апликацији на рачунару.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Биометријско откључавање није доступно из непознатог разлога." + }, "authenticating": { "message": "Аутентификација" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Врло широко" + }, + "sshKeyWrongPassword": { + "message": "Лозинка коју сте унели није тачна." + }, + "importSshKey": { + "message": "Увоз" + }, + "confirmSshKeyPassword": { + "message": "Потврда лозинке" + }, + "enterSshKeyPasswordDesc": { + "message": "Унети лозинку за SSH кључ." + }, + "enterSshKeyPassword": { + "message": "Унесите лозинку" + }, + "invalidSshKey": { + "message": "SSH кључ је неважећи" + }, + "sshKeyTypeUnsupported": { + "message": "Тип SSH кључа није подржан" + }, + "importSshKeyFromClipboard": { + "message": "Увезите кључ из оставе" + }, + "sshKeyImported": { + "message": "SSH кључ је успешно увезен" + }, + "cannotRemoveViewOnlyCollections": { + "message": "Не можете уклонити колекције са дозволама само за приказ: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Молим вас надоградите вашу апликацију на рачунару" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "Да би сте користили биометријско откључавање, надоградите вашу апликацију на рачунару, или онемогућите откључавање отиском прста у подешавањима на рачунару." + }, + "changeAtRiskPassword": { + "message": "Променити ризичну лозинку" } } diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index 84de9bbfa05..6db7a22490e 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -14,7 +14,7 @@ "message": "Logga in eller skapa ett nytt konto för att komma åt ditt säkra valv." }, "inviteAccepted": { - "message": "Invitation accepted" + "message": "Inbjudan accepterades" }, "createAccount": { "message": "Skapa konto" @@ -35,7 +35,7 @@ "message": "Ställ in ett starkt lösenord" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "Finish creating your account by setting a password" + "message": "Slutför skapandet av ditt konto genom att ange ett lösenord" }, "enterpriseSingleSignOn": { "message": "Single Sign-On för företag" @@ -80,11 +80,20 @@ "masterPassHint": { "message": "Huvudlösenordsledtråd (valfri)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { - "message": "Join organization" + "message": "Gå med i organisation" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "Gå med i $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -176,6 +185,10 @@ "copyNotes": { "message": "Kopiera anteckningar" }, + "copy": { + "message": "Kopiera", + "description": "Copy to clipboard" + }, "fill": { "message": "Fyll", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -193,10 +206,10 @@ "message": "Autofyll identitet" }, "fillVerificationCode": { - "message": "Fill verification code" + "message": "Fyll i verifieringskod" }, "fillVerificationCodeAria": { - "message": "Fill Verification Code", + "message": "Fyll i verifieringskod", "description": "Aria label for the heading displayed the inline menu for totp code autofill" }, "generatePasswordCopied": { @@ -239,7 +252,7 @@ "message": "Lägg till objekt" }, "accountEmail": { - "message": "Account email" + "message": "Kontots e-post" }, "requestHint": { "message": "Begär ledtråd" @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Lösenordsledtråd" - }, - "enterEmailToGetHint": { - "message": "Ange din e-postadress för att hämta din huvudlösenordsledtråd." - }, "getMasterPasswordHint": { "message": "Hämta huvudlösenordsledtråd" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Redigera mapp" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "Ny mapp" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Generera lösenfras" }, + "passwordGenerated": { + "message": "Lösenord genererades" + }, + "passphraseGenerated": { + "message": "Lösenordsfras genererades" + }, + "usernameGenerated": { + "message": "Användarnamn genererades" + }, + "emailGenerated": { + "message": "E-post genererades" + }, "regeneratePassword": { "message": "Återskapa lösenord" }, @@ -454,22 +482,6 @@ "length": { "message": "Längd" }, - "uppercase": { - "message": "Versaler (A-Ö)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Gemener (a-ö)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Siffror (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Specialtecken (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Inkludera", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Inkludera specialtecken", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Antal ord" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Din webbläsare har inte stöd för att enkelt kopiera till urklipp. Kopiera till urklipp manuellt istället." }, - "verifyIdentity": { - "message": "Verifiera identitet" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "Ditt valv är låst. Verifiera din identitet för att fortsätta." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Logga in på Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Restart registration" }, @@ -875,6 +904,9 @@ "no": { "message": "Nej" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "Ett okänt fel har inträffat." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "Visa kort på fliksida" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Lista kortobjekt på fliksidan för enkel automatisk fyllning." }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "Visa identiteter på fliksidan" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "Rensa urklipp", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Spara" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Be om att uppdatera befintlig inloggning" }, @@ -1084,10 +1169,6 @@ "message": "Ljust", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized mörk", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Exportera från" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Köp Premium" }, - "premiumPurchaseAlert": { - "message": "Du kan köpa premium-medlemskap i Bitwardens webbvalv. Vill du besöka webbplatsen nu?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Kom ihåg mig" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Skicka e-postmeddelandet med verifieringskoden igen" }, "useAnotherTwoStepMethod": { "message": "Använd en annan inloggningsmetod för tvåstegsverifiering" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Sätt i din YubiKey i en av datorns USB-portar och sätt fingret på knappen." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Öppna ny flik" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "Autentisera WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "Inloggning ej tillgänglig" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Alternativ för tvåstegsverifiering" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Förlorat åtkomst till alla dina metoder för tvåstegsverifiering? Använd din återställningskod för att inaktivera tvåstegsverifiering på ditt konto." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Egen-hostad miljö" }, - "selfHostedEnvironmentFooter": { - "message": "Ange bas-URL:en för din \"on-premise\"-hostade Bitwarden-installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Anpassad miljö" }, - "customEnvironmentFooter": { - "message": "För avancerade användare. Du kan ange bas-URL:en för varje tjänst oberoende av varandra." - }, "baseUrl": { "message": "Server-URL" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Dra för att sortera" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Text" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Username generator" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "Use this password" }, @@ -2063,12 +2163,24 @@ "message": "to create a strong unique password", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "Åtgärd när valvets tidsgräns överskrids" }, "vaultTimeoutAction1": { "message": "Timeout action" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "Lås", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domäner", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "Exkluderade domäner" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "Webbplats $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Fel" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generera användarnamn" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Inställningarna har ändrats" - }, - "environmentEditedClick": { - "message": "Klicka här" - }, - "environmentEditedReset": { - "message": "för att återställa till förkonfigurerade inställningar" - }, "serverVersion": { "message": "Serverversion" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Logga in med huvudlösenord" }, - "loggingInAs": { - "message": "Loggar in som" - }, - "notYou": { - "message": "Är det inte du?" - }, "newAroundHere": { "message": "Är du ny här?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Logga in med enhet" }, - "loginWithDeviceEnabledInfo": { - "message": "\"Logga in med enhet\" måste ställas in i inställningarna i Bitwardens app. Behöver du ett annat alternativ?" - }, "fingerprintPhraseHeader": { "message": "Fingeravtrycksfras" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "Visa alla inloggningsalternativ" }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" - }, "notificationSentDevice": { "message": "En avisering har skickats till din enhet." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "You will be notified once the request is approved" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Inloggning påbörjad" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Huvudlösenordet har exponerats" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Be om godkännande från administratör" }, - "approveWithMasterPassword": { - "message": "Godkänn med huvudlösenord" - }, "ssoIdentifierRequired": { "message": "Organisationens SSO-identifierare krävs." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Din begäran har skickats till din administratör." }, - "youWillBeNotifiedOnceApproved": { - "message": "Du kommer att meddelas vid godkännande." - }, "troubleLoggingIn": { "message": "Problem med att logga in?" }, @@ -3396,38 +3649,6 @@ "message": "Växla synlig/dold", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Importera din data till Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Skydda din LastPass-data och importera till Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Spara som okrypterad fil", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Importera till Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importerar...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data har importerats till ditt valv!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Fel vid importeringen. Kolla konsolen för detaljer.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Nätverksfel uppstod vid import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Aliasdomän" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Aktivt konto" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Tillgängliga konton" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "No values to copy" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Objektnamn" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organization is deactivated" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "You have not selected anything." }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden har fått ett nytt utseende!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Kontoåtgärder" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra wide" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index 6ab3755c8f4..c9c29611deb 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Master password hint (optional)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Copy notes" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { "message": "Fill", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Edit folder" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "New folder" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Generate passphrase" }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "regeneratePassword": { "message": "Regenerate password" }, @@ -454,22 +482,6 @@ "length": { "message": "Length" }, - "uppercase": { - "message": "Uppercase (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Lowercase (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Numbers (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Special characters (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Include", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Number of words" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Your web browser does not support easy clipboard copying. Copy it manually instead." }, - "verifyIdentity": { - "message": "Verify identity" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "Your vault is locked. Verify your identity to continue." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Restart registration" }, @@ -875,6 +904,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "An unexpected error has occurred." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "Show cards on Tab page" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "List card items on the Tab page for easy autofill." }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "Show identities on Tab page" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "Clear clipboard", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Save" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Ask to update existing login" }, @@ -1084,10 +1169,6 @@ "message": "Light", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized dark", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Export from" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Purchase Premium" }, - "premiumPurchaseAlert": { - "message": "You can purchase Premium membership on the bitwarden.com web vault. Do you want to visit the website now?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Open new tab" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "Login unavailable" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Lost access to all of your two-factor providers? Use your recovery code to turn off all two-factor providers from your account." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Self-hosted environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Custom environment" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "Server URL" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Drag to sort" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Text" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Username generator" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "Use this password" }, @@ -2063,12 +2163,24 @@ "message": "to create a strong unique password", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "Vault timeout action" }, "vaultTimeoutAction1": { "message": "Timeout action" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "Lock", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Settings have been edited" - }, - "environmentEditedClick": { - "message": "Click here" - }, - "environmentEditedReset": { - "message": "to reset to pre-configured settings" - }, "serverVersion": { "message": "Server version" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Log in with device" }, - "loginWithDeviceEnabledInfo": { - "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" - }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "View all log in options" }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" - }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "You will be notified once the request is approved" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3396,38 +3649,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Active account" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Available accounts" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "No values to copy" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organization is deactivated" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "You have not selected anything." }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra wide" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index d9ff1c3f076..c545f802d64 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Master Password Hint (optional)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Copy notes" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { "message": "Fill", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "คำใบ้รหัสผ่าน" - }, - "enterEmailToGetHint": { - "message": "กรอกอีเมลของบัญชีของคุณ เพื่อรับคำใบ้เกี่ยวกับรหัสผ่านหลักของคุณ" - }, "getMasterPasswordHint": { "message": "รับคำใบ้เกี่ยวกับรหัสผ่านหลักของคุณ" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "แก้ไขโฟลเดอร์" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "New folder" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Generate passphrase" }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "regeneratePassword": { "message": "Regenerate Password" }, @@ -454,22 +482,6 @@ "length": { "message": "ความยาว" }, - "uppercase": { - "message": "ตัวพิมพ์ใหญ่ (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "ตัวพิมพ์เล็ก (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "ตัวเลข (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "อักขระพิเศษ (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Include", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Number of Words" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "เว็บเบราว์เซอร์ของคุณไม่รองรับการคัดลอกคลิปบอร์ดอย่างง่าย คัดลอกด้วยตนเองแทน" }, - "verifyIdentity": { - "message": "ยืนยันตัวตน" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "ตู้เซฟของคุณถูกล็อก ยืนยันตัวตนของคุณเพื่อดำเนินการต่อ" @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Restart registration" }, @@ -875,6 +904,9 @@ "no": { "message": "ไม่ใช่" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "An unexpected error has occured." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." }, - "showCardsInVaultView": { - "message": "Show cards as Autofill suggestions on Vault view" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "แสดงการ์ดบนหน้าแท็บ" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "บัตรรายการในหน้าแท็บเพื่อให้ป้อนอัตโนมัติได้ง่าย" }, - "showIdentitiesInVaultView": { - "message": "Show identities as Autofill suggestions on Vault view" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "แสดงตัวตนบนหน้าแท็บ" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "ล้างคลิปบอร์ด", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Yes, Save Now" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "ขอให้ปรับปรุงการเข้าสู่ระบบที่มีอยู่" }, @@ -1084,10 +1169,6 @@ "message": "สว่าง", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized Dark", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Export from" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Purchase Premium" }, - "premiumPurchaseAlert": { - "message": "You can purchase Premium membership on the bitwarden.com web vault. Do you want to visit the website now?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "ส่งโค้ดยืนยันไปยังอีเมลอีกครั้ง" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Open new tab" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "Login Unavailable" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Two-step Login Options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Lost access to all of your two-factor providers? Use your recovery code to turn off all two-factor providers from your account." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Self-hosted Environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premise hosted bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Custom Environment" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "URL ของเซิร์ฟเวอร์" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Drag to sort" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "ข้อความ" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Username generator" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "Use this password" }, @@ -2063,12 +2163,24 @@ "message": "to create a strong unique password", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "การดำเนินการหลังหมดเวลาล็อคตู้เซฟ" }, "vaultTimeoutAction1": { "message": "Timeout action" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "ล็อก", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Settings have been edited" - }, - "environmentEditedClick": { - "message": "Click here" - }, - "environmentEditedReset": { - "message": "to reset to pre-configured settings" - }, "serverVersion": { "message": "Server version" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Log in with device" }, - "loginWithDeviceEnabledInfo": { - "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" - }, "fingerprintPhraseHeader": { "message": "Fingerprint phrase" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "View all log in options" }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" - }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "You will be notified once the request is approved" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3396,38 +3649,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Active account" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Available accounts" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "No values to copy" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Organization is deactivated" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "You have not selected anything." }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra wide" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index e1236b3f86d..e69b33d63af 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Ana parola ipucu (isteğe bağlı)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Kuruluşa katıl" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Notları kopyala" }, + "copy": { + "message": "Kopyala", + "description": "Copy to clipboard" + }, "fill": { "message": "Doldur", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Hesabınızın e-posta adresini girdiğinizde parola ipucunuz size gönderilecektir" }, - "passwordHint": { - "message": "Parola ipucu" - }, - "enterEmailToGetHint": { - "message": "Ana parola ipucunu almak için hesabınızın e-posta adresini girin." - }, "getMasterPasswordHint": { "message": "Ana parola ipucunu al" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Klasörü düzenle" }, + "editFolderWithName": { + "message": "Klasörü düzenle: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "Yeni klasör" }, @@ -385,7 +401,7 @@ "message": "Hiç klasör eklenmedi" }, "createFoldersToOrganize": { - "message": "Create folders to organize your vault items" + "message": "Kasanızdaki kayıtları organize etmek için klasörler oluşturun" }, "deleteFolderPermanently": { "message": "Bu klasörü kalıcı olarak silmek istediğinizden emin misiniz?" @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Parola üret" }, + "passwordGenerated": { + "message": "Parola üretildi" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Kullanıcı adı üretildi" + }, + "emailGenerated": { + "message": "E-posta üretildi" + }, "regeneratePassword": { "message": "Yeni parola oluştur" }, @@ -454,22 +482,6 @@ "length": { "message": "Uzunluk" }, - "uppercase": { - "message": "Büyük harf (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Küçük harf (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Rakamlar (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Özel karakterler (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Dahil et", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Özel karakterleri dahil et", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Kelime sayısı" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Web tarayıcınız panoya kopyalamayı desteklemiyor. Parolayı elle kopyalayın." }, - "verifyIdentity": { - "message": "Kimliği doğrula" + "verifyYourIdentity": { + "message": "Kimliğinizi doğrulayın" + }, + "weDontRecognizeThisDevice": { + "message": "Bu cihazı tanıyamadık. Kimliğinizi doğrulamak için e-postanıza gönderilen kodu girin." + }, + "continueLoggingIn": { + "message": "Giriş yapmaya devam et" }, "yourVaultIsLocked": { "message": "Kasanız kilitli. Devam etmek için kimliğinizi doğrulayın." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Bitwarden'a giriş yapın" }, + "enterTheCodeSentToYourEmail": { + "message": "E-posta adresinize gönderilen kodu girin" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Kimlik doğrulama uygulamanızdaki kodu girin" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Kaydı yeniden başlat" }, @@ -875,6 +904,9 @@ "no": { "message": "Hayır" }, + "location": { + "message": "Konum" + }, "unexpectedError": { "message": "Beklenmedik bir hata oluştu." }, @@ -978,7 +1010,7 @@ "message": "Hesap eklemeyi öner" }, "vaultSaveOptionsTitle": { - "message": "Kasa seçeneklerine kaydet" + "message": "Kasaya kaydetme seçenekleri" }, "addLoginNotificationDesc": { "message": "\"Hesap ekle\" bildirimi, ilk kez kullandığınız hesap bilgilerini kasanıza kaydetmek isteyip istemediğinizi otomatik olarak sorar." @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Kasanızda bulunmayan kayıtların eklenmesini isteyip istemediğinizi sorar. Oturum açmış tüm hesaplar için geçerlidir." }, - "showCardsInVaultView": { - "message": "Kasa görünümünde kartları otomatik doldurma önerisi olarak göster" + "showCardsInVaultViewV2": { + "message": "Kasa görünümünde kartları her zaman otomatik doldurma önerisi olarak göster" }, "showCardsCurrentTab": { "message": "Sekme sayfasında kartları göster" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Kolay otomatik doldurma için sekme sayfasında kartları listele." }, - "showIdentitiesInVaultView": { - "message": "Kasa görünümünde kimlikleri otomatik doldurma önerisi olarak göster" + "showIdentitiesInVaultViewV2": { + "message": "Kasa görünümünde kimlikleri her zaman otomatik doldurma önerisi olarak göster" }, "showIdentitiesCurrentTab": { "message": "Sekme sayfasında kimlikleri göster" @@ -1005,7 +1037,10 @@ "message": "Kolay otomatik doldurma için sekme sayfasında kimlikleri listele." }, "clickToAutofillOnVault": { - "message": "Kasa görünümünde otomatik doldurmak istediğiniz kayıtlara tıklayın" + "message": "Kasa görünümünde kayıtlara tıklayınca otomatik doldur" + }, + "clickToAutofill": { + "message": "Otomatik doldurma önerisindeki kayıtlara tıkladığımda doldur" }, "clearClipboard": { "message": "Panoyu temizle", @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Kaydet" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ Bitwarden'a kaydedildi.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ Bitwarden'da güncellendi.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Yeni hesap olarak kaydet", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Hesabı güncelle", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Hesap kaydedilsin mi?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Mevcut hesap güncellensin mi?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Hesap kaydedildi", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Hesap güncellendi", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Kaydetme hatası", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Bu hesabı kaydedemedik. Bilgileri elle girmeyi deneyin.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Mevcut hesapları güncellemeyi öner" }, @@ -1084,10 +1169,6 @@ "message": "Açık", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized koyu", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Dışa aktarılacak konum" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Premium satın al" }, - "premiumPurchaseAlert": { - "message": "Premium üyeliği bitwarden.com web kasası üzerinden satın alabilirsiniz. Şimdi siteye gitmek ister misiniz?" - }, "premiumPurchaseAlertV2": { "message": "Bitwarden web uygulamasındaki hesap ayarlarınızdan Premium abonelik satın alabilirsiniz." }, @@ -1275,7 +1353,7 @@ "message": "Bitwarden'ı desteklediğiniz için teşekkür ederiz." }, "premiumFeatures": { - "message": "Premium'a yükseltin ve şunları alın:" + "message": "Premium'a geçmenin avantajları:" }, "premiumPrice": { "message": "Bunların hepsi sadece yılda $PRICE$!", @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Beni hatırla" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Doğrulama kodunu yeniden gönder" }, "useAnotherTwoStepMethod": { "message": "Başka bir iki aşamalı giriş yöntemini kullan" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "YubiKey'i bilgisayarınızın USB portuna takın, ardından düğmesine dokunun." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Yeni sekme aç" }, + "openInNewTab": { + "message": "Yeni sekmede aç" + }, "webAuthnAuthenticate": { "message": "WebAutn ile doğrula" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "Giriş yapılamıyor" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "İki aşamalı giriş seçenekleri" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "İki aşamalı doğrulama sağlayıcılarınıza ulaşamıyor musunuz? Kurtarma kodunuzu kullanarak hesabınızdaki tüm iki aşamalı giriş sağlayıcılarını devre dışı bırakabilirsiniz." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Şirket içinde barındırılan ortam" }, - "selfHostedEnvironmentFooter": { - "message": "Kurum içinde barındırılan Bitwarden kurulumunuzun taban URL'sini belirtin." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Özel ortam" }, - "customEnvironmentFooter": { - "message": "İleri düzey kullanıcılar için. Her hizmetin taban URL'sini bağımsız olarak belirleyebilirsiniz." - }, "baseUrl": { "message": "Sunucu URL'si" }, @@ -1466,7 +1560,7 @@ "description": "Represents the message for allowing the user to enable the autofill overlay" }, "autofillSuggestionsSectionTitle": { - "message": "Önerileri otomatik doldur" + "message": "Otomatik doldurma önerileri" }, "showInlineMenuLabel": { "message": "Form alanlarında otomatik doldurma önerilerini göster" @@ -1502,13 +1596,13 @@ "description": "Overlay appearance select option for showing the field on click of the overlay icon" }, "enableAutoFillOnPageLoadSectionTitle": { - "message": "Sayfa yüklendiğinde otomatik doldur" + "message": "Sayfa yüklenince otomatik doldur" }, "enableAutoFillOnPageLoad": { - "message": "Sayfa yüklendiğinde otomatik doldur" + "message": "Sayfa yüklenince otomatik doldur" }, "enableAutoFillOnPageLoadDesc": { - "message": "Sayfa yüklendiğinde giriş formu tespit edilirse otomatik olarak formu doldur." + "message": "Sayfa yüklenince giriş formu tespit edilirse otomatik olarak formu doldur." }, "experimentalFeature": { "message": "Ele geçirilmiş veya güvenilmeyen web siteleri sayfa yüklenirken otomatik doldurmayı suistimal edebilir." @@ -1523,19 +1617,19 @@ "message": "Hesaplar için varsayılan otomatik doldurma ayarı" }, "defaultAutoFillOnPageLoadDesc": { - "message": "\"Sayfa yüklendiğinde otomatik doldur\"u her hesabın \"Düzenle\" görünümünden ayrı ayrı kapatabilirsiniz." + "message": "\"Sayfa yüklenince otomatik doldur\"u her hesabın \"Düzenle\" görünümünden ayrı ayrı kapatabilirsiniz." }, "itemAutoFillOnPageLoad": { - "message": "Sayfa yüklendiğinde otomatik doldur (Seçeneklerde ayarlanmışsa)" + "message": "Sayfa yüklenince otomatik doldur (Seçeneklerde ayarlanmışsa)" }, "autoFillOnPageLoadUseDefault": { "message": "Varsayılan ayarı kullan" }, "autoFillOnPageLoadYes": { - "message": "Sayfa yüklendiğinde otomatik doldur" + "message": "Sayfa yüklenince otomatik doldur" }, "autoFillOnPageLoadNo": { - "message": "Sayfa yüklendiğinde otomatik doldurma" + "message": "Sayfa yüklenince otomatik doldurma" }, "commandOpenPopup": { "message": "Kasayı açılır pencerede aç" @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Sıralamak için sürükleyin" }, + "dragToReorder": { + "message": "Sıralamak için sürükleyin" + }, "cfTypeText": { "message": "Metin" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Kullanıcı adı üreteci" }, + "useThisEmail": { + "message": "Bu e-postayı kullan" + }, "useThisPassword": { "message": "Bu parolayı kullan" }, @@ -2063,12 +2163,24 @@ "message": "üreteci kullanabilirsiniz", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Kasa kişiselleştirme" + }, "vaultTimeoutAction": { "message": "Kasa zaman aşımı eylemi" }, "vaultTimeoutAction1": { "message": "Zaman aşımı eylemi" }, + "newCustomizationOptionsCalloutTitle": { + "message": "Yeni özelleştirme seçenekleri" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "Kilitle", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Alan adları", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Engellenen alan adları" + }, + "learnMoreAboutBlockedDomains": { + "message": "Engellenmiş alan adları hakkında bilgi alın" + }, "excludedDomains": { "message": "Hariç tutulan alan adları" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden, oturum açmış tüm hesaplar için bu alan adlarının hesap bilgilerini kaydetmeyi sormayacaktır. Değişikliklerin etkili olması için sayfayı yenilemeniz gerekir." }, + "blockedDomainsDesc": { + "message": "Bu siteler için otomatik doldurma ve diğer ilgili özellikler önerilmeyecektir. Değişikliklerin devreye girmesi için sayfayı yenilemelisiniz." + }, + "autofillBlockedNoticeV2": { + "message": "Bu sitede otomatik doldurma engellenmiş." + }, + "autofillBlockedNoticeGuidance": { + "message": "Bunu ayarlardan değiştirebilirsiniz" + }, + "change": { + "message": "Değiştir" + }, + "changeButtonTitle": { + "message": "Parolayı değiştir - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "Riskli parolalar" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Otomatik doldurmayı etkinleştir" + }, + "turnedOnAutofill": { + "message": "Otomatik doldurma etkinleştirildi" + }, + "dismiss": { + "message": "Kapat" + }, "websiteItemLabel": { "message": "Web sitesi $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Engelli alan adı değişiklikleri kaydedildi" + }, "excludedDomainsSavedSuccess": { "message": "Alan adı istisnası değişiklikleri kaydedildi" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Hata" }, + "decryptionError": { + "message": "Şifre çözme sorunu" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Kullanıcı adı oluştur" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Ayarlar düzenlendi" - }, - "environmentEditedClick": { - "message": "Buraya tıklayarak" - }, - "environmentEditedReset": { - "message": "ön tanımlı ayarları sıfırlayabilirsiniz" - }, "serverVersion": { "message": "Sunucu sürümü" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Ana parola ile giriş yap" }, - "loggingInAs": { - "message": "Giriş yapılan kullanıcı:" - }, - "notYou": { - "message": "Siz değil misiniz?" - }, "newAroundHere": { "message": "Buralarda yeni misiniz?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Cihazla giriş yap" }, - "loginWithDeviceEnabledInfo": { - "message": "Cihazla girişi Bitwarden mobil uygulamasının ayarlarından etkinleştirmelisiniz. Başka bir seçeneğe mi ihtiyacınız var?" - }, "fingerprintPhraseHeader": { "message": "Parmak izi ifadesi" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "Tüm giriş seçeneklerini gör" }, - "viewAllLoginOptionsV1": { - "message": "Tüm giriş seçeneklerini gör" - }, "notificationSentDevice": { "message": "Cihazınıza bir bildirim gönderildi." }, + "notificationSentDevicePart1": { + "message": "Bitwarden kilidini cihazınızdan veya" + }, + "notificationSentDeviceAnchor": { + "message": "web uygulamasından açın" + }, + "notificationSentDevicePart2": { + "message": "Onay vermeden önce parmak izi ifadesinin aşağıdakiyle eşleştiğini kontrol edin." + }, "aNotificationWasSentToYourDevice": { "message": "Cihazınıza bir bildirim gönderildi" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Lütfen hesabınızın kilidinin açık olduğundan ve parmak izi ifadesinin diğer cihazla eşleştiğinden emin olun" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "İsteğiniz onaylanınca size haber vereceğiz" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Giriş başlatıldı" }, + "logInRequestSent": { + "message": "İstek gönderildi" + }, "exposedMasterPassword": { "message": "Açığa Çıkmış Ana Parola" }, @@ -3122,7 +3381,7 @@ } }, "autofillPageLoadPolicyActivated": { - "message": "Kuruluş ilkeleriniz, sayfa yüklendiğinde otomatik doldurmayı etkinleştirdi." + "message": "Kuruluş ilkeleriniz, sayfa yüklenince otomatik doldurmayı etkinleştirdi." }, "howToAutofill": { "message": "Otomatik doldurma nasıl yapılır?" @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Yönetici onayı iste" }, - "approveWithMasterPassword": { - "message": "Ana parola ile onayla" - }, "ssoIdentifierRequired": { "message": "Kuruluş SSO tanımlayıcısı gereklidir." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "İsteğiniz yöneticinize gönderildi." }, - "youWillBeNotifiedOnceApproved": { - "message": "Onaylandıktan sonra bilgilendirileceksiniz." - }, "troubleLoggingIn": { "message": "Giriş yaparken sorun mu yaşıyorsunuz?" }, @@ -3396,47 +3649,15 @@ "message": "Daraltmayı aç/kapat", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Verileriniz Bitwarden'a aktarılsın mı?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "LastPass verileriniz korunsun ve Bitwarden'a aktarılsın mı?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Şifrelenmemiş dosya olarak kaydet", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Bitwarden'a aktar", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "İçe aktarılıyor...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Veriler başarıyla içe aktarıldı!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "İçe aktarma hatası. Ayrıntılar için konsolu kontrol edin.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "İçe aktarma sırasında ağ hatasıyla karşılaşıldı.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias alan adı" }, "passwordRepromptDisabledAutofillOnPageLoad": { - "message": "Ana parolayı yeniden isteyen kayıtlar sayfa yüklendiğinde otomatik olarak doldurulamaz. Sayfa yüklendiğinde otomatik doldurma kapatıldı.", + "message": "Ana parolayı yeniden isteyen kayıtlar sayfa yüklenince otomatik olarak doldurulamaz. Sayfa yüklenince otomatik doldurma kapatıldı.", "description": "Toast message for describing that master password re-prompt cannot be autofilled on page load." }, "autofillOnPageLoadSetToDefault": { - "message": "Sayfa yüklendiğinde otomatik doldurma, varsayılan ayarı kullanacak şekilde ayarlandı.", + "message": "Sayfa yüklenince otomatik doldurma, varsayılan ayarı kullanacak şekilde ayarlandı.", "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "turnOffMasterPasswordPromptToEditField": { @@ -3711,7 +3932,7 @@ "message": "Geçiş anahtarı" }, "accessing": { - "message": "Erişim" + "message": "Erişilen konum:" }, "loggedInExclamation": { "message": "Giriş yapıldı!" @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Aktif hesap" }, + "bitwardenAccount": { + "message": "Bitwarden hesabı" + }, "availableAccounts": { "message": "Mevcut hesaplar" }, @@ -3979,7 +4203,10 @@ "message": "Geçiş anahtarı kaldırıldı" }, "autofillSuggestions": { - "message": "Önerileri otomatik doldur" + "message": "Otomatik doldurma önerileri" + }, + "itemSuggestions": { + "message": "Önerilen kayıtlar" }, "autofillSuggestionsTip": { "message": "Otomatik doldurma için bu siteye ait bir hesap kaydededin" @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Kopyala: $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "Kopyalanacak değer yok" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Kayıt adı" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Kuruluş pasifleştirilmiş" }, @@ -4304,7 +4536,7 @@ } }, "autoFillOnPageLoad": { - "message": "Sayfa yüklendiğinde otomatik doldur" + "message": "Sayfa yüklenince otomatik doldur" }, "cardExpiredTitle": { "message": "Kartın süresi dolmuş" @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "Hiçbir şey seçmediniz." }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Kayıtlar $ORGNAME$ kuruluşuna taşındı", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Metin Send'leri" }, - "bitwardenNewLook": { - "message": "Bitwarden'ın tasarımı güncellendi!" - }, - "bitwardenNewLookDesc": { - "message": "Otomatik doldurma ve kasanızda arama yapma artık eskisinden daha kolay. Yeni tasarıma göz atmayı unutmayın!" - }, "accountActions": { "message": "Hesap işlemleri" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "Bu kaydı düzenleme yetkisine sahip değilsiniz" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Kimlik doğrulanıyor" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Ekstra geniş" + }, + "sshKeyWrongPassword": { + "message": "Girdiğiniz parola yanlış." + }, + "importSshKey": { + "message": "İçe aktar" + }, + "confirmSshKeyPassword": { + "message": "Parolayı onaylayın" + }, + "enterSshKeyPasswordDesc": { + "message": "SSH anahtarının parolasını girin." + }, + "enterSshKeyPassword": { + "message": "Parolayı girin" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Lütfen masaüstü uygulamanızı güncelleyin" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index 8e20bc56ff5..d18d90babff 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Підказка для головного пароля (необов'язково)" }, + "passwordStrengthScore": { + "message": "Рейтинг надійності пароля $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Приєднатися до організації" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Копіювати нотатки" }, + "copy": { + "message": "Копіювати", + "description": "Copy to clipboard" + }, "fill": { "message": "Заповнити", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Введіть адресу е-пошти свого облікового запису і вам буде надіслано підказку для пароля" }, - "passwordHint": { - "message": "Підказка для пароля" - }, - "enterEmailToGetHint": { - "message": "Введіть свою адресу е-пошти, щоб отримати підказку для головного пароля." - }, "getMasterPasswordHint": { "message": "Отримати підказку для головного пароля" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Редагування" }, + "editFolderWithName": { + "message": "Редагувати теку: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "Нова тека" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Генерувати парольну фразу" }, + "passwordGenerated": { + "message": "Пароль згенеровано" + }, + "passphraseGenerated": { + "message": "Парольну фразу згенеровано" + }, + "usernameGenerated": { + "message": "Ім'я користувача згенеровано" + }, + "emailGenerated": { + "message": "Адресу е-пошти згенеровано" + }, "regeneratePassword": { "message": "Генерувати новий" }, @@ -454,22 +482,6 @@ "length": { "message": "Довжина" }, - "uppercase": { - "message": "Верхній регістр (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Нижній регістр (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Числа (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Спеціальні символи (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Включити", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Спеціальні символи", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Кількість слів" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Ваш браузер не підтримує копіювання даних в буфер обміну. Скопіюйте вручну." }, - "verifyIdentity": { - "message": "Виконати перевірку" + "verifyYourIdentity": { + "message": "Підтвердьте свою особу" + }, + "weDontRecognizeThisDevice": { + "message": "Ми не розпізнаємо цей пристрій. Введіть код, надісланий на вашу електронну пошту, щоб підтвердити вашу особу." + }, + "continueLoggingIn": { + "message": "Продовжити вхід" }, "yourVaultIsLocked": { "message": "Ваше сховище заблоковане. Для продовження виконайте перевірку." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Увійти в Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Введіть код, надісланий вам електронною поштою" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Введіть код з програми автентифікації" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Натисніть свій YubiKey для автентифікації" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Для вашого облікового запису необхідно пройти двоетапну перевірку з Duo. Виконайте наведені нижче кроки, щоб завершити вхід." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Виконайте наведені нижче кроки, щоб завершити вхід." + }, "restartRegistration": { "message": "Перезапустити реєстрацію" }, @@ -875,6 +904,9 @@ "no": { "message": "Ні" }, + "location": { + "message": "Розташування" + }, "unexpectedError": { "message": "Сталася неочікувана помилка." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Запитувати про додавання запису, якщо такого не знайдено у вашому сховищі. Застосовується для всіх облікових записів, до яких виконано вхід." }, - "showCardsInVaultView": { - "message": "Показувати картки як пропозиції автозаповнення в режимі перегляду сховища" + "showCardsInVaultViewV2": { + "message": "Завжди показувати картки як пропозиції автозаповнення в режимі перегляду сховища" }, "showCardsCurrentTab": { "message": "Показувати картки на вкладці" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Показувати список карток на сторінці вкладки для легкого автозаповнення." }, - "showIdentitiesInVaultView": { - "message": "Показувати посвідчення як пропозиції автозаповнення в режимі перегляду сховища" + "showIdentitiesInVaultViewV2": { + "message": "Завжди показувати посвідчення як пропозиції автозаповнення в режимі перегляду сховища" }, "showIdentitiesCurrentTab": { "message": "Показувати посвідчення на вкладці" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Натисніть на запис у режимі перегляду сховища для автозаповнення" }, + "clickToAutofill": { + "message": "Натисніть запис у пропозиціях для автозаповнення" + }, "clearClipboard": { "message": "Очистити буфер обміну", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Зберегти" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ збережено до Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ оновлено у Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Зберегти як новий запис", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Оновити запис", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Зберегти запис?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Оновити наявний запис?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Запис збережено", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Запис оновлено", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Помилка збереження", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "На жаль, не вдається зберегти. Введіть дані вручну.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Запитувати про оновлення запису" }, @@ -1084,10 +1169,6 @@ "message": "Світла", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized темна", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Експортувати з" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Придбати преміум" }, - "premiumPurchaseAlert": { - "message": "Ви можете передплатити преміум у сховищі на bitwarden.com. Хочете перейти на вебсайт зараз?" - }, "premiumPurchaseAlertV2": { "message": "Ви можете придбати Преміум у налаштуваннях облікового запису вебпрограмі Bitwarden." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Запам'ятати мене" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Більше не запитувати на цьому пристрої протягом 30 днів" + }, "sendVerificationCodeEmailAgain": { "message": "Надіслати код підтвердження ще раз" }, "useAnotherTwoStepMethod": { "message": "Інший спосіб двоетапної перевірки" }, + "selectAnotherMethod": { + "message": "Обрати інший спосіб", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Скористайтеся своїм кодом відновлення" + }, "insertYubiKey": { "message": "Вставте свій YubiKey в USB порт комп'ютера, потім торкніться цієї кнопки." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Відкрити нову вкладку" }, + "openInNewTab": { + "message": "Відкрити в новій вкладці" + }, "webAuthnAuthenticate": { "message": "Автентифікація WebAuthn" }, + "readSecurityKey": { + "message": "Зчитати ключ безпеки" + }, + "awaitingSecurityKeyInteraction": { + "message": "Очікується взаємодія з ключем безпеки..." + }, "loginUnavailable": { "message": "Вхід недоступний" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Налаштування двоетапної перевірки" }, + "selectTwoStepLoginMethod": { + "message": "Виберіть спосіб двоетапної перевірки" + }, "recoveryCodeDesc": { "message": "Втратили доступ до всіх провайдерів двоетапної перевірки? Скористайтеся кодом відновлення, щоб вимкнути двоетапну перевірку для свого облікового запису." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Середовище власного хостингу" }, - "selfHostedEnvironmentFooter": { - "message": "Вкажіть основну URL-адресу вашого встановлення Bitwarden на власному хостингу." - }, "selfHostedBaseUrlHint": { "message": "Вкажіть основну URL-адресу вашого встановлення Bitwarden на власному хостингу. Зразок: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Власне середовище" }, - "customEnvironmentFooter": { - "message": "Для досвідчених користувачів. Ви можете вказати основну URL-адресу окремо для кожної служби." - }, "baseUrl": { "message": "URL-адреса сервера" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Перетягніть, щоб відсортувати" }, + "dragToReorder": { + "message": "Потягніть, щоб упорядкувати" + }, "cfTypeText": { "message": "Текст" }, @@ -1603,10 +1700,10 @@ "message": "Показувати піктограми вебсайтів" }, "faviconDesc": { - "message": "Показувати впізнаване зображення біля кожного запису." + "message": "Показувати зображення біля кожного запису." }, "faviconDescAlt": { - "message": "Показати впізнаване зображення поруч з кожним записом. Застосовується для всіх облікових записів, до яких виконано вхід." + "message": "Показувати зображення поруч з кожним записом. Застосовується для всіх облікових записів, до яких виконано вхід." }, "enableBadgeCounter": { "message": "Показувати лічильник" @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Генератор імені користувача" }, + "useThisEmail": { + "message": "Використати цю е-пошту" + }, "useThisPassword": { "message": "Використати цей пароль" }, @@ -2063,12 +2163,24 @@ "message": "для створення надійного, унікального пароля", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Налаштування сховища" + }, "vaultTimeoutAction": { "message": "Дія після часу очікування сховища" }, "vaultTimeoutAction1": { "message": "Дія після часу очікування" }, + "newCustomizationOptionsCalloutTitle": { + "message": "Нові можливості налаштування" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Налаштуйте своє сховище за допомогою швидких дій копіювання, компактного режиму та інших можливостей!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "Всі налаштування подання" + }, "lock": { "message": "Блокувати", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Домени", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Заблоковані домени" + }, + "learnMoreAboutBlockedDomains": { + "message": "Докладніше про заблоковані домени" + }, "excludedDomains": { "message": "Виключені домени" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden не запитуватиме про збереження даних входу для цих доменів для всіх облікових записів, до яких виконано вхід. Потрібно оновити сторінку для застосування змін." }, + "blockedDomainsDesc": { + "message": "Автозаповнення та інші пов'язані функції не пропонуватимуться для цих вебсайтів. Вам слід оновити сторінку для застосування змін." + }, + "autofillBlockedNoticeV2": { + "message": "Автозаповнення для цього вебсайту заблоковано." + }, + "autofillBlockedNoticeGuidance": { + "message": "Змінити в налаштуваннях" + }, + "change": { + "message": "Змінити" + }, + "changeButtonTitle": { + "message": "Змінити пароль – $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "Ризиковані паролі" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ вимагає зміни один пароль, оскільки він ризикований.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ вимагає зміни $COUNT$ паролів, оскільки вони ризиковані.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Ваші організації вимагають зміни $COUNT$ паролів, оскільки вони ризиковані.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Перегляньте і змініть один ризикований пароль" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Перегляньте і змініть $COUNT$ ризикованих паролів", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Швидше змінюйте ризиковані паролі" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Оновіть налаштування, щоб швидше автоматично заповнювати й створювати паролі" + }, + "reviewAtRiskLogins": { + "message": "Переглянути записи з ризиком" + }, + "reviewAtRiskPasswords": { + "message": "Переглянути ризиковані паролі" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Паролі вашої організації ризиковані, оскільки вони ненадійні, повторно використовуються, та/або викриті.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Ілюстрація списку ризикованих записів" + }, + "generatePasswordSlideDesc": { + "message": "Швидко згенеруйте надійний, унікальний пароль через меню автозаповнення Bitwarden на сайті з ризикованим паролем.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Ілюстрація меню автозаповнення Bitwarden, що показує згенерований пароль" + }, + "updateInBitwarden": { + "message": "Оновити в Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Потім Bitwarden запропонує вам оновити пароль у менеджері паролів.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Ілюстрація сповіщення Bitwarden, що спонукає користувача оновити пароль" + }, + "turnOnAutofill": { + "message": "Увімкніть автозаповнення" + }, + "turnedOnAutofill": { + "message": "Автозаповнення увімкнено" + }, + "dismiss": { + "message": "Відхилити" + }, "websiteItemLabel": { "message": "Вебсайт $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Зміни заблокованих доменів збережено" + }, "excludedDomainsSavedSuccess": { "message": "Виняток для домену збережено" }, @@ -2376,7 +2609,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDetails": { - "message": "Надіслати подробиці", + "message": "Подробиці відправлення", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTypeText": { @@ -2789,6 +3022,20 @@ "error": { "message": "Помилка" }, + "decryptionError": { + "message": "Помилка розшифрування" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden не зміг розшифрувати вказані нижче елементи сховища." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Зверніться до служби підтримки клієнтів,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "щоб уникнути втрати даних.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Генерувати ім'я користувача" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ відхилив ваш запит. Зверніться до провайдера послуг по допомогу.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ відхилив ваш запит: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Не вдалося отримати ідентифікатор замаскованої е-пошти облікового запису для $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Налаштування змінено" - }, - "environmentEditedClick": { - "message": "Натисніть тут," - }, - "environmentEditedReset": { - "message": "щоб скинути налаштування" - }, "serverVersion": { "message": "Версія сервера" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Увійти з головним паролем" }, - "loggingInAs": { - "message": "Вхід у систему як" - }, - "notYou": { - "message": "Не ви?" - }, "newAroundHere": { "message": "Виконуєте вхід вперше?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Увійти з пристроєм" }, - "loginWithDeviceEnabledInfo": { - "message": "Потрібно увімкнути схвалення запитів на вхід у налаштуваннях програми Bitwarden. Потрібен інший варіант?" - }, "fingerprintPhraseHeader": { "message": "Фраза відбитка" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "Переглянути всі варіанти входу" }, - "viewAllLoginOptionsV1": { - "message": "Переглянути всі варіанти входу" - }, "notificationSentDevice": { "message": "Сповіщення було надіслано на ваш пристрій." }, + "notificationSentDevicePart1": { + "message": "Розблокуйте Bitwarden на своєму пристрої або у" + }, + "notificationSentDeviceAnchor": { + "message": "вебпрограмі" + }, + "notificationSentDevicePart2": { + "message": "Перш ніж підтверджувати, обов'язково перевірте відповідність зазначеної нижче фрази відбитка." + }, "aNotificationWasSentToYourDevice": { "message": "Сповіщення надіслано на ваш пристрій" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Переконайтеся, що ваш обліковий запис розблоковано і фраза відбитка на іншому пристрої збігається" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "Після схвалення запиту ви отримаєте сповіщення" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Ініційовано вхід" }, + "logInRequestSent": { + "message": "Запит надіслано" + }, "exposedMasterPassword": { "message": "Головний пароль викрито" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Запит підтвердження адміністратора" }, - "approveWithMasterPassword": { - "message": "Затвердити з головним паролем" - }, "ssoIdentifierRequired": { "message": "Потрібен SSO-ідентифікатор організації." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Ваш запит відправлено адміністратору." }, - "youWillBeNotifiedOnceApproved": { - "message": "Ви отримаєте сповіщення після затвердження." - }, "troubleLoggingIn": { "message": "Проблема під час входу?" }, @@ -3396,38 +3649,6 @@ "message": "Згорнути/розгорнути", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Імпортувати ваші дані до Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Захистити ваші дані LastPass та імпортувати до Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Зберегти незашифрований файл", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Імпортувати до Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Триває імпортування...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Дані успішно імпортовано!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Помилка імпортування. Перевірте консоль для перегляду подробиць.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Під час імпортування сталася мережева помилка.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Псевдонім домену" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Активний обліковий запис" }, + "bitwardenAccount": { + "message": "Обліковий запис Bitwarden" + }, "availableAccounts": { "message": "Доступні облікові записи" }, @@ -3981,6 +4205,9 @@ "autofillSuggestions": { "message": "Пропозиції автозаповнення" }, + "itemSuggestions": { + "message": "Запропоновані записи" + }, "autofillSuggestionsTip": { "message": "Зберегти дані входу цього сайту для автозаповнення" }, @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Копіювати $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "Немає значень для копіювання" }, @@ -4078,7 +4319,7 @@ "message": "Сповіщення" }, "appearance": { - "message": "Вигляд" + "message": "Подання" }, "errorAssigningTargetCollection": { "message": "Помилка призначення цільової збірки." @@ -4128,15 +4369,6 @@ "itemName": { "message": "Назва запису" }, - "cannotRemoveViewOnlyCollections": { - "message": "Ви не можете вилучати збірки, маючи дозвіл лише на перегляд: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Організацію деактивовано" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Змініть порядок URI вебсайтів. Використовуйте стрілки, щоб перемістити елемент вгору чи вниз." + }, "reorderFieldUp": { "message": "$LABEL$ переміщено вгору, позиція $INDEX$ з $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "Ви нічого не вибрали." }, - "movedItemsToOrg": { - "message": "Вибрані записи переміщено до $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Записи переміщено до $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Відправлення тексту" }, - "bitwardenNewLook": { - "message": "Bitwarden має новий вигляд!" - }, - "bitwardenNewLookDesc": { - "message": "Ще простіше автозаповнення та інтуїтивніший пошук у сховищі. Ознайомтеся!" - }, "accountActions": { "message": "Дії з обліковим записом" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "Вам не дозволено редагувати цей запис" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Біометричне розблокування недоступне, оскільки спочатку потрібно ввести PIN-код або пароль." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Біометричне розблокування наразі недоступне." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Біометричне розблокування недоступне через неправильно налаштовані системні файли." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Біометричне розблокування недоступне через неправильно налаштовані системні файли." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Біометричне розблокування недоступне, оскільки програму Bitwarden для комп'ютера закрито." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Біометричне розблокування недоступне, оскільки воно не увімкнене для $EMAIL$ у програмі Bitwarden для комп'ютера.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Біометричне розблокування зараз недоступне з невідомої причини." + }, "authenticating": { "message": "Аутентифікація" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Дуже широке" + }, + "sshKeyWrongPassword": { + "message": "Ви ввели неправильний пароль." + }, + "importSshKey": { + "message": "Імпорт" + }, + "confirmSshKeyPassword": { + "message": "Підтвердити пароль" + }, + "enterSshKeyPasswordDesc": { + "message": "Введіть пароль для ключа SSH." + }, + "enterSshKeyPassword": { + "message": "Введіть пароль" + }, + "invalidSshKey": { + "message": "Ключ SSH недійсний" + }, + "sshKeyTypeUnsupported": { + "message": "Тип ключа SSH не підтримується" + }, + "importSshKeyFromClipboard": { + "message": "Імпортувати ключ із буфера обміну" + }, + "sshKeyImported": { + "message": "Ключ SSH успішно імпортовано" + }, + "cannotRemoveViewOnlyCollections": { + "message": "Ви не можете вилучати збірки, маючи дозвіл лише на перегляд: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Оновіть свою комп'ютерну програму" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "Щоб використовувати біометричне розблокування, оновіть комп'ютерну програму, або вимкніть розблокування відбитком пальця в налаштуваннях системи." + }, + "changeAtRiskPassword": { + "message": "Змінити ризикований пароль" } } diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index 268b12a6254..db0da3b5874 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -80,6 +80,15 @@ "masterPassHint": { "message": "Gợi ý mật khẩu chính (tùy chọn)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Tham gia tổ chức" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "Copy notes" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { "message": "Fill", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Gợi ý mật khẩu" - }, - "enterEmailToGetHint": { - "message": "Nhập địa chỉ email tài khoản của bạn để nhận gợi ý mật khẩu chính." - }, "getMasterPasswordHint": { "message": "Nhận gợi ý mật khẩu chính" }, @@ -372,6 +379,15 @@ "editFolder": { "message": "Chỉnh sửa thư mục" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "Thư mục mới" }, @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "Generate passphrase" }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "regeneratePassword": { "message": "Tạo lại mật khẩu" }, @@ -454,22 +482,6 @@ "length": { "message": "Độ dài" }, - "uppercase": { - "message": "Chữ in hoa (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "Chữ in thường (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "Chữ số (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "Ký tự đặc biệt (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "Bao gồm", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "Bao gồm các ký tự đặc biệt", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Số từ" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "Trình duyệt web của bạn không hỗ trợ dễ dàng sao chép bộ nhớ tạm. Bạn có thể sao chép nó theo cách thủ công để thay thế." }, - "verifyIdentity": { - "message": "Xác minh danh tính" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" }, "yourVaultIsLocked": { "message": "Kho của bạn đã bị khóa. Xác minh danh tính của bạn để mở khoá." @@ -854,6 +868,21 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Tiến hành đăng ký lại" }, @@ -875,6 +904,9 @@ "no": { "message": "Không" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "Một lỗi bất ngờ đã xảy ra." }, @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "Đưa ra lựa chọn để thêm một mục nếu không tìm thấy mục đó trong hòm của bạn. Áp dụng với mọi tài khoản đăng nhập trên thiết bị." }, - "showCardsInVaultView": { - "message": "Hiển thị các thẻ như các gợi ý tự động điền trên giao diện kho" + "showCardsInVaultViewV2": { + "message": "Always show cards as Autofill suggestions on Vault view" }, "showCardsCurrentTab": { "message": "Hiển thị thẻ trên trang Tab" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "Liệt kê các mục thẻ trên trang Tab để dễ dàng tự động điền." }, - "showIdentitiesInVaultView": { - "message": "Hiển thị các danh tính như các gợi ý tự động điền trên giao diện kho" + "showIdentitiesInVaultViewV2": { + "message": "Always show identities as Autofill suggestions on Vault view" }, "showIdentitiesCurrentTab": { "message": "Hiển thị danh tính trên trang Tab" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" + }, "clearClipboard": { "message": "Dọn dẹp khay nhớ tạm", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "Lưu" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "Hỏi để cập nhật đăng nhập hiện có" }, @@ -1084,10 +1169,6 @@ "message": "Sáng", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized Dark", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "Xuất từ" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "Mua bản Cao Cấp" }, - "premiumPurchaseAlert": { - "message": "Bạn có thể nâng cấp làm thành viên cao cấp trong kho bitwarden nền web. Bạn có muốn truy cập trang web bây giờ?" - }, "premiumPurchaseAlertV2": { "message": "Bạn có thể mua gói Premium từ cài đặt tài khoản trên trang Bitwarden." }, @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "Ghi nhớ đăng nhập" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Gửi lại email chứa mã xác nhận" }, "useAnotherTwoStepMethod": { "message": "Sử dụng phương pháp đăng nhập 2 bước khác" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Lắp YubiKey vào cổng USB máy tính của bạn, sau đó chạm vào nút trên nó." }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "Mở thẻ mới" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "Xác thực WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "Đăng nhập không có sẵn" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "Tùy chọn xác thực hai lớp" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Bạn mất quyền truy cập vào tất cả các dịch vụ xác thực 2 lớp? Sử dụng mã phục hồi của bạn để vô hiệu hóa tất cả các dịch vụ xác thực hai lớp trong tài khoản của bạn." }, @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "Môi trường tự lưu trữ" }, - "selfHostedEnvironmentFooter": { - "message": "Chỉ định liên kết cơ bản của cài đặt bitwarden tại chỗ của bạn." - }, "selfHostedBaseUrlHint": { "message": "Nhập địa chỉ cơ sở của bản cài đặt Bitwarden được lưu trữ tại máy chủ của bạn. Ví dụ: https://bitwarden.company.com" }, @@ -1433,9 +1530,6 @@ "customEnvironment": { "message": "Môi trường tùy chỉnh" }, - "customEnvironmentFooter": { - "message": "Đối với người dùng nâng cao. Bạn có thể chỉ định URL cơ bản của mỗi dịch vụ một cách độc lập." - }, "baseUrl": { "message": "URL máy chủ" }, @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "Kéo để sắp xếp" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Văn bản" }, @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "Bộ tạo tên người dùng" }, + "useThisEmail": { + "message": "Use this email" + }, "useThisPassword": { "message": "Use this password" }, @@ -2063,12 +2163,24 @@ "message": "to create a strong unique password", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "Hành động khi hết thời gian chờ của kho lưu trữ" }, "vaultTimeoutAction1": { "message": "Timeout action" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "Khóa", "description": "Verb form: to make secure or inaccessible by" @@ -2324,6 +2436,12 @@ "message": "Các tên miền", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "Tên miền đã loại trừ" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "Bitwarden sẽ không yêu cầu lưu thông tin đăng nhập cho các miền này. Bạn phải làm mới trang để các thay đổi có hiệu lực." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." + }, + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "Trang Web $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Các thay đổi tên miền loại trừ đã được lưu" }, @@ -2789,6 +3022,20 @@ "error": { "message": "Lỗi" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Tạo tên người dùng" }, @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Không thể lấy ID tài khoản email ẩn từ $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "Cài đặt đã được chỉnh sửa" - }, - "environmentEditedClick": { - "message": "Nhấn vào đây" - }, - "environmentEditedReset": { - "message": "để đặt lại cài đặt đã thiết đặt từ trước" - }, "serverVersion": { "message": "Phiên bản máy chủ" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "Đăng nhập bằng mật khẩu chính" }, - "loggingInAs": { - "message": "Đang đăng nhập với tên" - }, - "notYou": { - "message": "Không phải bạn?" - }, "newAroundHere": { "message": "Bạn mới tới đây sao?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "Đăng nhập bằng thiết bị" }, - "loginWithDeviceEnabledInfo": { - "message": "Đăng nhập bằng thiết bị phải được thiết lập trong cài đặt của ứng dụng Bitwarden. Dùng cách khác?" - }, "fingerprintPhraseHeader": { "message": "Cụm vân tay" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "View all log in options" }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" - }, "notificationSentDevice": { "message": "Một thông báo đã được gửi đến thiết bị của bạn." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "You will be notified once the request is approved" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "Bắt đầu đăng nhập" }, + "logInRequestSent": { + "message": "Request sent" + }, "exposedMasterPassword": { "message": "Mật khẩu chính bị lộ" }, @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "Yêu cầu quản trị viên phê duyệt" }, - "approveWithMasterPassword": { - "message": "Phê duyệt bằng mật khẩu chính" - }, "ssoIdentifierRequired": { "message": "Cần có mã định danh SSO của tổ chức." }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Yêu cầu của bạn đã được gửi đến quản trị viên." }, - "youWillBeNotifiedOnceApproved": { - "message": "Bạn sẽ có thông báo nếu được phê duyệt." - }, "troubleLoggingIn": { "message": "Không thể đăng nhập?" }, @@ -3396,38 +3649,6 @@ "message": "Bật/tắt thu gọn", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Nhập dữ liệu của bạn vào Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Bảo vệ dữ liệu LastPass của bạn và nhập vào Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Lưu dưới dạng tập tin không được mã hóa", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Nhập vào Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Đang nhập...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Dữ liệu đã được nhập thành công!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Xảy ra lỗi trong quá trình nhập. Kiểm tra bảng điều khiển để biết thêm chi tiết.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Đã xảy ra lỗi mạng trong quá trình nhập dữ liệu.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Tên miền thay thế" }, @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "Tài khoản đang hoạt động" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "Các tài khoản khả dụng" }, @@ -3979,7 +4203,10 @@ "message": "Đã xóa mã khoá" }, "autofillSuggestions": { - "message": "Gợi ý điền tự động" + "message": "Autofill suggestions" + }, + "itemSuggestions": { + "message": "Suggested items" }, "autofillSuggestionsTip": { "message": "Lưu thông tin đăng nhập cho trang này để tự động điền" @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "Không có giá trị để sao chép" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "Tên mục" }, - "cannotRemoveViewOnlyCollections": { - "message": "Bạn không thể xóa các bộ sưu tập với quyền chỉ xem: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "Tổ chức không còn hoạt động" }, @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { "message": "$LABEL$ đã di chuyển lên vị trí $INDEX$ / $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "Bạn chưa chọn gì." }, - "movedItemsToOrg": { - "message": "Đã chuyển các mục được chọn đến $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Các mục đã được chuyển tới $ORGNAME$", "placeholders": { @@ -4557,12 +4783,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "Extra wide" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "Bạn không thể xóa các bộ sưu tập với quyền chỉ xem: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "Please update your desktop application" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index 69c1194af47..bdfaeb92531 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -7,7 +7,7 @@ "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "无论是在家里、工作中还是在外出时,Bitwarden 都可以轻松地保护您的所有密码、通行密钥和敏感信息。", + "message": "无论是在家中、工作中还是在旅途中,Bitwarden 都可以轻松地保护您的所有密码、通行密钥和敏感信息。", "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { @@ -56,7 +56,7 @@ "message": "主密码" }, "masterPassDesc": { - "message": "主密码是您访问密码库的唯一密码。它非常重要,请您不要忘记。一旦忘记,无任何办法恢复此密码。" + "message": "主密码是用于访问您的密码库的密码。不要忘记您的主密码,这一点非常重要。一旦忘记,无任何办法恢复此密码。" }, "masterPassHintDesc": { "message": "主密码提示可以在您忘记密码时帮您回忆起来。" @@ -80,6 +80,15 @@ "masterPassHint": { "message": "主密码提示(可选)" }, + "passwordStrengthScore": { + "message": "密码强度评分 $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "加入组织" }, @@ -176,6 +185,10 @@ "copyNotes": { "message": "复制备注" }, + "copy": { + "message": "复制", + "description": "Copy to clipboard" + }, "fill": { "message": "填充", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "输入您的账户电子邮箱地址,您的密码提示将发送给您" }, - "passwordHint": { - "message": "密码提示" - }, - "enterEmailToGetHint": { - "message": "请输入您的账户电子邮箱地址来接收主密码提示。" - }, "getMasterPasswordHint": { "message": "获取主密码提示" }, @@ -290,7 +297,7 @@ "message": "前往帮助中心吗?" }, "continueToHelpCenterDesc": { - "message": "访问帮助中心了解更多如何使用 Bitwarden 的信息。" + "message": "在帮助中心进一步了解如何使用 Bitwarden。" }, "continueToBrowserExtensionStore": { "message": "前往浏览器扩展商店吗?" @@ -372,6 +379,15 @@ "editFolder": { "message": "编辑文件夹" }, + "editFolderWithName": { + "message": "编辑文件夹:$FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "新增文件夹" }, @@ -379,13 +395,13 @@ "message": "文件夹名称" }, "folderHintText": { - "message": "通过在父文件夹名后面跟随「/」来嵌套文件夹。示例:Social/Forums" + "message": "通过在父文件夹名后面添加「/」来嵌套文件夹。示例:Social/Forums" }, "noFoldersAdded": { "message": "未添加文件夹" }, "createFoldersToOrganize": { - "message": "创建文件夹以整理你的密码库项目" + "message": "创建文件夹以整理您的密码库项目" }, "deleteFolderPermanently": { "message": "您确定要永久删除这个文件夹吗?" @@ -445,6 +461,18 @@ "generatePassphrase": { "message": "生成密码短语" }, + "passwordGenerated": { + "message": "密码已生成" + }, + "passphraseGenerated": { + "message": "密码短语已生成" + }, + "usernameGenerated": { + "message": "用户名已生成" + }, + "emailGenerated": { + "message": "电子邮箱已生成" + }, "regeneratePassword": { "message": "重新生成密码" }, @@ -454,22 +482,6 @@ "length": { "message": "长度" }, - "uppercase": { - "message": "大写 (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "小写 (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "数字 (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "特殊字符 (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "包含", "description": "Card header for password generator include block" @@ -502,10 +514,6 @@ "message": "包含特殊字符", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "单词个数" }, @@ -644,8 +652,14 @@ "browserNotSupportClipboard": { "message": "您的浏览器不支持剪贴板简单复制,请手动复制。" }, - "verifyIdentity": { - "message": "验证身份" + "verifyYourIdentity": { + "message": "验证您的身份" + }, + "weDontRecognizeThisDevice": { + "message": "我们无法识别这个设备。请输入发送到您电子邮箱中的代码以验证您的身份。" + }, + "continueLoggingIn": { + "message": "继续登录" }, "yourVaultIsLocked": { "message": "您的密码库已锁定。请先验证您的身份。" @@ -748,7 +762,7 @@ "message": "主密码提示" }, "errorOccurred": { - "message": "发生了一个错误" + "message": "发生错误" }, "emailRequired": { "message": "必须填写电子邮箱地址。" @@ -834,7 +848,7 @@ "message": "Bitwarden 可以存储并填充两步验证码。选择相机图标来截取此网站的验证器二维码,或者手动复制并粘贴密钥到此字段。" }, "learnMoreAboutAuthenticators": { - "message": "了解更多关于验证器的信息" + "message": "进一步了解验证器" }, "copyTOTP": { "message": "复制验证器密钥 (TOTP)" @@ -854,14 +868,29 @@ "logInToBitwarden": { "message": "登录到 Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "输入发送到您的电子邮箱的代码" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "输入来自您的验证器 App 的代码" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "按下 YubiKey 以验证身份" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "您的账户要求使用 Duo 两步登录。请按照以下步骤完成登录。" + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "按照以下步骤完成登录。" + }, "restartRegistration": { - "message": "重新开始注册" + "message": "重启注册" }, "expiredLink": { "message": "失效链接" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "请重新注册或尝试登录。" + "message": "请重启注册或尝试登录。" }, "youMayAlreadyHaveAnAccount": { "message": "您可能已经有一个账户了" @@ -875,6 +904,9 @@ "no": { "message": "否" }, + "location": { + "message": "位置" + }, "unexpectedError": { "message": "发生意外错误。" }, @@ -885,7 +917,7 @@ "message": "文件夹已添加" }, "twoStepLoginConfirmation": { - "message": "两步登录要求您从其他设备(例如安全钥匙、验证器 App、短信、电话或者电子邮件)来验证您的登录,这能使您的账户更加安全。两步登录需要在 bitwarden.com 网页版密码库中设置。现在访问此网站吗?" + "message": "两步登录要求您从其他设备(例如安全密钥、验证器 App、短信、电话或者电子邮件)来验证您的登录,这能使您的账户更加安全。两步登录需要在 bitwarden.com 网页版密码库中设置。现在访问此网站吗?" }, "twoStepLoginConfirmationContent": { "message": "在 Bitwarden 网页 App 中设置两步登录,让您的账户更加安全。" @@ -971,7 +1003,7 @@ "message": "搜索类型" }, "noneFolder": { - "message": "无文件夹", + "message": "默认文件夹", "description": "This is the folder for uncategorized items" }, "enableAddLoginNotification": { @@ -984,10 +1016,10 @@ "message": "在密码库中找不到匹配项目时询问添加一个。" }, "addLoginNotificationDescAlt": { - "message": "如果在密码库中找不到项目,询问添加一个。适用于所有已登录的账户。" + "message": "在密码库中找不到匹配项目时询问添加一个。适用于所有已登录的账户。" }, - "showCardsInVaultView": { - "message": "在密码库视图中将支付卡显示为自动填充建议" + "showCardsInVaultViewV2": { + "message": "在密码库视图中将支付卡始终显示为自动填充建议" }, "showCardsCurrentTab": { "message": "在标签页上显示支付卡" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "在标签页上列出支付卡项目,以便于自动填充。" }, - "showIdentitiesInVaultView": { - "message": "在密码库视图中将身份显示为自动填充建议" + "showIdentitiesInVaultViewV2": { + "message": "在密码库视图中将身份始终显示为自动填充建议" }, "showIdentitiesCurrentTab": { "message": "在标签页上显示身份" @@ -1007,6 +1039,9 @@ "clickToAutofillOnVault": { "message": "在密码库视图中点击项目以自动填充" }, + "clickToAutofill": { + "message": "点击自动填充建议中的项目以填充" + }, "clearClipboard": { "message": "清空剪贴板", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." @@ -1021,14 +1056,64 @@ "notificationAddSave": { "message": "保存" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ 已保存到 Bitwarden。", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ 已在 Bitwarden 中更新。", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "保存为新的登录", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "更新登录", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "保存登录吗?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "更新现有的登录吗?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "登录已保存", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "登录已更新", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "保存时出错", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "哦不!我们无法保存它。请尝试手动输入详细信息。", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "询问更新现有的登录" }, "changedPasswordNotificationDesc": { - "message": "在网站上检测到更改时询问更新登录密码。" + "message": "在网站上检测到更改时询问更新登录的密码。" }, "changedPasswordNotificationDescAlt": { - "message": "当在网站上检测到更改时,询问更新登录项目的密码。适用于所有已登录的账户。" + "message": "在网站上检测到更改时询问更新登录的密码。适用于所有已登录的账户。" }, "enableUsePasskeys": { "message": "询问保存和使用通行密钥" @@ -1084,10 +1169,6 @@ "message": "浅色", "description": "Light color" }, - "solarizedDark": { - "message": "过曝暗", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "导出自" }, @@ -1142,7 +1223,7 @@ "message": "每个 Bitwarden 用户账户的账户加密密钥都是唯一的,因此您无法将加密的导出导入到另一个账户。" }, "exportMasterPassword": { - "message": "输入您的主密码以导出你的密码库数据。" + "message": "输入您的主密码以导出您的密码库数据。" }, "shared": { "message": "已共享" @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "购买高级版" }, - "premiumPurchaseAlert": { - "message": "您可以在 bitwarden.com 网页版密码库购买高级会员。现在要访问吗?" - }, "premiumPurchaseAlertV2": { "message": "您可以在 Bitwarden 网页 App 的账户设置中购买高级版。" }, @@ -1278,7 +1356,7 @@ "message": "升级到高级版并接收:" }, "premiumPrice": { - "message": "全部仅需 $PRICE$ /年!", + "message": "所有功能仅需 $PRICE$ /年!", "placeholders": { "price": { "content": "$1", @@ -1287,7 +1365,7 @@ } }, "premiumPriceV2": { - "message": "全部仅需 $PRICE$ 每年!", + "message": "所有功能仅需 $PRICE$ /年!", "placeholders": { "price": { "content": "$1", @@ -1305,7 +1383,7 @@ "message": "如果登录包含验证器密钥,当自动填充此登录时,将 TOTP 验证码复制到剪贴板。" }, "enableAutoBiometricsPrompt": { - "message": "启动时要求生物识别" + "message": "启动时提示生物识别" }, "premiumRequired": { "message": "需要高级会员" @@ -1343,17 +1421,27 @@ "rememberMe": { "message": "记住我" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "30 天内在此设备上不再询问" + }, "sendVerificationCodeEmailAgain": { "message": "再次发送验证码电子邮件" }, "useAnotherTwoStepMethod": { "message": "使用其他两步登录方式" }, + "selectAnotherMethod": { + "message": "选择其他方式", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "使用您的恢复代码" + }, "insertYubiKey": { "message": "将您的 YubiKey 插入计算机的 USB 端口,然后触摸其按钮。" }, "insertU2f": { - "message": "将您的安全钥匙插入计算机的 USB 端口。如果它有按钮,请触摸它。" + "message": "将您的安全密钥插入计算机的 USB 端口。如果它有按钮,请触摸它。" }, "webAuthnNewTab": { "message": "要开始 WebAuthn 2FA 验证,请点击下面的按钮打开一个新标签页,并按照新标签页中提供的说明操作。" @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "打开新标签页" }, + "openInNewTab": { + "message": "在新标签页中打开" + }, "webAuthnAuthenticate": { "message": "验证 WebAuthn" }, + "readSecurityKey": { + "message": "读取安全密钥" + }, + "awaitingSecurityKeyInteraction": { + "message": "等待安全密钥交互..." + }, "loginUnavailable": { "message": "登录不可用" }, @@ -1371,13 +1468,16 @@ "message": "此账户已设置两步登录,但此浏览器不支持任何已配置的两步登录提供程序。" }, "noTwoStepProviders2": { - "message": "请使用支持的网页浏览器(例如 Chrome)和/或添加其他支持更广泛的提供程序(例如验证器 App)。" + "message": "请使用受支持的网页浏览器(例如 Chrome),和/或添加其他跨网页浏览器支持更好的提供程序(例如验证器 App)。" }, "twoStepOptions": { "message": "两步登录选项" }, + "selectTwoStepLoginMethod": { + "message": "选择两步登录方式" + }, "recoveryCodeDesc": { - "message": "无法访问您所有的双重身份提供程序吗?请使用您的恢复代码来停用您账户中所有的双重身份提供程序。" + "message": "无法访问您所有的双重身份提供程序吗?请使用您的恢复代码来关闭您账户中所有的双重身份提供程序。" }, "recoveryCodeTitle": { "message": "恢复代码" @@ -1390,7 +1490,7 @@ "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP 安全钥匙" + "message": "Yubico OTP 安全密钥" }, "yubiKeyDesc": { "message": "使用 YubiKey 来访问您的账户。支持 YubiKey 4、4 Nano、4C 以及 NEO 设备。" @@ -1400,14 +1500,14 @@ "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { - "message": "为您的组织使用 Duo Security 的 Duo 移动 App、短信、电话或 U2F 安全钥匙来进行验证。", + "message": "为您的组织使用 Duo Security 的 Duo 移动 App、短信、电话或 U2F 安全密钥来进行验证。", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, "webAuthnDesc": { - "message": "使用任何 WebAuthn 兼容的安全钥匙访问您的帐户。" + "message": "使用任何 WebAuthn 兼容的安全密钥访问您的账户。" }, "emailTitle": { "message": "电子邮箱" @@ -1418,9 +1518,6 @@ "selfHostedEnvironment": { "message": "自托管环境" }, - "selfHostedEnvironmentFooter": { - "message": "指定您本地托管的 Bitwarden 安装的基础 URL。" - }, "selfHostedBaseUrlHint": { "message": "指定您的本地托管 Bitwarden 安装的基础 URL。例如:https://bitwarden.company.com" }, @@ -1428,14 +1525,11 @@ "message": "对于高级配置,您可以单独指定每个服务的基础 URL。" }, "selfHostedEnvFormInvalid": { - "message": "您必须添加基础服务器 URL 或至少一个自定义环境。" + "message": "您必须添加基础服务器 URL 或至少添加一个自定义环境。" }, "customEnvironment": { "message": "自定义环境" }, - "customEnvironmentFooter": { - "message": "适用于高级用户。您可以分别指定各个服务的基础 URL。" - }, "baseUrl": { "message": "服务器 URL" }, @@ -1514,10 +1608,10 @@ "message": "不完整或不信任的网站可以利用页面加载时的自动填充功能。" }, "learnMoreAboutAutofillOnPageLoadLinkText": { - "message": "了解更多关于风险的信息" + "message": "进一步了解风险" }, "learnMoreAboutAutofill": { - "message": "了解更多关于自动填充的信息" + "message": "进一步了解自动填充" }, "defaultAutoFillOnPageLoad": { "message": "登录项目的默认自动填充设置" @@ -1553,7 +1647,7 @@ "message": "为当前网站自动填充最后一次使用的身份信息" }, "commandGeneratePasswordDesc": { - "message": "生成一个新的随机密码并将其复制到剪贴板中。" + "message": "生成一个新的随机密码并将其复制到剪贴板" }, "commandLockVaultDesc": { "message": "锁定密码库" @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "拖动排序" }, + "dragToReorder": { + "message": "拖动以重新排序" + }, "cfTypeText": { "message": "文本型" }, @@ -1744,7 +1841,7 @@ "message": "州 / 省" }, "zipPostalCode": { - "message": "邮编 / 邮政代码" + "message": "邮政编码" }, "country": { "message": "国家" @@ -1856,7 +1953,7 @@ "message": "检查密码是否已经被公开。" }, "passwordExposed": { - "message": "此密码在泄露数据中已被公开 $VALUE$ 次。请立即修改。", + "message": "此密码在数据泄露中已被暴露 $VALUE$ 次。请立即修改。", "placeholders": { "value": { "content": "$1", @@ -1868,11 +1965,11 @@ "message": "没有在已知的数据泄露中发现此密码,它暂时比较安全。" }, "baseDomain": { - "message": "基础域", + "message": "基础域名", "description": "Domain name. Ex. website.com" }, "baseDomainOptionRecommended": { - "message": "基础域(推荐)", + "message": "基础域名(推荐)", "description": "Domain name. Ex. website.com" }, "domainName": { @@ -1980,10 +2077,10 @@ "description": "ex. A weak password. Scale: Weak -> Good -> Strong" }, "weakMasterPassword": { - "message": "弱主密码" + "message": "脆弱的主密码" }, "weakMasterPasswordDesc": { - "message": "您选择的主密码较弱。您应该使用强密码(或密码短语)来正确保护您的 Bitwarden 账户。仍要使用此主密码吗?" + "message": "您选择的主密码较弱。您应该使用强密码(或密码短语)来正确保护您的 Bitwarden 账户。确定要使用这个主密码吗?" }, "pin": { "message": "PIN 码", @@ -2020,16 +2117,16 @@ "message": "使用主密码解锁" }, "awaitDesktop": { - "message": "等待来自桌面应用程序的确认" + "message": "等待来自桌面端的确认" }, "awaitDesktopDesc": { - "message": "请确认在 Bitwarden 桌面应用程序中设置了生物识别以设置浏览器的生物识别。" + "message": "请确认在 Bitwarden 桌面应用程序中使用了生物识别以设置浏览器的生物识别。" }, "lockWithMasterPassOnRestart": { "message": "浏览器重启后使用主密码锁定" }, "lockWithMasterPassOnRestart1": { - "message": "浏览器重启时需要主密码" + "message": "浏览器重启时要求主密码" }, "selectOneCollection": { "message": "您必须至少选择一个集合。" @@ -2046,6 +2143,9 @@ "usernameGenerator": { "message": "用户名生成器" }, + "useThisEmail": { + "message": "使用此电子邮箱" + }, "useThisPassword": { "message": "使用此密码" }, @@ -2063,12 +2163,24 @@ "message": "创建一个强大且唯一的密码", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "密码库自定义" + }, "vaultTimeoutAction": { "message": "密码库超时动作" }, "vaultTimeoutAction1": { "message": "超时动作" }, + "newCustomizationOptionsCalloutTitle": { + "message": "新的自定义选项" + }, + "newCustomizationOptionsCalloutContent": { + "message": "自定义您的密码库体验,包括快速复制操作、紧凑模式等!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "查看所有外观设置" + }, "lock": { "message": "锁定", "description": "Verb form: to make secure or inaccessible by" @@ -2096,7 +2208,7 @@ "message": "项目已恢复" }, "alreadyHaveAccount": { - "message": "已经拥有账户了吗?" + "message": "已经有账户了吗?" }, "vaultTimeoutLogOutConfirmation": { "message": "超时后注销账户将解除对密码库的所有访问权限,并需要进行在线身份验证。确定使用此设置吗?" @@ -2273,7 +2385,7 @@ "message": "生物识别未设置" }, "biometricsNotEnabledDesc": { - "message": "需要首先在桌面应用程序的设置中设置生物识别才能使用浏览器的生物识别。" + "message": "需要首先在桌面端的设置中设置生物识别,才能使用浏览器的生物识别。" }, "biometricsNotSupportedTitle": { "message": "不支持生物识别" @@ -2318,20 +2430,138 @@ "message": "一个组织策略正影响您的所有权选项。" }, "personalOwnershipPolicyInEffectImports": { - "message": "组织策略已阻止将项目导入您的个人密码库。" + "message": "某个组织策略已阻止将项目导入您的个人密码库。" }, "domainsTitle": { "message": "域名", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "屏蔽域名" + }, + "learnMoreAboutBlockedDomains": { + "message": "进一步了解屏蔽域名" + }, "excludedDomains": { "message": "排除域名" }, "excludedDomainsDesc": { - "message": "Bitwarden 将不会询问是否为这些域名保存登录信息。您必须刷新页面才能使更改生效。" + "message": "Bitwarden 将不会提示保存这些域名的登录信息。您必须刷新页面才能使更改生效。" }, "excludedDomainsDescAlt": { - "message": "Bitwarden 不会询问保存所有已登录的账户的这些域名的登录信息。必须刷新页面才能使更改生效。" + "message": "Bitwarden 将不会提示为所有已登录账户保存这些域名的登录信息。您必须刷新页面才能使更改生效。" + }, + "blockedDomainsDesc": { + "message": "将不会为这些网站提供自动填充和其他相关功能。您必须刷新页面才能使更改生效。" + }, + "autofillBlockedNoticeV2": { + "message": "该网站的自动填充已被屏蔽。" + }, + "autofillBlockedNoticeGuidance": { + "message": "在设置中更改它" + }, + "change": { + "message": "更改" + }, + "changeButtonTitle": { + "message": "更改密码 - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "存在风险的密码" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ 要求您更改 1 个密码,因为它存在风险。", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ 要求您更改 $COUNT$ 个密码,因为它们存在风险。", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "您的组织要求您更改 $COUNT$ 个密码,因为它们存在风险。", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "审查并更改 1 个存在风险的密码" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "审查并更改 $COUNT$ 个存在风险的密码", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "尽快更改有风险的密码" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "更新您的设置,以便您可以快速自动填充密码并生成新的密码" + }, + "reviewAtRiskLogins": { + "message": "审查存在风险的登录" + }, + "reviewAtRiskPasswords": { + "message": "审查存在风险的密码" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "您的组织密码存在风险,因为它们过于简单、被重复使用和/或已暴露。", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "存在风险的登录列表示意图" + }, + "generatePasswordSlideDesc": { + "message": "在存在风险的网站上,使用 Bitwarden 自动填充菜单快速生成强大且唯一的密码。", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Bitwarden 自动填充菜单显示生成的密码示意图" + }, + "updateInBitwarden": { + "message": "在 Bitwarden 中更新" + }, + "updateInBitwardenSlideDesc": { + "message": "然后,Bitwarden 会提示您更新密码管理器中的密码。", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "提示用户更新登录的 Bitwarden 通知示意图" + }, + "turnOnAutofill": { + "message": "开启自动填充" + }, + "turnedOnAutofill": { + "message": "已开启自动填充" + }, + "dismiss": { + "message": "忽略" }, "websiteItemLabel": { "message": "网站 $number$ (URI)", @@ -2351,11 +2581,14 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "屏蔽域名更改已保存" + }, "excludedDomainsSavedSuccess": { "message": "排除域名更改已保存" }, "limitSendViews": { - "message": "限制查看" + "message": "查看次数限制" }, "limitSendViewsHint": { "message": "达到限额后,任何人无法查看此 Send。", @@ -2473,7 +2706,7 @@ "message": "自定义" }, "sendPasswordDescV3": { - "message": "添加一个用于收件人访问此 Send 的可选密码。", + "message": "添加一个用于接收者访问此 Send 的可选密码。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createSend": { @@ -2601,7 +2834,7 @@ "message": "更新主密码" }, "updateMasterPasswordWarning": { - "message": "您的主密码最近被您组织的管理员更改过。要访问密码库,您必须立即更新它。继续操作将使您退出当前会话并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" + "message": "您的主密码最近被您组织的管理员更改过。要访问密码库,您必须立即更新它。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" }, "updateWeakMasterPasswordWarning": { "message": "您的主密码不符合某一项或多项组织策略要求。要访问密码库,必须立即更新您的主密码。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" @@ -2613,7 +2846,7 @@ "message": "自动注册" }, "resetPasswordAutoEnrollInviteWarning": { - "message": "此组织有一个企业策略,将为您自动注册密码重置。注册后将允许组织管理员更改您的主密码。" + "message": "此组织有一个企业策略,将自动为您注册密码重置。注册后将允许组织管理员更改您的主密码。" }, "selectFolder": { "message": "选择文件夹..." @@ -2631,7 +2864,7 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "$TOTAL$ 不足", + "message": "总计 $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -2789,6 +3022,20 @@ "error": { "message": "错误" }, + "decryptionError": { + "message": "解密错误" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden 无法解密下列密码库项目。" + }, + "contactCSToAvoidDataLossPart1": { + "message": "联系客户成功团队", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "以避免额外的数据丢失。", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "生成用户名" }, @@ -2810,7 +3057,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " 使用 $RECOMMENDED$ 或更多个字符生成强大的密码。", + "message": " 使用 $RECOMMENDED$ 个或更多字符生成强大的密码。", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2820,7 +3067,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " 使用 $RECOMMENDED$ 或更多个单词生成强大的密码短语。", + "message": " 使用 $RECOMMENDED$ 个或更多单词生成强大的密码短语。", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2920,6 +3167,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ 拒绝了您的请求。请联系您的服务提供商寻求帮助。", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ 拒绝了您的请求:$ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "无法获取 $SERVICENAME$ 电子邮箱账户 ID。", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2990,7 +3261,7 @@ "message": "组织已停用。" }, "disabledOrganizationFilterError": { - "message": "无法访问已停用组织中的项目。请联系您的组织所有者获取协助。" + "message": "无法访问已停用组织中的项目。请联系您的组织所有者寻求帮助。" }, "loggingInTo": { "message": "正在登录到 $DOMAIN$", @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "设置已编辑" - }, - "environmentEditedClick": { - "message": "点击此处" - }, - "environmentEditedReset": { - "message": "重置为预设设置" - }, "serverVersion": { "message": "服务器版本" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "使用主密码登录" }, - "loggingInAs": { - "message": "正登录为" - }, - "notYou": { - "message": "不是您吗?" - }, "newAroundHere": { "message": "初来乍到吗?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "使用设备登录" }, - "loginWithDeviceEnabledInfo": { - "message": "必须在 Bitwarden App 的设置中启用设备登录。需要其他登录选项吗?" - }, "fingerprintPhraseHeader": { "message": "指纹短语" }, @@ -3070,18 +3323,21 @@ "viewAllLogInOptions": { "message": "查看所有登录选项" }, - "viewAllLoginOptionsV1": { - "message": "查看所有登录选项" - }, "notificationSentDevice": { "message": "通知已发送到您的设备。" }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" + }, + "notificationSentDeviceAnchor": { + "message": "网页 App" + }, + "notificationSentDevicePart2": { + "message": "在批准前,请确保指纹短语与下面的相匹配。" + }, "aNotificationWasSentToYourDevice": { "message": "通知已发送到您的设备" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "确保您的账户已解锁,并且指纹短语与其他设备上的相匹配。" - }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "请求获得批准后,您将收到通知" }, @@ -3091,6 +3347,9 @@ "loginInitiated": { "message": "登录已发起" }, + "logInRequestSent": { + "message": "请求已发送" + }, "exposedMasterPassword": { "message": "已暴露的主密码" }, @@ -3101,7 +3360,7 @@ "message": "主密码弱且曾经暴露" }, "weakAndBreachedMasterPasswordDesc": { - "message": "识别到弱密码且其出现在数据泄露中。请使用一个强且唯一的密码以保护你的账户。确定要使用这个密码吗?" + "message": "识别到弱密码且其出现在数据泄露中。请使用一个强且唯一的密码以保护您的账户。确定要使用这个密码吗?" }, "checkForBreaches": { "message": "检查已知的数据泄露是否包含此密码。" @@ -3205,9 +3464,6 @@ "requestAdminApproval": { "message": "请求管理员批准" }, - "approveWithMasterPassword": { - "message": "使用主密码批准" - }, "ssoIdentifierRequired": { "message": "必须填写组织 SSO 标识符。" }, @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "您的请求已发送给您的管理员。" }, - "youWillBeNotifiedOnceApproved": { - "message": "批准后,您将收到通知。" - }, "troubleLoggingIn": { "message": "登录遇到问题吗?" }, @@ -3290,7 +3543,7 @@ "message": "搜索" }, "inputMinLength": { - "message": "至少输入 $COUNT$ 个字符。", + "message": "输入长度不能低于 $COUNT$ 个字符。", "placeholders": { "count": { "content": "$1", @@ -3375,7 +3628,7 @@ "message": "正在获取选项..." }, "multiSelectNotFound": { - "message": "未找到任何条目" + "message": "未找到任何项目" }, "multiSelectClearAll": { "message": "清除全部" @@ -3396,38 +3649,6 @@ "message": "切换折叠", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "导入您的数据到 Bitwarden 吗?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "保护您的 LastPass 数据并导入到 Bitwarden 吗?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "保存为未加密的文件", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "导入到 Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "导入中...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "数据导入成功!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "导入时出错。检查控制台以获取详细信息。", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "导入过程中遇到网络错误。", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "别名域" }, @@ -3568,7 +3789,7 @@ } }, "tryAgain": { - "message": "请重试" + "message": "重试" }, "verificationRequiredForActionSetPinToContinue": { "message": "此操作需要验证。设置一个 PIN 码以继续。" @@ -3616,7 +3837,7 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "与 Duo 服务连接时出错。请使用不同的两步登录方式或联系 Duo 获取协助。" + "message": "与 Duo 服务连接时出错。请使用其他两步登录方式或联系 Duo 寻求帮助。" }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "启动 DUO 并按照步骤完成登录。" @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "活动账户" }, + "bitwardenAccount": { + "message": "Bitwarden 账户" + }, "availableAccounts": { "message": "可用账户" }, @@ -3880,7 +4104,7 @@ "message": "托管于" }, "useDeviceOrHardwareKey": { - "message": "使用您的设备或实体钥匙" + "message": "使用您的设备或硬件密钥" }, "justOnce": { "message": "仅此一次" @@ -3926,7 +4150,7 @@ "description": "Body content for dialog which asks if the user wants to proceed to the browser's keyboard shortcut settings page" }, "overrideDefaultBrowserAutofillTitle": { - "message": "将 Bitwarden 设置为您的默认密码管理器吗?", + "message": "将 Bitwarden 设置为默认密码管理器吗?", "description": "Dialog title facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutofillDescription": { @@ -3934,7 +4158,7 @@ "description": "Dialog message facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutoFillSettings": { - "message": "将 Bitwarden 设置为您的默认密码管理器", + "message": "将 Bitwarden 设置为默认密码管理器", "description": "Label for the setting that allows overriding the default browser autofill settings" }, "privacyPermissionAdditionNotGrantedTitle": { @@ -3981,8 +4205,11 @@ "autofillSuggestions": { "message": "自动填充建议" }, + "itemSuggestions": { + "message": "建议的项目" + }, "autofillSuggestionsTip": { - "message": "将此站点保存为登录项目以用于自动填充" + "message": "为这个站点保存一个登录项目以自动填充" }, "yourVaultIsEmpty": { "message": "您的密码库是空的" @@ -3991,7 +4218,7 @@ "message": "没有搜索到匹配的项目" }, "clearFiltersOrTryAnother": { - "message": "清除筛选器或尝试另一个搜索词" + "message": "清除筛选或尝试其他搜索词" }, "copyInfoTitle": { "message": "复制信息 - $ITEMNAME$", @@ -4053,6 +4280,20 @@ } } }, + "copyFieldValue": { + "message": "复制 $FIELD$,$VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { "message": "没有要复制的值" }, @@ -4128,15 +4369,6 @@ "itemName": { "message": "项目名称" }, - "cannotRemoveViewOnlyCollections": { - "message": "您无法删除仅具有「查看」权限的集合:$COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "organizationIsDeactivated": { "message": "组织已停用" }, @@ -4148,7 +4380,7 @@ "description": "Used as a label to indicate that the user is the owner of an item." }, "contactYourOrgAdmin": { - "message": "无法访问已停用组织中的项目。请联系您的组织所有者获取协助。" + "message": "无法访问已停用组织中的项目。请联系您的组织所有者寻求帮助。" }, "additionalInformation": { "message": "更多信息" @@ -4208,7 +4440,7 @@ "message": "筛选" }, "filterVault": { - "message": "密码库筛选" + "message": "筛选密码库" }, "filterApplied": { "message": "已应用一个筛选" @@ -4438,6 +4670,9 @@ } } }, + "reorderWebsiteUriButton": { + "message": "重新排序网站 URI。使用箭头键向上或向下移动项目。" + }, "reorderFieldUp": { "message": "$LABEL$ 已上移,位置 $INDEX$ / $LENGTH$", "placeholders": { @@ -4498,15 +4733,6 @@ "nothingSelected": { "message": "您尚未选择任何内容。" }, - "movedItemsToOrg": { - "message": "所选项目已移动到 $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "项目已移动到 $ORGNAME$", "placeholders": { @@ -4557,17 +4783,11 @@ "textSends": { "message": "文本 Send" }, - "bitwardenNewLook": { - "message": "Bitwarden 拥有一个新的外观!" - }, - "bitwardenNewLookDesc": { - "message": "从密码库标签页自动填充和搜索比以往任何时候都更简单直观。来看看吧!" - }, "accountActions": { "message": "账户操作" }, "showNumberOfAutofillSuggestions": { - "message": "在扩展图标上显示自动填充建议的登录的数量" + "message": "在扩展图标上显示自动填充建议的登录数量" }, "showQuickCopyActions": { "message": "在密码库上显示快速复制操作" @@ -4609,7 +4829,7 @@ "message": "自定义超时时间最小为 1 分钟。" }, "additionalContentAvailable": { - "message": "其他内容可用" + "message": "有更多内容可用" }, "fileSavedToDevice": { "message": "文件已保存到设备。可以在设备下载中进行管理。" @@ -4641,6 +4861,33 @@ "noEditPermissions": { "message": "您没有编辑此项目的权限" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "生物识别解锁不可用,因为需要先使用 PIN 或密码解锁。" + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "生物识别解锁当前不可用。" + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "由于系统文件配置错误,生物识别解锁不可用。" + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "由于系统文件配置错误,生物识别解锁不可用。" + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "生物识别解锁不可用,因为 Bitwarden 桌面 App 已关闭。" + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "生物识别解锁不可用,因为在 Bitwarden 桌面 App 中没有为 $EMAIL$ 启用生物识别解锁。", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "由于某个未知的原因,生物识别解锁当前不可用。" + }, "authenticating": { "message": "正在验证" }, @@ -4819,7 +5066,7 @@ "message": "稍后提醒我" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "您能可正常访问您的电子邮箱 $EMAIL$ 吗?", + "message": "您可以正常访问您的电子邮箱 $EMAIL$ 吗?", "placeholders": { "email": { "content": "$1", @@ -4847,5 +5094,50 @@ }, "extraWide": { "message": "超宽" + }, + "sshKeyWrongPassword": { + "message": "您输入的密码不正确。" + }, + "importSshKey": { + "message": "导入" + }, + "confirmSshKeyPassword": { + "message": "确认密码" + }, + "enterSshKeyPasswordDesc": { + "message": "输入 SSH 密钥的密码。" + }, + "enterSshKeyPassword": { + "message": "输入密码" + }, + "invalidSshKey": { + "message": "此 SSH 密钥无效" + }, + "sshKeyTypeUnsupported": { + "message": "不支持此 SSH 密钥类型" + }, + "importSshKeyFromClipboard": { + "message": "从剪贴板导入密钥" + }, + "sshKeyImported": { + "message": "SSH 密钥导入成功" + }, + "cannotRemoveViewOnlyCollections": { + "message": "您无法删除仅具有「查看」权限的集合:$COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "请更新您的桌面应用程序" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "要使用生物识别解锁,请更新您的桌面应用程序,或在桌面设置中禁用指纹解锁。" + }, + "changeAtRiskPassword": { + "message": "更改有风险的密码" } } diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index 4b215df6ba2..37e450c8fd2 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -20,7 +20,7 @@ "message": "建立帳戶" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "第一次使用 Bitwarden?" }, "logInWithPasskey": { "message": "使用密碼金鑰登入" @@ -29,13 +29,13 @@ "message": "使用單一登入" }, "welcomeBack": { - "message": "Welcome back" + "message": "歡迎回來" }, "setAStrongPassword": { "message": "設定一個強密碼" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "設定密碼以完成創建您的帳戶。" + "message": "設定密碼以完成建立您的帳號" }, "enterpriseSingleSignOn": { "message": "企業單一登入" @@ -80,11 +80,20 @@ "masterPassHint": { "message": "主密碼提示(選用)" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "加入組織" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "加入 $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -120,7 +129,7 @@ "message": "複製密碼" }, "copyPassphrase": { - "message": "Copy passphrase" + "message": "複製密碼短語" }, "copyNote": { "message": "複製備註" @@ -153,13 +162,13 @@ "message": "複製駕照號碼" }, "copyPrivateKey": { - "message": "Copy private key" + "message": "複製私密金鑰" }, "copyPublicKey": { - "message": "Copy public key" + "message": "複製公開金鑰" }, "copyFingerprint": { - "message": "Copy fingerprint" + "message": "複製指紋" }, "copyCustomField": { "message": "複製 $FIELD$", @@ -176,8 +185,12 @@ "copyNotes": { "message": "複製備註" }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, "fill": { - "message": "Fill", + "message": "填入", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." }, "autoFill": { @@ -193,10 +206,10 @@ "message": "自動填入身分資訊" }, "fillVerificationCode": { - "message": "Fill verification code" + "message": "填入驗證碼" }, "fillVerificationCodeAria": { - "message": "Fill Verification Code", + "message": "填入驗證碼", "description": "Aria label for the heading displayed the inline menu for totp code autofill" }, "generatePasswordCopied": { @@ -250,12 +263,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "輸入您帳號的電子郵件,您的密碼提示會傳送給您" }, - "passwordHint": { - "message": "密碼提示" - }, - "enterEmailToGetHint": { - "message": "請輸入您的帳户電子郵件地址以接收主密碼提示。" - }, "getMasterPasswordHint": { "message": "取得主密碼提示" }, @@ -337,22 +344,22 @@ "message": "您可以使用 Bitwarden 驗證器儲存驗證器金鑰,並為兩步驟驗證流程產生 TOTP 代碼。前往 bitwarden.com 網站以了解更多資訊。" }, "bitwardenSecretsManager": { - "message": "Bitwarden Secrets Manager" + "message": "Bitwarden 密鑰管理" }, "continueToSecretsManagerPageDesc": { - "message": "Securely store, manage, and share developer secrets with Bitwarden Secrets Manager. Learn more on the bitwarden.com website." + "message": "使用 Bitwarden 密鑰管理來安全儲存、管理並分享開發人員密鑰。在 bitwarden.com 網站上了解更多。" }, "passwordlessDotDev": { "message": "Passwordless.dev" }, "continueToPasswordlessDotDevPageDesc": { - "message": "Create smooth and secure login experiences free from traditional passwords with Passwordless.dev. Learn more on the bitwarden.com website." + "message": "使用 Passwordless.dev 建立流暢且安全的登入體驗,無需使用傳統密碼。在 bitwarden 網站上了解更多。" }, "freeBitwardenFamilies": { - "message": "Free Bitwarden Families" + "message": "免費的 Bitwarden 家庭方案" }, "freeBitwardenFamiliesPageDesc": { - "message": "You are eligible for Free Bitwarden Families. Redeem this offer today in the web app." + "message": "您符合免費 Bitwarden 家庭方案的資格要求。在網頁應用程式中兌換您的優惠。" }, "version": { "message": "版本" @@ -372,6 +379,15 @@ "editFolder": { "message": "編輯資料夾" }, + "editFolderWithName": { + "message": "Edit folder: $FOLDERNAME$", + "placeholders": { + "foldername": { + "content": "$1", + "example": "Social" + } + } + }, "newFolder": { "message": "新增資料夾" }, @@ -379,16 +395,16 @@ "message": "資料夾名稱" }, "folderHintText": { - "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + "message": "在資料夾名稱後面使用「/」來建立樹狀結構。\n例如:社交網路/論壇" }, "noFoldersAdded": { - "message": "No folders added" + "message": "未新增資料夾" }, "createFoldersToOrganize": { - "message": "Create folders to organize your vault items" + "message": "建立資料夾來管理您的密碼庫項目" }, "deleteFolderPermanently": { - "message": "Are you sure you want to permanently delete this folder?" + "message": "您確定要永久刪除此資料夾嗎?" }, "deleteFolder": { "message": "刪除資料夾" @@ -431,7 +447,7 @@ "message": "自動產生安全、唯一的登入密碼。" }, "bitWebVaultApp": { - "message": "Bitwarden web app" + "message": "Bitwarden 網頁應用程式" }, "importItems": { "message": "匯入項目" @@ -443,7 +459,19 @@ "message": "產生密碼" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "產生密碼短語" + }, + "passwordGenerated": { + "message": "已產生密碼" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" }, "regeneratePassword": { "message": "重新產生密碼" @@ -454,28 +482,12 @@ "length": { "message": "長度" }, - "uppercase": { - "message": "大寫 (A-Z)", - "description": "deprecated. Use uppercaseLabel instead." - }, - "lowercase": { - "message": "小寫 (a-z)", - "description": "deprecated. Use lowercaseLabel instead." - }, - "numbers": { - "message": "數字 (0-9)", - "description": "deprecated. Use numbersLabel instead." - }, - "specialCharacters": { - "message": "特殊字元 (!@#$%^&*)", - "description": "deprecated. Use specialCharactersLabel instead." - }, "include": { "message": "包含", "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "包含大寫字元", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -483,7 +495,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "包含小寫字元", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -491,7 +503,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "包含數字", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -499,13 +511,9 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "包含特殊字元", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "單字數量" }, @@ -526,11 +534,11 @@ "message": "最少符號位數" }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "避免易混淆的字元", "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { - "message": "Enterprise policy requirements have been applied to your generator options.", + "message": "企業原則之要求已在你的產生器選項中生效", "description": "Indicates that a policy limits the credential generator screen." }, "searchVault": { @@ -564,19 +572,19 @@ "message": "我的最愛" }, "unfavorite": { - "message": "Unfavorite" + "message": "取消最愛" }, "itemAddedToFavorites": { - "message": "Item added to favorites" + "message": "項目已加入到最愛" }, "itemRemovedFromFavorites": { - "message": "Item removed from favorites" + "message": "項目已從最愛中移除" }, "notes": { "message": "備註" }, "privateNote": { - "message": "Private note" + "message": "私人備註" }, "note": { "message": "備註" @@ -600,7 +608,7 @@ "message": "開啟網站" }, "launchWebsiteName": { - "message": "Launch website $ITEMNAME$", + "message": "開啟網站 $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -644,14 +652,20 @@ "browserNotSupportClipboard": { "message": "您的瀏覽器不支援剪貼簿簡單複製,請手動複製。" }, - "verifyIdentity": { - "message": "驗證身份" + "verifyYourIdentity": { + "message": "Verify your identity" + }, + "weDontRecognizeThisDevice": { + "message": "我們無法識別此裝置。請輸入已傳送到您電子郵件的驗證碼以驗證您的身分。" + }, + "continueLoggingIn": { + "message": "繼續登入" }, "yourVaultIsLocked": { "message": "您的密碼庫已鎖定。請驗證身分以繼續。" }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "您的密碼庫已被鎖定" }, "yourAccountIsLocked": { "message": "您的帳戶已被鎖定。" @@ -742,10 +756,10 @@ "message": "主密碼" }, "masterPassImportant": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "若您忘記主密碼,將會無法找回!" }, "masterPassHintLabel": { - "message": "您已成功創建新帳戶!" + "message": "主密碼提示" }, "errorOccurred": { "message": "發生錯誤" @@ -779,10 +793,10 @@ "message": "帳戶已建立!現在可以登入了。" }, "newAccountCreated2": { - "message": "您已成功創建新帳戶!" + "message": "您已成功建立新帳號!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "你已經登入!" }, "youSuccessfullyLoggedIn": { "message": "登入成功" @@ -797,7 +811,7 @@ "message": "必須填入驗證碼。" }, "webauthnCancelOrTimeout": { - "message": "The authentication was cancelled or took too long. Please try again." + "message": "驗證已被取消或時間超過。請再試一次。" }, "invalidVerificationCode": { "message": "無效的驗證碼" @@ -834,7 +848,7 @@ "message": "Bitwarden 可以儲存並填入兩步驟驗證碼。選擇相機圖示來截取此網站的驗證器QR code,或手動複製金鑰並貼上到此欄位。" }, "learnMoreAboutAuthenticators": { - "message": "Learn more about authenticators" + "message": "了解更多驗證程式" }, "copyTOTP": { "message": "複製驗證器金鑰 (TOTP)" @@ -852,19 +866,34 @@ "message": "登入" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "登入 Bitwarden" + }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." }, "restartRegistration": { - "message": "Restart registration" + "message": "重新啟動註冊" }, "expiredLink": { - "message": "Expired link" + "message": "過期連結" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "請重新啟動註冊流程或是重試登入。" }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "您可能已經有帳號" }, "logOutConfirmation": { "message": "您確定要登出嗎?" @@ -875,6 +904,9 @@ "no": { "message": "否" }, + "location": { + "message": "Location" + }, "unexpectedError": { "message": "發生了未預期的錯誤。" }, @@ -888,7 +920,7 @@ "message": "兩步驟登入需要您從其他裝置(例如安全鑰匙、驗證器程式、SMS、手機或電子郵件)來驗證您的登入,這使您的帳戶更加安全。兩步驟登入可以在 bitwarden.com 網頁版密碼庫啟用。現在要前往嗎?" }, "twoStepLoginConfirmationContent": { - "message": "Make your account more secure by setting up two-step login in the Bitwarden web app." + "message": "在 Bitwarden 網頁應用程式中設定兩步驟登入,讓您的帳號更加安全。" }, "twoStepLoginConfirmationTitle": { "message": "前往網頁 App 嗎?" @@ -934,7 +966,7 @@ "message": "新增 URI" }, "addDomain": { - "message": "Add domain", + "message": "新增網域", "description": "'Domain' here refers to an internet domain name (e.g. 'bitwarden.com') and the message in whole described the act of putting a domain value into the context." }, "addedItem": { @@ -986,8 +1018,8 @@ "addLoginNotificationDescAlt": { "message": "如果在您的密碼庫中找不到項目,則詢問是否新增項目。適用於所有已登入的帳戶。" }, - "showCardsInVaultView": { - "message": "在密碼庫介面中顯示支付卡自動填入建議" + "showCardsInVaultViewV2": { + "message": "一律在密碼庫介面中顯示支付卡自動填入建議" }, "showCardsCurrentTab": { "message": "於分頁頁面顯示支付卡" @@ -995,8 +1027,8 @@ "showCardsCurrentTabDesc": { "message": "於分頁頁面顯示信用卡以便於自動填入。" }, - "showIdentitiesInVaultView": { - "message": "在密碼庫介面中顯示身分自動填入建議" + "showIdentitiesInVaultViewV2": { + "message": "一律在密碼庫介面中顯示身分自動填入建議" }, "showIdentitiesCurrentTab": { "message": "於分頁頁面顯示身分" @@ -1005,7 +1037,10 @@ "message": "於分頁頁面顯示身分以便於自動填入。" }, "clickToAutofillOnVault": { - "message": "Click items to autofill on Vault view" + "message": "在密碼庫檢視中點擊項目來自動填入" + }, + "clickToAutofill": { + "message": "Click items in autofill suggestion to fill" }, "clearClipboard": { "message": "清除剪貼簿", @@ -1021,6 +1056,56 @@ "notificationAddSave": { "message": "儲存" }, + "loginSaveSuccessDetails": { + "message": "$USERNAME$ saved to Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is saved." + }, + "loginUpdatedSuccessDetails": { + "message": "$USERNAME$ updated in Bitwarden.", + "placeholders": { + "username": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." + }, + "saveAsNewLoginAction": { + "message": "Save as new login", + "description": "Button text for saving login details as a new entry." + }, + "updateLoginAction": { + "message": "Update login", + "description": "Button text for updating an existing login entry." + }, + "saveLoginPrompt": { + "message": "Save login?", + "description": "Prompt asking the user if they want to save their login details." + }, + "updateLoginPrompt": { + "message": "Update existing login?", + "description": "Prompt asking the user if they want to update an existing login entry." + }, + "loginSaveSuccess": { + "message": "Login saved", + "description": "Message displayed when login details are successfully saved." + }, + "loginUpdateSuccess": { + "message": "Login updated", + "description": "Message displayed when login details are successfully updated." + }, + "saveFailure": { + "message": "Error saving", + "description": "Error message shown when the system fails to save login details." + }, + "saveFailureDetails": { + "message": "Oh no! We couldn't save this. Try entering the details manually.", + "description": "Detailed error message shown when saving login details fails." + }, "enableChangedPasswordNotification": { "message": "詢問更新現有的登入資料" }, @@ -1084,10 +1169,6 @@ "message": "淺色", "description": "Light color" }, - "solarizedDark": { - "message": "Solarized 深色主題", - "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." - }, "exportFrom": { "message": "匯出自" }, @@ -1262,9 +1343,6 @@ "premiumPurchase": { "message": "升級為進階會員" }, - "premiumPurchaseAlert": { - "message": "您可以在 bitwarden.com 網頁版密碼庫購買進階會員資格。現在要前往嗎?" - }, "premiumPurchaseAlertV2": { "message": "您可以在 Bitwarden 網頁 App 的帳號設定中購買進階版。" }, @@ -1317,10 +1395,10 @@ "message": "輸入驗證器應用程式提供的 6 位數驗證碼。" }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "驗證逾時" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "驗證工作階段因時間過久已逾時。請重試登入。" }, "enterVerificationCodeEmail": { "message": "輸入已傳送至 $EMAIL$ 的 6 位數驗證碼。", @@ -1343,12 +1421,22 @@ "rememberMe": { "message": "記住我" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "再次傳送​​包含驗證碼的電子郵件" }, "useAnotherTwoStepMethod": { "message": "使用另一種兩步驟登入方法" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "將您的 YubiKey 插入電腦的 USB 連接埠,然後按一下它的按鈕。" }, @@ -1361,9 +1449,18 @@ "webAuthnNewTabOpen": { "message": "開啟新分頁" }, + "openInNewTab": { + "message": "Open in new tab" + }, "webAuthnAuthenticate": { "message": "驗證 WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "loginUnavailable": { "message": "登入無法使用" }, @@ -1376,6 +1473,9 @@ "twoStepOptions": { "message": "兩步驟登入選項" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "無法使用任何雙因素提供程式嗎?請使用您的復原碼以停用您帳戶的所有雙因素提供程式。" }, @@ -1386,17 +1486,17 @@ "message": "驗證器應用程式" }, "authenticatorAppDescV2": { - "message": "Enter a code generated by an authenticator app like Bitwarden Authenticator.", + "message": "輸入驗證器應用程式產生的驗證碼,例如 Bitwarden 驗證器。", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP Security Key" + "message": "YubiKey OTP 安全金鑰" }, "yubiKeyDesc": { "message": "使用 YubiKey 存取您的帳戶。支援 YubiKey 4、4 Nano、4C、以及 NEO 裝置。" }, "duoDescV2": { - "message": "Enter a code generated by Duo Security.", + "message": "輸入 Duo 應用程式產生的驗證碼。", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { @@ -1413,34 +1513,28 @@ "message": "電子郵件" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "輸入寄送到您電子郵件信箱的驗證碼。" }, "selfHostedEnvironment": { "message": "自我裝載環境" }, - "selfHostedEnvironmentFooter": { - "message": "指定您內部部署的 Bitwarden 安裝之基礎 URL。" - }, "selfHostedBaseUrlHint": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" + "message": "指定您自建的 Bitwarden 伺服器的網域 URL。例如:https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { - "message": "For advanced configuration, you can specify the base URL of each service independently." + "message": "適用於進階設定。您可以單獨指定各個服務的網域 URL。" }, "selfHostedEnvFormInvalid": { - "message": "You must add either the base Server URL or at least one custom environment." + "message": "您必須新增伺服器網域 URL 或至少一個自定義環境。" }, "customEnvironment": { "message": "自訂環境" }, - "customEnvironmentFooter": { - "message": "適用於進階使用者。您可以單獨指定各個服務的基礎 URL。" - }, "baseUrl": { "message": "伺服器 URL" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "自建伺服器 URL", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -1466,22 +1560,22 @@ "description": "Represents the message for allowing the user to enable the autofill overlay" }, "autofillSuggestionsSectionTitle": { - "message": "Autofill suggestions" + "message": "自動填入建議" }, "showInlineMenuLabel": { - "message": "Show autofill suggestions on form fields" + "message": "在表單欄位上顯示自動填入選單" }, "showInlineMenuIdentitiesLabel": { - "message": "Display identities as suggestions" + "message": "顯示身分建議" }, "showInlineMenuCardsLabel": { - "message": "Display cards as suggestions" + "message": "顯示信用卡建議" }, "showInlineMenuOnIconSelectionLabel": { - "message": "Display suggestions when icon is selected" + "message": "當選擇圖示時,顯示建議" }, "showInlineMenuOnFormFieldsDescAlt": { - "message": "Applies to all logged in accounts." + "message": "適用於所有已登入的帳戶。" }, "turnOffBrowserBuiltInPasswordManagerSettings": { "message": "關閉你的瀏覽器內建密碼管理器設定以避免衝突。" @@ -1502,7 +1596,7 @@ "description": "Overlay appearance select option for showing the field on click of the overlay icon" }, "enableAutoFillOnPageLoadSectionTitle": { - "message": "Autofill on page load" + "message": "頁面載入時自動填入" }, "enableAutoFillOnPageLoad": { "message": "頁面載入時自動填入" @@ -1514,7 +1608,7 @@ "message": "被入侵或不被信任的網站,可能會濫用頁面載入的自動填入功能。" }, "learnMoreAboutAutofillOnPageLoadLinkText": { - "message": "Learn more about risks" + "message": "了解更多風險" }, "learnMoreAboutAutofill": { "message": "進一步瞭解「自動填入」功能" @@ -1544,13 +1638,13 @@ "message": "在側邊欄中開啟密碼庫" }, "commandAutofillLoginDesc": { - "message": "Autofill the last used login for the current website" + "message": "自動將上次使用的登入資料填入目前網站" }, "commandAutofillCardDesc": { - "message": "Autofill the last used card for the current website" + "message": "自動將上次使用的信用卡填入目前網站" }, "commandAutofillIdentityDesc": { - "message": "Autofill the last used identity for the current website" + "message": "自動將上次使用的身分資料填入目前網站" }, "commandGeneratePasswordDesc": { "message": "產生一組新的隨機密碼並將它複製到剪貼簿中。" @@ -1573,6 +1667,9 @@ "dragToSort": { "message": "透過拖曳來排序" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "文字型" }, @@ -1768,7 +1865,7 @@ "message": "身分" }, "typeSshKey": { - "message": "SSH key" + "message": "SSH 金鑰" }, "newItemHeader": { "message": "新增 $TYPE$", @@ -1801,13 +1898,13 @@ "message": "密碼歷史記錄" }, "generatorHistory": { - "message": "Generator history" + "message": "產生器歷史記錄" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "清除產生器歷史記錄" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "若繼續,所有產生器曾經產生的記錄會被刪除。您確定要繼續?" }, "back": { "message": "返回" @@ -1846,7 +1943,7 @@ "message": "安全筆記" }, "sshKeys": { - "message": "SSH Keys" + "message": "SSH 金鑰" }, "clear": { "message": "清除", @@ -1929,10 +2026,10 @@ "message": "清除歷史紀錄" }, "nothingToShow": { - "message": "Nothing to show" + "message": "沒有可顯示的內容" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "您最近未產生任何密碼" }, "remove": { "message": "移除" @@ -2002,7 +2099,7 @@ "message": "設定您用來解鎖 Bitwarden 的 PIN 碼。您的 PIN 設定將在您完全登出本應用程式時被重設。" }, "setYourPinCode1": { - "message": "Your PIN will be used to unlock Bitwarden instead of your master password. Your PIN will reset if you ever fully log out of Bitwarden." + "message": "您的 PIN 碼會取代主密碼用來解鎖 Bitwarden。您的 PIN 碼會重置,若您完全登出 Bitwarden。" }, "pinRequired": { "message": "必須填入 PIN 碼。" @@ -2017,7 +2114,7 @@ "message": "使用生物特徵辨識解鎖" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "使用主密碼解鎖" }, "awaitDesktop": { "message": "等待來自桌面應用程式的確認" @@ -2029,7 +2126,7 @@ "message": "瀏覽器重啟後使用主密碼鎖定" }, "lockWithMasterPassOnRestart1": { - "message": "Require master password on browser restart" + "message": "瀏覽器重啟後使用主密碼鎖定" }, "selectOneCollection": { "message": "您必須至少選擇一個集合。" @@ -2041,34 +2138,49 @@ "message": "克隆" }, "passwordGenerator": { - "message": "Password generator" + "message": "密碼產生器" }, "usernameGenerator": { - "message": "Username generator" + "message": "使用者名稱產生器" + }, + "useThisEmail": { + "message": "使用此電子郵件" }, "useThisPassword": { - "message": "Use this password" + "message": "使用此密碼" }, "useThisUsername": { - "message": "Use this username" + "message": "使用此使用者名稱" }, "securePasswordGenerated": { - "message": "Secure password generated! Don't forget to also update your password on the website." + "message": "已產生安全的密碼!請不要忘記同時更新您網站上的密碼。" }, "useGeneratorHelpTextPartOne": { - "message": "Use the generator", + "message": "使用產生器", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "useGeneratorHelpTextPartTwo": { - "message": "to create a strong unique password", + "message": "來產生高強度且唯一的密碼", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, + "vaultCustomization": { + "message": "Vault customization" + }, "vaultTimeoutAction": { "message": "密碼庫逾時動作" }, "vaultTimeoutAction1": { "message": "逾時後動作" }, + "newCustomizationOptionsCalloutTitle": { + "message": "New customization options" + }, + "newCustomizationOptionsCalloutContent": { + "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + }, + "newCustomizationOptionsCalloutLink": { + "message": "View all Appearance settings" + }, "lock": { "message": "鎖定", "description": "Verb form: to make secure or inaccessible by" @@ -2096,7 +2208,7 @@ "message": "項目已還原" }, "alreadyHaveAccount": { - "message": "Already have an account?" + "message": "已經有帳號了嗎?" }, "vaultTimeoutLogOutConfirmation": { "message": "選擇登出將會在密碼庫逾時後移除對密碼庫的所有存取權限,若要重新驗證則需連線網路。確定要使用此設定嗎?" @@ -2108,7 +2220,7 @@ "message": "自動填入並儲存" }, "fillAndSave": { - "message": "Fill and save" + "message": "填入並儲存" }, "autoFillSuccessAndSavedUri": { "message": "項目已自動填入並且已儲存統一資源標識符(URI)" @@ -2189,19 +2301,19 @@ "message": "您新的主密碼不符合原則要求。" }, "receiveMarketingEmailsV2": { - "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." + "message": "獲得來自 Bitwarden 的公告、建議及研究資訊電子郵件。" }, "unsubscribe": { - "message": "Unsubscribe" + "message": "取消訂閱" }, "atAnyTime": { - "message": "at any time." + "message": "在任何時間。" }, "byContinuingYouAgreeToThe": { - "message": "By continuing, you agree to the" + "message": "若是繼續,則代表您同意" }, "and": { - "message": "and" + "message": "和" }, "acceptPolicies": { "message": "選中此選取框,即表示您同意下列條款:" @@ -2222,10 +2334,10 @@ "message": "確定" }, "errorRefreshingAccessToken": { - "message": "Access Token Refresh Error" + "message": "存取權杖更新失敗" }, "errorRefreshingAccessTokenDesc": { - "message": "No refresh token or API keys found. Please try logging out and logging back in." + "message": "未找到存取權杖或 API 密鑰。請重試登出再登入。" }, "desktopSyncVerificationTitle": { "message": "桌面同步驗證" @@ -2264,10 +2376,10 @@ "message": "帳戶不相符" }, "nativeMessagingWrongUserKeyTitle": { - "message": "Biometric key missmatch" + "message": "生物辨識金鑰錯誤" }, "nativeMessagingWrongUserKeyDesc": { - "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + "message": "生物辨識解鎖失敗。生物辨識金鑰解鎖密碼庫失敗。請嘗試重新設定生物辨識。" }, "biometricsNotEnabledTitle": { "message": "生物特徵辨識未設定" @@ -2282,16 +2394,16 @@ "message": "此裝置不支援瀏覽器生物特徵辨識。" }, "biometricsNotUnlockedTitle": { - "message": "User locked or logged out" + "message": "使用者已鎖定或登出" }, "biometricsNotUnlockedDesc": { - "message": "Please unlock this user in the desktop application and try again." + "message": "請在桌面應用程式解鎖此使用者之後再重試。" }, "biometricsNotAvailableTitle": { "message": "生物辨識解鎖不可用" }, "biometricsNotAvailableDesc": { - "message": "Biometric unlock is currently unavailable. Please try again later." + "message": "生物辨識解鎖現在無法使用。請稍後重試。" }, "biometricsFailedTitle": { "message": "生物特徵辨識失敗" @@ -2324,6 +2436,12 @@ "message": "網域", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "已封鎖的網域" + }, + "learnMoreAboutBlockedDomains": { + "message": "Learn more about blocked domains" + }, "excludedDomains": { "message": "排除網域" }, @@ -2333,6 +2451,118 @@ "excludedDomainsDescAlt": { "message": "對於所有已登入的帳戶,Bitwarden 不會詢問是否儲存這些網域的登入資訊。您必須重新整理頁面變更才會生效。" }, + "blockedDomainsDesc": { + "message": "自動填入及其它相關的功能無法在這些網站上使用。您必須重新整理頁面來使變更生效。" + }, + "autofillBlockedNoticeV2": { + "message": "自動填入已於此網站封鎖" + }, + "autofillBlockedNoticeGuidance": { + "message": "您可以於設定中進行更改" + }, + "change": { + "message": "Change" + }, + "changeButtonTitle": { + "message": "Change password - $ITEMNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "atRiskPasswords": { + "message": "At-risk passwords" + }, + "atRiskPasswordDescSingleOrg": { + "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + } + }, + "atRiskPasswordsDescSingleOrgPlural": { + "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + }, + "count": { + "content": "$2", + "example": "2" + } + } + }, + "atRiskPasswordsDescMultiOrgPlural": { + "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "reviewAndChangeAtRiskPassword": { + "message": "Review and change one at-risk password" + }, + "reviewAndChangeAtRiskPasswordsPlural": { + "message": "Review and change $COUNT$ at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, + "changeAtRiskPasswordsFaster": { + "message": "Change at-risk passwords faster" + }, + "changeAtRiskPasswordsFasterDesc": { + "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + }, + "reviewAtRiskLogins": { + "message": "Review at-risk logins" + }, + "reviewAtRiskPasswords": { + "message": "Review at-risk passwords" + }, + "reviewAtRiskLoginsSlideDesc": { + "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "description": "Description of the review at-risk login slide on the at-risk password page carousel" + }, + "reviewAtRiskLoginSlideImgAlt": { + "message": "Illustration of a list of logins that are at-risk" + }, + "generatePasswordSlideDesc": { + "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "description": "Description of the generate password slide on the at-risk password page carousel" + }, + "generatePasswordSlideImgAlt": { + "message": "Illustration of the Bitwarden autofill menu displaying a generated password" + }, + "updateInBitwarden": { + "message": "Update in Bitwarden" + }, + "updateInBitwardenSlideDesc": { + "message": "Bitwarden will then prompt you to update the password in the password manager.", + "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" + }, + "updateInBitwardenSlideImgAlt": { + "message": "Illustration of a Bitwarden’s notification prompting the user to update the login" + }, + "turnOnAutofill": { + "message": "Turn on autofill" + }, + "turnedOnAutofill": { + "message": "Turned on autofill" + }, + "dismiss": { + "message": "Dismiss" + }, "websiteItemLabel": { "message": "網站 $number$ (URI)", "placeholders": { @@ -2351,6 +2581,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "已儲存封鎖的網域" + }, "excludedDomainsSavedSuccess": { "message": "例外網域更改已儲存" }, @@ -2496,7 +2729,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSendSuccessfully": { - "message": "Send 創建成功!", + "message": "Send 建立成功!", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendExpiresInHoursSingle": { @@ -2789,6 +3022,20 @@ "error": { "message": "錯誤" }, + "decryptionError": { + "message": "解密發生錯誤" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden 無法解密您密碼庫中下面的項目。" + }, + "contactCSToAvoidDataLossPart1": { + "message": "聯絡客戶支援部門", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "來避免更多資料遺失。", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "產生使用者名稱" }, @@ -2887,7 +3134,7 @@ "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "網站:$WEBSITE$。透過 Bitwarden 產生。", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -2897,7 +3144,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "無效的 $SERVICENAME$ API 權杖", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -2907,7 +3154,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "無效的 $SERVICENAME$ API 權杖:$ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -2920,8 +3167,32 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { - "message": "Unable to obtain $SERVICENAME$ masked email account ID.", + "message": "無法獲得 $SERVICENAME$ 的轉送電子郵件帳號。", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -2931,7 +3202,7 @@ } }, "forwarderNoDomain": { - "message": "Invalid $SERVICENAME$ domain.", + "message": "無效的 $SERVICENAME$ 網域。", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -2941,7 +3212,7 @@ } }, "forwarderNoUrl": { - "message": "Invalid $SERVICENAME$ url.", + "message": "無效的 $SERVICENAME$ URI。", "description": "Displayed when the url of the forwarding service wasn't supplied.", "placeholders": { "servicename": { @@ -2951,7 +3222,7 @@ } }, "forwarderUnknownError": { - "message": "Unknown $SERVICENAME$ error occurred.", + "message": "發生未知的 $SERVICENAME$ 錯誤。", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -2961,7 +3232,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Unknown forwarder: '$SERVICENAME$'.", + "message": "未知的轉送服務提供商:$SERVICENAME$。", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -3001,15 +3272,6 @@ } } }, - "settingsEdited": { - "message": "設定已編輯" - }, - "environmentEditedClick": { - "message": "點選此處" - }, - "environmentEditedReset": { - "message": "重設為預設設定" - }, "serverVersion": { "message": "伺服器版本" }, @@ -3040,12 +3302,6 @@ "loginWithMasterPassword": { "message": "使用主密碼登入" }, - "loggingInAs": { - "message": "正登入為" - }, - "notYou": { - "message": "不是您嗎?" - }, "newAroundHere": { "message": "第一次使用?" }, @@ -3055,9 +3311,6 @@ "loginWithDevice": { "message": "使用裝置登入" }, - "loginWithDeviceEnabledInfo": { - "message": "裝置登入必須在 Bitwarden 應用程式的設定中啟用。需要其他選項嗎?" - }, "fingerprintPhraseHeader": { "message": "指紋短語" }, @@ -3068,29 +3321,35 @@ "message": "重新傳送通知" }, "viewAllLogInOptions": { - "message": "View all log in options" - }, - "viewAllLoginOptionsV1": { - "message": "View all log in options" + "message": "檢視所有登入選項" }, "notificationSentDevice": { "message": "已傳送通知至您的裝置。" }, - "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDeviceAnchor": { + "message": "網頁應用程式" + }, + "notificationSentDevicePart2": { + "message": "在核准前請確保您的指紋短語與下面完全相符。" + }, + "aNotificationWasSentToYourDevice": { + "message": "已傳送通知至您的裝置" }, "youWillBeNotifiedOnceTheRequestIsApproved": { - "message": "You will be notified once the request is approved" + "message": "一旦您的請求被通過,您會獲得通知。" }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "需要另一個選項嗎?" }, "loginInitiated": { "message": "登入已啟動" }, + "logInRequestSent": { + "message": "已傳送請求" + }, "exposedMasterPassword": { "message": "已洩露的主密碼" }, @@ -3146,22 +3405,22 @@ "message": "自動填入設定" }, "autofillKeyboardShortcutSectionTitle": { - "message": "Autofill shortcut" + "message": "自動填入快速鍵" }, "autofillKeyboardShortcutUpdateLabel": { - "message": "Change shortcut" + "message": "修改鍵盤快速鍵" }, "autofillKeyboardManagerShortcutsLabel": { - "message": "Manage shortcuts" + "message": "管理鍵盤快速鍵" }, "autofillShortcut": { "message": "自動填入鍵盤快速鍵" }, "autofillLoginShortcutNotSet": { - "message": "The autofill login shortcut is not set. Change this in the browser's settings." + "message": "自動填入快速鍵尚未設定。請在瀏覽器的設定中變更。" }, "autofillLoginShortcutText": { - "message": "The autofill login shortcut is $COMMAND$. Manage all shortcuts in the browser's settings.", + "message": "自動填入快速鍵是 $COMMAND$。請在瀏覽器的設定中變更。", "placeholders": { "command": { "content": "$1", @@ -3182,16 +3441,16 @@ "message": "在新視窗開啟" }, "rememberThisDeviceToMakeFutureLoginsSeamless": { - "message": "Remember this device to make future logins seamless" + "message": "記住此裝置來讓未來的登入體驗更簡易" }, "deviceApprovalRequired": { "message": "裝置需要取得核准。請在下面選擇一個核准選項:" }, "deviceApprovalRequiredV2": { - "message": "Device approval required" + "message": "需要核准裝置" }, "selectAnApprovalOptionBelow": { - "message": "Select an approval option below" + "message": "選擇下面的一個核准選項" }, "rememberThisDevice": { "message": "記住這個裝置" @@ -3205,32 +3464,29 @@ "requestAdminApproval": { "message": "要求管理員核准" }, - "approveWithMasterPassword": { - "message": "使用主密碼核准" - }, "ssoIdentifierRequired": { "message": "需要組織 SSO 識別碼。" }, "creatingAccountOn": { - "message": "Creating account on" + "message": "建立帳號於" }, "checkYourEmail": { - "message": "Check your email" + "message": "檢查您的電子郵件" }, "followTheLinkInTheEmailSentTo": { - "message": "Follow the link in the email sent to" + "message": "跟隨電子郵件中的連結" }, "andContinueCreatingYourAccount": { - "message": "and continue creating your account." + "message": "並繼續建立您的帳號" }, "noEmail": { - "message": "No email?" + "message": "沒有電子郵件?" }, "goBack": { - "message": "Go back" + "message": "返回" }, "toEditYourEmailAddress": { - "message": "to edit your email address." + "message": "來編輯您的電子郵件位址。" }, "eu": { "message": "歐盟", @@ -3254,9 +3510,6 @@ "adminApprovalRequestSentToAdmins": { "message": "您的要求已傳送給您的管理員。" }, - "youWillBeNotifiedOnceApproved": { - "message": "核准後將通知您。" - }, "troubleLoggingIn": { "message": "登入時遇到困難?" }, @@ -3267,17 +3520,17 @@ "message": "缺少使用者電子郵件地址" }, "activeUserEmailNotFoundLoggingYouOut": { - "message": "Active user email not found. Logging you out." + "message": "未找到使用中帳號的電子郵件。正在將您登出。" }, "deviceTrusted": { "message": "裝置已信任" }, "sendsNoItemsTitle": { - "message": "No active Sends", + "message": "沒有可用的 Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "message": "使用 Send 可以與任何人安全地共用加密資訊。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -3354,10 +3607,10 @@ } }, "singleFieldNeedsAttention": { - "message": "1 field needs your attention." + "message": "您需注意上方的 1 個欄位。" }, "multipleFieldsNeedAttention": { - "message": "$COUNT$ fields need your attention.", + "message": "您需注意上方的 $COUNT$ 個欄位。", "placeholders": { "count": { "content": "$1", @@ -3396,38 +3649,6 @@ "message": "切換至折疊狀態", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "匯入你的資料至 Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "保護你的 LastPass 資料並匯入至 Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "儲存為未加密的檔案", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "匯入至 Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "匯入中……", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "資料匯入成功!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "匯入時發生錯誤。檢查控制台以了解詳細資訊。", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "匯入時遇到網路錯誤", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "別名網域" }, @@ -3444,7 +3665,7 @@ "description": "Message appearing below the autofill on load message when master password reprompt is set for a vault item." }, "toggleSideNavigation": { - "message": "Toggle side navigation" + "message": "切換側邊欄" }, "skipToContent": { "message": "跳至內容" @@ -3466,7 +3687,7 @@ "description": "Text to display in overlay when the account is locked." }, "unlockYourAccountToViewAutofillSuggestions": { - "message": "Unlock your account to view autofill suggestions", + "message": "解鎖您的帳號來查看建議的自動填入", "description": "Text to display in overlay when the account is locked." }, "unlockAccount": { @@ -3474,15 +3695,15 @@ "description": "Button text to display in overlay when the account is locked." }, "unlockAccountAria": { - "message": "Unlock your account, opens in a new window", + "message": "解鎖您的帳號,並開啟在新視窗", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, "totpCodeAria": { - "message": "Time-based One-Time Password Verification Code", + "message": "基於時間的一次性驗證碼", "description": "Aria label for the totp code displayed in the inline menu for autofill" }, "totpSecondsSpanAria": { - "message": "Time remaining before current TOTP expires", + "message": "現在的 TOTP 到期剩餘時間", "description": "Aria label for the totp seconds displayed in the inline menu for autofill" }, "fillCredentialsFor": { @@ -3510,23 +3731,23 @@ "description": "Button text to display within inline menu when there are no matching items on a login field" }, "addNewLoginItemAria": { - "message": "Add new vault login item, opens in a new window", + "message": "新增新的密碼庫登入項目,在新視窗中顯示", "description": "Screen reader text (aria-label) for new login button within inline menu" }, "newCard": { - "message": "New card", + "message": "新信用卡", "description": "Button text to display within inline menu when there are no matching items on a credit card field" }, "addNewCardItemAria": { - "message": "Add new vault card item, opens in a new window", + "message": "新增新的密碼庫信用卡項目,在新視窗中顯示", "description": "Screen reader text (aria-label) for new card button within inline menu" }, "newIdentity": { - "message": "New identity", + "message": "新身份識別", "description": "Button text to display within inline menu when there are no matching items on an identity field" }, "addNewIdentityItemAria": { - "message": "Add new vault identity item, opens in a new window", + "message": "新增新的密碼庫身分項目,在新視窗中顯示", "description": "Screen reader text (aria-label) for new identity button within inline menu" }, "bitwardenOverlayMenuAvailable": { @@ -3616,19 +3837,19 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + "message": "連接到 Duo 服務時發生錯誤。使用不同的兩階段認證或聯繫 Duo 來獲得支援。" }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "啟動 Duo 並依照步驟完成登入。" }, "duoRequiredForAccount": { - "message": "Duo two-step login is required for your account." + "message": "您的帳號要求使用 Duo 兩步驟驗證登入。" }, "popoutTheExtensionToCompleteLogin": { - "message": "Popout the extension to complete login." + "message": "彈出擴充套件視窗來完成登入。" }, "popoutExtension": { - "message": "Popout extension" + "message": "彈出擴充套件視窗" }, "launchDuo": { "message": "開啟Duo" @@ -3646,7 +3867,7 @@ "message": "檔案密碼無效,請使用您當初匯出檔案時輸入的密碼。" }, "destination": { - "message": "Destination" + "message": "目的" }, "learnAboutImportOptions": { "message": "瞭解更多匯入選項" @@ -3705,16 +3926,16 @@ "message": "確認檔案密碼" }, "exportSuccess": { - "message": "Vault data exported" + "message": "密碼庫資料已匯出" }, "typePasskey": { "message": "密碼金鑰" }, "accessing": { - "message": "Accessing" + "message": "正在存取" }, "loggedInExclamation": { - "message": "Logged in!" + "message": "已登入!" }, "passkeyNotCopied": { "message": "密碼金鑰不會被複製" @@ -3738,7 +3959,7 @@ "message": "您沒有符合該網站的登入資訊。" }, "noMatchingLoginsForSite": { - "message": "No matching logins for this site" + "message": "未找到此網站的登入資訊" }, "searchSavePasskeyNewLogin": { "message": "搜尋或將密碼金鑰儲存為新的登入資訊" @@ -3858,6 +4079,9 @@ "activeAccount": { "message": "目前帳戶" }, + "bitwardenAccount": { + "message": "Bitwarden account" + }, "availableAccounts": { "message": "可用帳戶" }, @@ -3877,19 +4101,19 @@ "message": "伺服器" }, "hostedAt": { - "message": "hosted at" + "message": "架設在" }, "useDeviceOrHardwareKey": { - "message": "Use your device or hardware key" + "message": "使用您的裝置或密碼金鑰" }, "justOnce": { "message": "僅此一次" }, "alwaysForThisSite": { - "message": "Always for this site" + "message": "永遠針對此網站" }, "domainAddedToExcludedDomains": { - "message": "$DOMAIN$ added to excluded domains.", + "message": "$DOMAIN$ 已新增到排除的網域。", "placeholders": { "domain": { "content": "$1", @@ -3898,51 +4122,51 @@ } }, "commonImportFormats": { - "message": "Common formats", + "message": "常見格式", "description": "Label indicating the most common import formats" }, "confirmContinueToBrowserSettingsTitle": { - "message": "Continue to browser settings?", + "message": "繼續前往瀏覽器設定?", "description": "Title for dialog which asks if the user wants to proceed to a relevant browser settings page" }, "confirmContinueToHelpCenter": { - "message": "Continue to Help Center?", + "message": "繼續前往說明中心?", "description": "Title for dialog which asks if the user wants to proceed to a relevant Help Center page" }, "confirmContinueToHelpCenterPasswordManagementContent": { - "message": "Change your browser's autofill and password management settings.", + "message": "在您瀏覽器的偏好設定中更改自動填入及密碼管理。", "description": "Body content for dialog which asks if the user wants to proceed to the Help Center's page about browser password management settings" }, "confirmContinueToHelpCenterKeyboardShortcutsContent": { - "message": "You can view and set extension shortcuts in your browser's settings.", + "message": "您可以在您瀏覽器的偏好設定中檢視及設定擴充套件的快速鍵。", "description": "Body content for dialog which asks if the user wants to proceed to the Help Center's page about browser keyboard shortcut settings" }, "confirmContinueToBrowserPasswordManagementSettingsContent": { - "message": "Change your browser's autofill and password management settings.", + "message": "在您瀏覽器的偏好設定中更改自動填入及密碼管理。", "description": "Body content for dialog which asks if the user wants to proceed to the browser's password management settings page" }, "confirmContinueToBrowserKeyboardShortcutSettingsContent": { - "message": "You can view and set extension shortcuts in your browser's settings.", + "message": "您可以在您瀏覽器的偏好設定中檢視及設定擴充套件的快速鍵。", "description": "Body content for dialog which asks if the user wants to proceed to the browser's keyboard shortcut settings page" }, "overrideDefaultBrowserAutofillTitle": { - "message": "Make Bitwarden your default password manager?", + "message": "使用 Bitwarden 作為預設的密碼管理器?", "description": "Dialog title facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutofillDescription": { - "message": "Ignoring this option may cause conflicts between Bitwarden autofill suggestions and your browser's.", + "message": "忽略此設定可能會導致 Bitwarden 自動填入選單與您的瀏覽器產生衝突。", "description": "Dialog message facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutoFillSettings": { - "message": "Make Bitwarden your default password manager", + "message": "使用 Bitwarden 作為預設的密碼管理器", "description": "Label for the setting that allows overriding the default browser autofill settings" }, "privacyPermissionAdditionNotGrantedTitle": { - "message": "Unable to set Bitwarden as the default password manager", + "message": "無法設定 Bitwarden 作為預設的密碼管理器", "description": "Title for the dialog that appears when the user has not granted the extension permission to set privacy settings" }, "privacyPermissionAdditionNotGrantedDescription": { - "message": "You must grant browser privacy permissions to Bitwarden to set it as the default password manager.", + "message": "您必須同意您瀏覽器的隱私權限設定來設定 Bitwarden 為預設的密碼管理器。", "description": "Description for the dialog that appears when the user has not granted the extension permission to set privacy settings" }, "makeDefault": { @@ -3950,19 +4174,19 @@ "description": "Button text for the setting that allows overriding the default browser autofill settings" }, "saveCipherAttemptSuccess": { - "message": "Credentials saved successfully!", + "message": "憑證資訊成功儲存!", "description": "Notification message for when saving credentials has succeeded." }, "passwordSaved": { - "message": "Password saved!", + "message": "密碼已儲存!", "description": "Notification message for when saving credentials has succeeded." }, "updateCipherAttemptSuccess": { - "message": "Credentials updated successfully!", + "message": "憑證資訊成功更新!", "description": "Notification message for when updating credentials has succeeded." }, "passwordUpdated": { - "message": "Password updated!", + "message": "密碼已更新!", "description": "Notification message for when updating credentials has succeeded." }, "saveCipherAttemptFailed": { @@ -3970,7 +4194,7 @@ "description": "Notification message for when saving credentials has failed." }, "success": { - "message": "Success" + "message": "成功" }, "removePasskey": { "message": "移除密碼金鑰" @@ -3979,19 +4203,22 @@ "message": "密碼金鑰已移除" }, "autofillSuggestions": { - "message": "Autofill suggestions" + "message": "自動填入建議" + }, + "itemSuggestions": { + "message": "建議項目" }, "autofillSuggestionsTip": { - "message": "Save a login item for this site to autofill" + "message": "對此網站儲存登入項目為自動填入" }, "yourVaultIsEmpty": { - "message": "Your vault is empty" + "message": "您的密碼庫是空的" }, "noItemsMatchSearch": { - "message": "No items match your search" + "message": "沒有找到相符的項目。" }, "clearFiltersOrTryAnother": { - "message": "Clear filters or try another search term" + "message": "清除過濾器或更換另一個搜尋條件" }, "copyInfoTitle": { "message": "複製資訊 - $ITEMNAME$", @@ -4004,7 +4231,7 @@ } }, "copyNoteTitle": { - "message": "Copy Note - $ITEMNAME$", + "message": "複製備註 - $ITEMNAME$", "description": "Title for a button copies a note to the clipboard.", "placeholders": { "itemname": { @@ -4034,7 +4261,7 @@ } }, "viewItemTitle": { - "message": "View item - $ITEMNAME$", + "message": "檢視項目 - $ITEMNAME$", "description": "Title for a link that opens a view for an item.", "placeholders": { "itemname": { @@ -4044,7 +4271,7 @@ } }, "autofillTitle": { - "message": "Autofill - $ITEMNAME$", + "message": "自動填入 - $ITEMNAME$", "description": "Title for a button that autofills a login item.", "placeholders": { "itemname": { @@ -4053,23 +4280,37 @@ } } }, + "copyFieldValue": { + "message": "Copy $FIELD$, $VALUE$", + "description": "Title for a button that copies a field value to the clipboard.", + "placeholders": { + "field": { + "content": "$1", + "example": "Username" + }, + "value": { + "content": "$2", + "example": "Foo" + } + } + }, "noValuesToCopy": { - "message": "No values to copy" + "message": "沒有資料可以複製" }, "assignToCollections": { "message": "指派至集合" }, "copyEmail": { - "message": "Copy email" + "message": "複製電子郵件地址" }, "copyPhone": { - "message": "Copy phone" + "message": "複製電話" }, "copyAddress": { - "message": "Copy address" + "message": "複製地址" }, "adminConsole": { - "message": "Admin Console" + "message": "管理控制台" }, "accountSecurity": { "message": "帳戶安全性" @@ -4081,13 +4322,13 @@ "message": "外觀" }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "指定目標集合時發生錯誤。" }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "指定目標資料夾時發生錯誤。" }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "檢視 $NAME$ 中的項目", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -4097,7 +4338,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "回到 $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -4107,7 +4348,7 @@ } }, "new": { - "message": "New" + "message": "新增" }, "removeItem": { "message": "移除 $NAME$", @@ -4120,65 +4361,56 @@ } }, "itemsWithNoFolder": { - "message": "Items with no folder" + "message": "不在任何資料夾中的項目" }, "itemDetails": { "message": "項目詳細資訊" }, "itemName": { - "message": "Item name" - }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } + "message": "項目名稱" }, "organizationIsDeactivated": { - "message": "Organization is deactivated" + "message": "組織已被停用" }, "owner": { - "message": "Owner" + "message": "擁有者" }, "selfOwnershipLabel": { - "message": "You", + "message": "您", "description": "Used as a label to indicate that the user is the owner of an item." }, "contactYourOrgAdmin": { - "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." + "message": "無法存取已停用組織中的項目。請聯絡您組織的擁有者以獲取協助。" }, "additionalInformation": { - "message": "Additional information" + "message": "更多資訊" }, "itemHistory": { - "message": "Item history" + "message": "項目歷史記錄" }, "lastEdited": { - "message": "Last edited" + "message": "最後編輯" }, "ownerYou": { - "message": "Owner: You" + "message": "擁有者: 您" }, "linked": { - "message": "Linked" + "message": "連結" }, "copySuccessful": { - "message": "Copy Successful" + "message": "複製成功" }, "upload": { - "message": "Upload" + "message": "上傳" }, "addAttachment": { - "message": "Add attachment" + "message": "新增附件" }, "maxFileSizeSansPunctuation": { - "message": "Maximum file size is 500 MB" + "message": "最大檔案大小為 500MB" }, "deleteAttachmentName": { - "message": "Delete attachment $NAME$", + "message": "刪除附檔 $NAME$", "placeholders": { "name": { "content": "$1", @@ -4187,7 +4419,7 @@ } }, "downloadAttachmentName": { - "message": "Download $NAME$", + "message": "下載 $NAME$", "placeholders": { "name": { "content": "$1", @@ -4196,25 +4428,25 @@ } }, "permanentlyDeleteAttachmentConfirmation": { - "message": "Are you sure you want to permanently delete this attachment?" + "message": "您確定要永久刪除此附檔嗎?" }, "premium": { "message": "進階版" }, "freeOrgsCannotUseAttachments": { - "message": "Free organizations cannot use attachments" + "message": "免費組織無法使用附檔" }, "filters": { - "message": "Filters" + "message": "篩選器" }, "filterVault": { - "message": "Filter vault" + "message": "過濾密碼庫" }, "filterApplied": { - "message": "One filter applied" + "message": "套用了一個過濾條件" }, "filterAppliedPlural": { - "message": "$COUNT$ filters applied", + "message": "套用了 $COUNT$ 個過濾條件", "placeholders": { "count": { "content": "$1", @@ -4226,13 +4458,13 @@ "message": "個人資訊" }, "identification": { - "message": "Identification" + "message": "身份" }, "contactInfo": { - "message": "Contact info" + "message": "聯繫資訊" }, "downloadAttachment": { - "message": "Download - $ITEMNAME$", + "message": "下載 - $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -4241,23 +4473,23 @@ } }, "cardNumberEndsWith": { - "message": "card number ends with", + "message": "以此號碼結尾的信用卡", "description": "Used within the inline menu to provide an aria description when users are attempting to fill a card cipher." }, "loginCredentials": { - "message": "Login credentials" + "message": "登入資訊" }, "authenticatorKey": { - "message": "Authenticator key" + "message": "驗證器金鑰" }, "autofillOptions": { "message": "自動填入選項" }, "websiteUri": { - "message": "Website (URI)" + "message": "網站 (URI)" }, "websiteUriCount": { - "message": "Website (URI) $COUNT$", + "message": "網站 (URI) $COUNT$", "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", "placeholders": { "count": { @@ -4267,16 +4499,16 @@ } }, "websiteAdded": { - "message": "Website added" + "message": "網站已新增" }, "addWebsite": { - "message": "Add website" + "message": "新增網站" }, "deleteWebsite": { - "message": "Delete website" + "message": "刪除網站" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "預設 ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -4286,7 +4518,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "顯示偵測到的吻合 $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -4295,7 +4527,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "隱藏偵測到的吻合 $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -4304,13 +4536,13 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "在頁面載入時自動填寫?" }, "cardExpiredTitle": { - "message": "Expired card" + "message": "過期的信用卡" }, "cardExpiredMessage": { - "message": "If you've renewed it, update the card's information" + "message": "如果您已續期,請更新信用卡資訊" }, "cardDetails": { "message": "信用卡詳細資料" @@ -4334,17 +4566,17 @@ "message": "新增帳戶" }, "loading": { - "message": "Loading" + "message": "正在載入" }, "data": { - "message": "Data" + "message": "資料" }, "passkeys": { "message": "密碼金鑰", "description": "A section header for a list of passkeys." }, "passwords": { - "message": "Passwords", + "message": "密碼", "description": "A section header for a list of passwords." }, "logInWithPasskeyAriaLabel": { @@ -4352,16 +4584,16 @@ "description": "ARIA label for the inline menu button that logs in with a passkey." }, "assign": { - "message": "Assign" + "message": "指定" }, "bulkCollectionAssignmentDialogDescriptionSingular": { - "message": "Only organization members with access to these collections will be able to see the item." + "message": "只有可以檢視集合的組織成員才能看到其中的項目。" }, "bulkCollectionAssignmentDialogDescriptionPlural": { - "message": "Only organization members with access to these collections will be able to see the items." + "message": "只有可以檢視集合的組織成員才能看到其中的項目。" }, "bulkCollectionAssignmentWarning": { - "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "message": "您已經選擇 $TOTAL_COUNT$ 個項目。由於您沒有編輯權限,無法更新其中的 $READONLY_COUNT$ 個項目。", "placeholders": { "total_count": { "content": "$1", @@ -4373,37 +4605,37 @@ } }, "addField": { - "message": "Add field" + "message": "新增欄位" }, "add": { - "message": "Add" + "message": "新增" }, "fieldType": { - "message": "Field type" + "message": "欄位類型" }, "fieldLabel": { - "message": "Field label" + "message": "欄位標籤" }, "textHelpText": { - "message": "Use text fields for data like security questions" + "message": "像是安全問答的資訊可以使用文字欄位來儲存" }, "hiddenHelpText": { - "message": "Use hidden fields for sensitive data like a password" + "message": "像是密碼的機密資訊可以使用隱藏欄位來儲存" }, "checkBoxHelpText": { - "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + "message": "若您想自動填入欄位中的核取方塊,例如儲存電子郵件,可以使用核取方塊。" }, "linkedHelpText": { - "message": "Use a linked field when you are experiencing autofill issues for a specific website." + "message": "使用連結欄位若您在特定網站上遇到自動填入問題。" }, "linkedLabelHelpText": { - "message": "Enter the the field's html id, name, aria-label, or placeholder." + "message": "填入欄位的 html id、名稱、標籤或預留字元" }, "editField": { - "message": "Edit field" + "message": "編輯欄位" }, "editFieldLabel": { - "message": "Edit $LABEL$", + "message": "編輯 $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4412,7 +4644,7 @@ } }, "deleteCustomField": { - "message": "Delete $LABEL$", + "message": "刪除 $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4421,7 +4653,7 @@ } }, "fieldAdded": { - "message": "$LABEL$ added", + "message": "$LABEL$ 已新增", "placeholders": { "label": { "content": "$1", @@ -4430,7 +4662,7 @@ } }, "reorderToggleButton": { - "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "message": "重新排序 $LABEL$。使用方向鍵來往上或下移動。", "placeholders": { "label": { "content": "$1", @@ -4438,8 +4670,11 @@ } } }, + "reorderWebsiteUriButton": { + "message": "Reorder website URI. Use arrow key to move item up or down." + }, "reorderFieldUp": { - "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "message": "往上移動 $LABEL$,位置 $LENGTH$ 之 $INDEX$", "placeholders": { "label": { "content": "$1", @@ -4459,10 +4694,10 @@ "message": "選擇要指派的集合" }, "personalItemTransferWarningSingular": { - "message": "1 item will be permanently transferred to the selected organization. You will no longer own this item." + "message": "1 個項目會被永久移到選擇的組織。您將不再擁有此項目。" }, "personalItemsTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to the selected organization. You will no longer own these items.", + "message": "$PERSONAL_ITEMS_COUNT$ 個項目會被永久移到選擇的組織。您將不再擁有這些項目。", "placeholders": { "personal_items_count": { "content": "$1", @@ -4471,7 +4706,7 @@ } }, "personalItemWithOrgTransferWarningSingular": { - "message": "1 item will be permanently transferred to $ORG$. You will no longer own this item.", + "message": "1 個項目會被永久移到 $ORG$。您將不再擁有此項目。", "placeholders": { "org": { "content": "$1", @@ -4480,7 +4715,7 @@ } }, "personalItemsWithOrgTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to $ORG$. You will no longer own these items.", + "message": "$PERSONAL_ITEMS_COUNT$ 個項目會被永久移到 $ORG$。您將不再擁有這些項目。", "placeholders": { "personal_items_count": { "content": "$1", @@ -4496,19 +4731,10 @@ "message": "指派集合成功" }, "nothingSelected": { - "message": "You have not selected anything." - }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } + "message": "您沒有選擇任何項目。" }, "itemsMovedToOrg": { - "message": "Items moved to $ORGNAME$", + "message": "項目已移到 $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -4517,7 +4743,7 @@ } }, "itemMovedToOrg": { - "message": "Item moved to $ORGNAME$", + "message": "項目已移到 $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -4526,7 +4752,7 @@ } }, "reorderFieldDown": { - "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "message": "往下移動 $LABEL$,位置 $LENGTH$ 之 $INDEX$", "placeholders": { "label": { "content": "$1", @@ -4543,52 +4769,46 @@ } }, "itemLocation": { - "message": "Item Location" + "message": "項目位置" }, "fileSend": { - "message": "File Send" + "message": "檔案 Send" }, "fileSends": { - "message": "File Sends" + "message": "檔案 Send" }, "textSend": { - "message": "Text Send" + "message": "文字 Send" }, "textSends": { - "message": "Text Sends" - }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" + "message": "文字 Sends" }, "accountActions": { - "message": "Account actions" + "message": "帳號動作" }, "showNumberOfAutofillSuggestions": { - "message": "Show number of login autofill suggestions on extension icon" + "message": "在擴充套件圖示上顯示自動填入建議的數量" }, "showQuickCopyActions": { - "message": "Show quick copy actions on Vault" + "message": "在密碼庫中顯示快速複製" }, "systemDefault": { - "message": "System default" + "message": "系統預設值" }, "enterprisePolicyRequirementsApplied": { - "message": "Enterprise policy requirements have been applied to this setting" + "message": "企業政策已套用至您的選項中" }, "sshPrivateKey": { - "message": "Private key" + "message": "私密金鑰" }, "sshPublicKey": { - "message": "Public key" + "message": "公共金鑰" }, "sshFingerprint": { - "message": "Fingerprint" + "message": "指紋" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "金鑰類型" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -4603,223 +4823,250 @@ "message": "RSA 4096-Bit" }, "retry": { - "message": "Retry" + "message": "重試" }, "vaultCustomTimeoutMinimum": { - "message": "Minimum custom timeout is 1 minute." + "message": "自訂逾時時間最小為 1 分鐘。" }, "additionalContentAvailable": { - "message": "Additional content is available" + "message": "以及更多內容" }, "fileSavedToDevice": { - "message": "File saved to device. Manage from your device downloads." + "message": "檔案已儲存到裝置。在您的裝置上管理下載檔案。" }, "showCharacterCount": { - "message": "Show character count" + "message": "顯示字元數" }, "hideCharacterCount": { - "message": "Hide character count" + "message": "隱藏字元數" }, "itemsInTrash": { - "message": "Items in trash" + "message": "在垃圾桶中的項目" }, "noItemsInTrash": { - "message": "No items in trash" + "message": "垃圾桶中沒有項目" }, "noItemsInTrashDesc": { - "message": "Items you delete will appear here and be permanently deleted after 30 days" + "message": "您刪除的項目會在此顯示,並會在 30 天之後永久刪除" }, "trashWarning": { - "message": "Items that have been in trash more than 30 days will automatically be deleted" + "message": "垃圾桶中超過 30 天的項目將會被自動刪除。" }, "restore": { - "message": "Restore" + "message": "還原" }, "deleteForever": { - "message": "Delete forever" + "message": "永遠刪除" }, "noEditPermissions": { - "message": "You don't have permission to edit this item" + "message": "你沒有權限編輯這個項目" + }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "需要 PIN 碼或密碼解鎖才能使用生物辨識解鎖。" + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "生物辨識解鎖暫時無法使用。" + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "由於系統檔案不正確,生物辨識解鎖無法使用。" + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "由於系統檔案不正確,生物辨識解鎖無法使用。" + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "由於 Bitwarden 桌面應用程式已關閉,生物辨識解鎖無法使用。" + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "由於未 Bitwarden 桌面應用程式的 $EMAIL$ 帳號上啟動,生物辨識解鎖無法使用。", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "基於不明原因,生物辨識解鎖無法使用。" }, "authenticating": { - "message": "Authenticating" + "message": "驗證中" }, "fillGeneratedPassword": { - "message": "Fill generated password", + "message": "自動填入產生的密碼", "description": "Heading for the password generator within the inline menu" }, "passwordRegenerated": { - "message": "Password regenerated", + "message": "密碼已重新產生", "description": "Notification message for when a password has been regenerated" }, "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "message": "在 Bitwarden 中儲存登入資訊?", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { - "message": "Space", + "message": "空格", "description": "Represents the space key in screen reader content as a readable word" }, "tildeCharacterDescriptor": { - "message": "Tilde", + "message": "波浪", "description": "Represents the ~ key in screen reader content as a readable word" }, "backtickCharacterDescriptor": { - "message": "Backtick", + "message": "重音符", "description": "Represents the ` key in screen reader content as a readable word" }, "exclamationCharacterDescriptor": { - "message": "Exclamation mark", + "message": "驚歎號", "description": "Represents the ! key in screen reader content as a readable word" }, "atSignCharacterDescriptor": { - "message": "At sign", + "message": "在符號", "description": "Represents the @ key in screen reader content as a readable word" }, "hashSignCharacterDescriptor": { - "message": "Hash sign", + "message": "井字號", "description": "Represents the # key in screen reader content as a readable word" }, "dollarSignCharacterDescriptor": { - "message": "Dollar sign", + "message": "錢字號", "description": "Represents the $ key in screen reader content as a readable word" }, "percentSignCharacterDescriptor": { - "message": "Percent sign", + "message": "百分比", "description": "Represents the % key in screen reader content as a readable word" }, "caretCharacterDescriptor": { - "message": "Caret", + "message": "插入號", "description": "Represents the ^ key in screen reader content as a readable word" }, "ampersandCharacterDescriptor": { - "message": "Ampersand", + "message": "和符號", "description": "Represents the & key in screen reader content as a readable word" }, "asteriskCharacterDescriptor": { - "message": "Asterisk", + "message": "星號", "description": "Represents the * key in screen reader content as a readable word" }, "parenLeftCharacterDescriptor": { - "message": "Left parenthesis", + "message": "左括號", "description": "Represents the ( key in screen reader content as a readable word" }, "parenRightCharacterDescriptor": { - "message": "Right parenthesis", + "message": "右括號", "description": "Represents the ) key in screen reader content as a readable word" }, "hyphenCharacterDescriptor": { - "message": "Underscore", + "message": "底線", "description": "Represents the _ key in screen reader content as a readable word" }, "underscoreCharacterDescriptor": { - "message": "Hyphen", + "message": "連字號", "description": "Represents the - key in screen reader content as a readable word" }, "plusCharacterDescriptor": { - "message": "Plus", + "message": "加號", "description": "Represents the + key in screen reader content as a readable word" }, "equalsCharacterDescriptor": { - "message": "Equals", + "message": "等號", "description": "Represents the = key in screen reader content as a readable word" }, "braceLeftCharacterDescriptor": { - "message": "Left brace", + "message": "左大括號", "description": "Represents the { key in screen reader content as a readable word" }, "braceRightCharacterDescriptor": { - "message": "Right brace", + "message": "右大括號", "description": "Represents the } key in screen reader content as a readable word" }, "bracketLeftCharacterDescriptor": { - "message": "Left bracket", + "message": "左中括號", "description": "Represents the [ key in screen reader content as a readable word" }, "bracketRightCharacterDescriptor": { - "message": "Right bracket", + "message": "右中括號", "description": "Represents the ] key in screen reader content as a readable word" }, "pipeCharacterDescriptor": { - "message": "Pipe", + "message": "垂直符號", "description": "Represents the | key in screen reader content as a readable word" }, "backSlashCharacterDescriptor": { - "message": "Back slash", + "message": "反斜線", "description": "Represents the back slash key in screen reader content as a readable word" }, "colonCharacterDescriptor": { - "message": "Colon", + "message": "冒號", "description": "Represents the : key in screen reader content as a readable word" }, "semicolonCharacterDescriptor": { - "message": "Semicolon", + "message": "分號", "description": "Represents the ; key in screen reader content as a readable word" }, "doubleQuoteCharacterDescriptor": { - "message": "Double quote", + "message": "雙引號", "description": "Represents the double quote key in screen reader content as a readable word" }, "singleQuoteCharacterDescriptor": { - "message": "Single quote", + "message": "單引號", "description": "Represents the ' key in screen reader content as a readable word" }, "lessThanCharacterDescriptor": { - "message": "Less than", + "message": "小於", "description": "Represents the < key in screen reader content as a readable word" }, "greaterThanCharacterDescriptor": { - "message": "Greater than", + "message": "大於", "description": "Represents the > key in screen reader content as a readable word" }, "commaCharacterDescriptor": { - "message": "Comma", + "message": "逗號", "description": "Represents the , key in screen reader content as a readable word" }, "periodCharacterDescriptor": { - "message": "Period", + "message": "句號", "description": "Represents the . key in screen reader content as a readable word" }, "questionCharacterDescriptor": { - "message": "Question mark", + "message": "問號", "description": "Represents the ? key in screen reader content as a readable word" }, "forwardSlashCharacterDescriptor": { - "message": "Forward slash", + "message": "斜線", "description": "Represents the / key in screen reader content as a readable word" }, "lowercaseAriaLabel": { - "message": "Lowercase" + "message": "小寫" }, "uppercaseAriaLabel": { - "message": "Uppercase" + "message": "大寫" }, "generatedPassword": { - "message": "Generated password" + "message": "已產生的密碼" }, "compactMode": { - "message": "Compact mode" + "message": "緊湊模式" }, "beta": { - "message": "Beta" + "message": "測試版" }, "importantNotice": { - "message": "Important notice" + "message": "重要通知" }, "setupTwoStepLogin": { - "message": "Set up two-step login" + "message": "啟動兩階段登入" }, "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + "message": "從 2025 年 2 月開始,Bitwarden 會傳送代碼到您的帳號電子郵件中來驗證新裝置的登入。" }, "newDeviceVerificationNoticeContentPage2": { - "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + "message": "您可以啟動兩階段認證來保護您的帳號或更改您可以存取的電子郵件位址。" }, "remindMeLater": { - "message": "Remind me later" + "message": "稍後再提醒我" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", + "message": "您可以存取您的電子郵件位址 $EMAIL$ 嗎?", "placeholders": { "email": { "content": "$1", @@ -4828,24 +5075,69 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "不,我不行" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" + "message": "是,我可以存取我的電子郵件位址" }, "turnOnTwoStepLogin": { - "message": "Turn on two-step login" + "message": "啟動兩階段登入" }, "changeAcctEmail": { - "message": "Change account email" + "message": "更改帳號電子郵件位址" }, "extensionWidth": { - "message": "Extension width" + "message": "擴充套件寬度" }, "wide": { - "message": "Wide" + "message": "寬度" }, "extraWide": { - "message": "Extra wide" + "message": "更寬" + }, + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "cannotRemoveViewOnlyCollections": { + "message": "若您只有檢視權限,無法移除集合 $COLLECTIONS$。", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, + "updateDesktopAppOrDisableFingerprintDialogTitle": { + "message": "請更新您的桌面應用程式" + }, + "updateDesktopAppOrDisableFingerprintDialogMessage": { + "message": "為了使用生物辨識解鎖,請更新您的桌面應用程式,或在設定中停用指紋解鎖。" + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/browser/src/auth/popup/account-switching/account-switcher.component.html b/apps/browser/src/auth/popup/account-switching/account-switcher.component.html index 0152cd1c7ff..de8ab4c7b08 100644 --- a/apps/browser/src/auth/popup/account-switching/account-switcher.component.html +++ b/apps/browser/src/auth/popup/account-switching/account-switcher.component.html @@ -15,7 +15,7 @@ -

{{ "availableAccounts" | i18n }}

+

{{ "availableAccounts" | i18n }}

diff --git a/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts b/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts index 25e1b2ae83f..78bee121afb 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 @@ -5,12 +5,14 @@ import { Subject, firstValueFrom, map, of, startWith, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { LockService } from "@bitwarden/auth/common"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; -import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; import { 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 { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; +import { + VaultTimeoutAction, + VaultTimeoutService, + VaultTimeoutSettingsService, +} from "@bitwarden/common/key-management/vault-timeout"; import { UserId } from "@bitwarden/common/types/guid"; import { AvatarModule, @@ -19,6 +21,7 @@ import { ItemModule, SectionComponent, SectionHeaderComponent, + TypographyModule, } from "@bitwarden/components"; import { enableAccountSwitching } from "../../../platform/flags"; @@ -46,6 +49,7 @@ import { AccountSwitcherService } from "./services/account-switcher.service"; AccountComponent, SectionComponent, SectionHeaderComponent, + TypographyModule, ], }) export class AccountSwitcherComponent implements OnInit, OnDestroy { diff --git a/apps/browser/src/auth/popup/account-switching/current-account.component.html b/apps/browser/src/auth/popup/account-switching/current-account.component.html index dacf4b34be5..f59a2b08fdd 100644 --- a/apps/browser/src/auth/popup/account-switching/current-account.component.html +++ b/apps/browser/src/auth/popup/account-switching/current-account.component.html @@ -5,8 +5,7 @@ class="tw-rounded-full hover:tw-outline hover:tw-outline-1 hover:tw-outline-offset-1" (click)="currentAccountClicked()" > - {{ "switchAccounts" | i18n }}: - {{ "activeAccount" | i18n }} {{ currentAccount.email }} + {{ "bitwardenAccount" | i18n }} {{ currentAccount.email }} { let activeAccountSubject: BehaviorSubject; let authStatusSubject: ReplaySubject>; + let envBSubject: BehaviorSubject; + const mockHostName = "mockHostName"; + const mockEnv: Partial = { + getHostname: () => mockHostName, + }; + const accountService = mock(); const avatarService = mock(); const messagingService = mock(); @@ -41,6 +50,9 @@ describe("AccountSwitcherService", () => { accountService.activeAccount$ = activeAccountSubject; authService.authStatuses$ = authStatusSubject; + envBSubject = new BehaviorSubject(mockEnv as Environment); + environmentService.getEnvironment$.mockReturnValue(envBSubject); + accountSwitcherService = new AccountSwitcherService( accountService, avatarService, @@ -79,11 +91,16 @@ describe("AccountSwitcherService", () => { expect(accounts).toHaveLength(3); expect(accounts[0].id).toBe("1"); expect(accounts[0].isActive).toBeTruthy(); + + expect(accounts[0].server).toBe(mockHostName); + expect(accounts[1].id).toBe("2"); expect(accounts[1].isActive).toBeFalsy(); + expect(accounts[1].server).toBe(mockHostName); expect(accounts[2].id).toBe("addAccount"); expect(accounts[2].isActive).toBeFalsy(); + expect(accounts[2].server).toBe(undefined); }); it.each([5, 6])( diff --git a/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.ts b/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.ts index 535df3ec6bb..bfed7dc1408 100644 --- a/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.ts +++ b/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.ts @@ -66,11 +66,12 @@ export class AccountSwitcherService { const hasMaxAccounts = loggedInIds.length >= this.ACCOUNT_LIMIT; const options: AvailableAccount[] = await Promise.all( loggedInIds.map(async (id: UserId) => { + const userEnv = await firstValueFrom(this.environmentService.getEnvironment$(id)); return { name: accounts[id].name ?? accounts[id].email, email: accounts[id].email, id: id, - server: (await this.environmentService.getEnvironment(id))?.getHostname(), + server: userEnv?.getHostname(), status: accountStatuses[id], isActive: id === activeAccount?.id, avatarColor: await firstValueFrom( diff --git a/apps/browser/src/auth/popup/environment.component.html b/apps/browser/src/auth/popup/environment.component.html deleted file mode 100644 index ff19739548a..00000000000 --- a/apps/browser/src/auth/popup/environment.component.html +++ /dev/null @@ -1,122 +0,0 @@ -
-
-
- -
-

- {{ "appName" | i18n }} -

-
- -
-
-
- - - {{ "environmentEditedClick" | i18n }} - - {{ "environmentEditedReset" | i18n }} - - -
-

- {{ "selfHostedEnvironment" | i18n }} -

-
-
- - -
-
- -
-
-

- {{ "customEnvironment" | i18n }} -

-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- -
-
-
diff --git a/apps/browser/src/auth/popup/environment.component.ts b/apps/browser/src/auth/popup/environment.component.ts deleted file mode 100644 index c922c61b7e8..00000000000 --- a/apps/browser/src/auth/popup/environment.component.ts +++ /dev/null @@ -1,55 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, OnInit } from "@angular/core"; -import { Router } from "@angular/router"; - -import { EnvironmentComponent as BaseEnvironmentComponent } from "@bitwarden/angular/auth/components/environment.component"; -import { ModalService } from "@bitwarden/angular/services/modal.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { ToastService } from "@bitwarden/components"; - -import { BrowserEnvironmentService } from "../../platform/services/browser-environment.service"; - -@Component({ - selector: "app-environment", - templateUrl: "environment.component.html", -}) -export class EnvironmentComponent extends BaseEnvironmentComponent implements OnInit { - showEditedManagedSettings = false; - - constructor( - platformUtilsService: PlatformUtilsService, - public environmentService: BrowserEnvironmentService, - i18nService: I18nService, - private router: Router, - modalService: ModalService, - toastService: ToastService, - ) { - super(platformUtilsService, environmentService, i18nService, modalService, toastService); - this.showCustom = true; - } - - async ngOnInit() { - this.showEditedManagedSettings = await this.environmentService.settingsHaveChanged(); - } - - async resetEnvironment() { - const urls = await this.environmentService.getManagedEnvironment(); - - this.baseUrl = urls.base; - this.webVaultUrl = urls.webVault; - this.apiUrl = urls.api; - this.iconsUrl = urls.icons; - this.identityUrl = urls.identity; - this.notificationsUrl = urls.notifications; - this.iconsUrl = urls.icons; - } - - saved() { - super.saved(); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate([""]); - } -} diff --git a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html index 8893697da17..88a3b1c3076 100644 --- a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html +++ b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html @@ -5,7 +5,7 @@ [showBackButton]="showBackButton" [pageTitle]="''" > - + diff --git a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts index ad7e6f67361..841eefda0ad 100644 --- a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts +++ b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts @@ -25,6 +25,8 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { UserId } from "@bitwarden/common/types/guid"; import { ButtonModule, I18nMockService } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { RegistrationCheckEmailIcon } from "../../../../../../libs/auth/src/angular/icons/registration-check-email.icon"; import { PopupRouterCacheService } from "../../../platform/popup/view-cache/popup-router-cache.service"; import { AccountSwitcherService } from "../account-switching/services/account-switcher.service"; diff --git a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-bitwarden-logo.icon.ts b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-bitwarden-logo.icon.ts index 51d748e1fbb..1de4bd37239 100644 --- a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-bitwarden-logo.icon.ts +++ b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-bitwarden-logo.icon.ts @@ -8,6 +8,7 @@ export const ExtensionBitwardenLogo = svgIcon` fill="none" xmlns="http://www.w3.org/2000/svg" > + Bitwarden
@@ -66,5 +72,8 @@ > {{ "save" | i18n }} + diff --git a/apps/browser/src/autofill/popup/settings/excluded-domains.component.ts b/apps/browser/src/autofill/popup/settings/excluded-domains.component.ts index 7d429bfe4f0..504d2dbfc17 100644 --- a/apps/browser/src/autofill/popup/settings/excluded-domains.component.ts +++ b/apps/browser/src/autofill/popup/settings/excluded-domains.component.ts @@ -7,7 +7,13 @@ import { AfterViewInit, ViewChildren, } from "@angular/core"; -import { FormsModule } from "@angular/forms"; +import { + FormsModule, + ReactiveFormsModule, + FormBuilder, + FormGroup, + FormArray, +} from "@angular/forms"; import { RouterModule } from "@angular/router"; import { Subject, takeUntil } from "rxjs"; @@ -34,6 +40,7 @@ import { PopOutComponent } from "../../../platform/popup/components/pop-out.comp import { PopupFooterComponent } from "../../../platform/popup/layout/popup-footer.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; +import { PopupRouterCacheService } from "../../../platform/popup/view-cache/popup-router-cache.service"; @Component({ selector: "app-excluded-domains", @@ -45,6 +52,7 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co CommonModule, FormFieldModule, FormsModule, + ReactiveFormsModule, IconButtonModule, ItemModule, JslibModule, @@ -68,6 +76,11 @@ export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy { isLoading = false; excludedDomainsState: string[] = []; storedExcludedDomains: string[] = []; + + protected domainListForm = new FormGroup({ + domains: this.formBuilder.array([]), + }); + // How many fields should be non-editable before editable fields fieldsEditThreshold: number = 0; @@ -77,10 +90,16 @@ export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy { private domainSettingsService: DomainSettingsService, private i18nService: I18nService, private toastService: ToastService, + private formBuilder: FormBuilder, + private popupRouterCacheService: PopupRouterCacheService, ) { this.accountSwitcherEnabled = enableAccountSwitching(); } + get domainForms() { + return this.domainListForm.get("domains") as FormArray; + } + async ngAfterViewInit() { this.domainSettingsService.neverDomains$ .pipe(takeUntil(this.destroy$)) @@ -117,8 +136,7 @@ export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy { } async addNewDomain() { - // add empty field to the Domains list interface - this.excludedDomainsState.push(""); + this.domainForms.push(this.formBuilder.control(null)); await this.fieldChange(); } @@ -134,6 +152,10 @@ export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy { await this.fieldChange(); } + async goBack() { + await this.popupRouterCacheService.back(); + } + async fieldChange() { if (this.dataIsPristine) { this.dataIsPristine = false; @@ -148,7 +170,11 @@ export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy { this.isLoading = true; const newExcludedDomainsSaveState: NeverDomains = {}; - const uniqueExcludedDomains = new Set(this.excludedDomainsState); + + const uniqueExcludedDomains = new Set([ + ...this.excludedDomainsState, + ...this.domainForms.value, + ]); for (const uri of uniqueExcludedDomains) { if (uri && uri !== "") { @@ -194,13 +220,14 @@ export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy { title: "", variant: "success", }); + + this.domainForms.clear(); } catch { this.toastService.showToast({ message: this.i18nService.t("unexpectedError"), title: "", variant: "error", }); - // Don't reset via `handleStateUpdate` to preserve input values this.isLoading = false; } diff --git a/apps/browser/src/autofill/popup/settings/notifications-v1.component.html b/apps/browser/src/autofill/popup/settings/notifications-v1.component.html deleted file mode 100644 index 89d83c9e480..00000000000 --- a/apps/browser/src/autofill/popup/settings/notifications-v1.component.html +++ /dev/null @@ -1,89 +0,0 @@ -
-
- -
-

- {{ "notifications" | i18n }} -

-
- -
-
-
-
-
-
- - -
-
- -
-
-
-
- - -
-
- -
-
-
-
- - -
-
- -
-
-
- -
-
-
diff --git a/apps/browser/src/autofill/popup/settings/notifications-v1.component.ts b/apps/browser/src/autofill/popup/settings/notifications-v1.component.ts deleted file mode 100644 index 442e91e55eb..00000000000 --- a/apps/browser/src/autofill/popup/settings/notifications-v1.component.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Component, OnInit } from "@angular/core"; -import { firstValueFrom } from "rxjs"; - -import { UserNotificationSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/user-notification-settings.service"; -import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; - -import { enableAccountSwitching } from "../../../platform/flags"; - -@Component({ - selector: "autofill-notification-v1-settings", - templateUrl: "notifications-v1.component.html", -}) -export class NotificationsSettingsV1Component implements OnInit { - enableAddLoginNotification = false; - enableChangedPasswordNotification = false; - enablePasskeys = true; - accountSwitcherEnabled = false; - - constructor( - private userNotificationSettingsService: UserNotificationSettingsServiceAbstraction, - private vaultSettingsService: VaultSettingsService, - ) { - this.accountSwitcherEnabled = enableAccountSwitching(); - } - - async ngOnInit() { - this.enableAddLoginNotification = await firstValueFrom( - this.userNotificationSettingsService.enableAddedLoginPrompt$, - ); - - this.enableChangedPasswordNotification = await firstValueFrom( - this.userNotificationSettingsService.enableChangedPasswordPrompt$, - ); - - this.enablePasskeys = await firstValueFrom(this.vaultSettingsService.enablePasskeys$); - } - - async updateAddLoginNotification() { - await this.userNotificationSettingsService.setEnableAddedLoginPrompt( - this.enableAddLoginNotification, - ); - } - - async updateChangedPasswordNotification() { - await this.userNotificationSettingsService.setEnableChangedPasswordPrompt( - this.enableChangedPasswordNotification, - ); - } - - async updateEnablePasskeys() { - await this.vaultSettingsService.setEnablePasskeys(this.enablePasskeys); - } -} diff --git a/apps/browser/src/autofill/services/autofill-overlay-content.service.spec.ts b/apps/browser/src/autofill/services/autofill-overlay-content.service.spec.ts index 8a77534d0d4..48bc7ceafda 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 @@ -459,15 +459,20 @@ describe("AutofillOverlayContentService", () => { const passwordFieldElement = document.getElementById( "password-field", ) as ElementWithOpId; - autofillFieldData.type = "password"; + + const passwordFieldData = createAutofillFieldMock({ + opid: "password-field", + form: "validFormId", + elementNumber: 2, + type: "password", + }); await autofillOverlayContentService.setupOverlayListeners( passwordFieldElement, - autofillFieldData, + passwordFieldData, pageDetailsMock, ); passwordFieldElement.dispatchEvent(new Event("input")); - expect(autofillOverlayContentService["userFilledFields"].password).toEqual( passwordFieldElement, ); 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 daa65d74ae6..6757972e839 100644 --- a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts +++ b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts @@ -424,6 +424,7 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ } await this.setupSubmitListenerOnFormlessField(formFieldElement); + return; } /** @@ -439,15 +440,16 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ this.formElements.add(formElement); formElement.addEventListener(EVENTS.SUBMIT, this.handleFormFieldSubmitEvent); - const closesSubmitButton = await this.findSubmitButton(formElement); + const closestSubmitButton = await this.findSubmitButton(formElement); // If we cannot find a submit button within the form, check for a submit button outside the form. - if (!closesSubmitButton) { + if (!closestSubmitButton) { await this.setupSubmitListenerOnFormlessField(formFieldElement); return; } - this.setupSubmitButtonEventListeners(closesSubmitButton); + this.setupSubmitButtonEventListeners(closestSubmitButton); + return; } } @@ -459,9 +461,11 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ */ private async setupSubmitListenerOnFormlessField(formFieldElement: FillableFormFieldElement) { if (formFieldElement && !this.fieldsWithSubmitElements.has(formFieldElement)) { - const closesSubmitButton = await this.findClosestFormlessSubmitButton(formFieldElement); - this.setupSubmitButtonEventListeners(closesSubmitButton); + const closestSubmitButton = await this.findClosestFormlessSubmitButton(formFieldElement); + + this.setupSubmitButtonEventListeners(closestSubmitButton); } + return; } /** @@ -957,8 +961,17 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ accountCreationFieldType: autofillFieldData?.accountCreationFieldType, }; + const allFields = this.formFieldElements; + const allFieldsRect = []; + + for (const key of allFields.keys()) { + const rect = await this.getMostRecentlyFocusedFieldRects(key); + allFieldsRect.push({ ...allFields.get(key), rect }); // Add the combined result to the array + } + await this.sendExtensionMessage("updateFocusedFieldData", { focusedFieldData: this.focusedFieldData, + allFieldsRect, }); } @@ -1408,6 +1421,8 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ url.origin + pathWithoutTrailingSlashAndSearch, url.origin + pathWithoutTrailingSlashSearchAndHash, ]); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (_error) { return null; } diff --git a/apps/browser/src/autofill/services/autofill.service.spec.ts b/apps/browser/src/autofill/services/autofill.service.spec.ts index 16b11b98866..7bc66ea322c 100644 --- a/apps/browser/src/autofill/services/autofill.service.spec.ts +++ b/apps/browser/src/autofill/services/autofill.service.spec.ts @@ -769,7 +769,10 @@ describe("AutofillService", () => { ); expect(autofillService["generateLoginFillScript"]).toHaveBeenCalled(); expect(logService.info).not.toHaveBeenCalled(); - expect(cipherService.updateLastUsedDate).toHaveBeenCalledWith(autofillOptions.cipher.id); + expect(cipherService.updateLastUsedDate).toHaveBeenCalledWith( + autofillOptions.cipher.id, + mockUserId, + ); expect(chrome.tabs.sendMessage).toHaveBeenCalledWith( autofillOptions.pageDetails[0].tab.id, { @@ -918,12 +921,12 @@ describe("AutofillService", () => { .spyOn(billingAccountProfileStateService, "hasPremiumFromAnySource$") .mockImplementation(() => of(true)); jest.spyOn(autofillService, "getShouldAutoCopyTotp").mockResolvedValue(true); - jest.spyOn(totpService, "getCode").mockResolvedValue(totpCode); + totpService.getCode$.mockReturnValue(of({ code: totpCode, period: 30 })); const autofillResult = await autofillService.doAutoFill(autofillOptions); expect(autofillService.getShouldAutoCopyTotp).toHaveBeenCalled(); - expect(totpService.getCode).toHaveBeenCalledWith(autofillOptions.cipher.login.totp); + expect(totpService.getCode$).toHaveBeenCalledWith(autofillOptions.cipher.login.totp); expect(autofillResult).toBe(totpCode); }); @@ -937,7 +940,7 @@ describe("AutofillService", () => { const autofillResult = await autofillService.doAutoFill(autofillOptions); expect(autofillService.getShouldAutoCopyTotp).not.toHaveBeenCalled(); - expect(totpService.getCode).not.toHaveBeenCalled(); + expect(totpService.getCode$).not.toHaveBeenCalled(); expect(autofillResult).toBeNull(); }); @@ -953,12 +956,12 @@ describe("AutofillService", () => { it("returns a null value if the login does not contain a TOTP value", async () => { autofillOptions.cipher.login.totp = undefined; jest.spyOn(autofillService, "getShouldAutoCopyTotp"); - jest.spyOn(totpService, "getCode"); + jest.spyOn(totpService, "getCode$"); const autofillResult = await autofillService.doAutoFill(autofillOptions); expect(autofillService.getShouldAutoCopyTotp).not.toHaveBeenCalled(); - expect(totpService.getCode).not.toHaveBeenCalled(); + expect(totpService.getCode$).not.toHaveBeenCalled(); expect(autofillResult).toBeNull(); }); @@ -981,12 +984,12 @@ describe("AutofillService", () => { .spyOn(billingAccountProfileStateService, "hasPremiumFromAnySource$") .mockImplementation(() => of(true)); jest.spyOn(autofillService, "getShouldAutoCopyTotp").mockResolvedValue(false); - jest.spyOn(totpService, "getCode"); + jest.spyOn(totpService, "getCode$"); const autofillResult = await autofillService.doAutoFill(autofillOptions); expect(autofillService.getShouldAutoCopyTotp).toHaveBeenCalled(); - expect(totpService.getCode).not.toHaveBeenCalled(); + expect(totpService.getCode$).not.toHaveBeenCalled(); expect(autofillResult).toBeNull(); }); }); @@ -1030,8 +1033,8 @@ describe("AutofillService", () => { const result = await autofillService.doAutoFillOnTab(pageDetails, tab, false); expect(cipherService.getNextCipherForUrl).not.toHaveBeenCalled(); - expect(cipherService.getLastLaunchedForUrl).toHaveBeenCalledWith(tab.url, true); - expect(cipherService.getLastUsedForUrl).toHaveBeenCalledWith(tab.url, true); + expect(cipherService.getLastLaunchedForUrl).toHaveBeenCalledWith(tab.url, mockUserId, true); + expect(cipherService.getLastUsedForUrl).toHaveBeenCalledWith(tab.url, mockUserId, true); expect(autofillService.doAutoFill).not.toHaveBeenCalled(); expect(result).toBeNull(); }); @@ -1044,7 +1047,7 @@ describe("AutofillService", () => { const result = await autofillService.doAutoFillOnTab(pageDetails, tab, true); - expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url); + expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url, mockUserId); expect(cipherService.getLastLaunchedForUrl).not.toHaveBeenCalled(); expect(cipherService.getLastUsedForUrl).not.toHaveBeenCalled(); expect(autofillService.doAutoFill).not.toHaveBeenCalled(); @@ -1074,7 +1077,7 @@ describe("AutofillService", () => { const result = await autofillService.doAutoFillOnTab(pageDetails, tab, fromCommand); - expect(cipherService.getLastLaunchedForUrl).toHaveBeenCalledWith(tab.url, true); + expect(cipherService.getLastLaunchedForUrl).toHaveBeenCalledWith(tab.url, mockUserId, true); expect(cipherService.getLastUsedForUrl).not.toHaveBeenCalled(); expect(cipherService.updateLastUsedIndexForUrl).not.toHaveBeenCalled(); expect(autofillService.doAutoFill).toHaveBeenCalledWith({ @@ -1104,8 +1107,8 @@ describe("AutofillService", () => { const result = await autofillService.doAutoFillOnTab(pageDetails, tab, fromCommand); - expect(cipherService.getLastLaunchedForUrl).toHaveBeenCalledWith(tab.url, true); - expect(cipherService.getLastUsedForUrl).toHaveBeenCalledWith(tab.url, true); + expect(cipherService.getLastLaunchedForUrl).toHaveBeenCalledWith(tab.url, mockUserId, true); + expect(cipherService.getLastUsedForUrl).toHaveBeenCalledWith(tab.url, mockUserId, true); expect(cipherService.updateLastUsedIndexForUrl).not.toHaveBeenCalled(); expect(autofillService.doAutoFill).toHaveBeenCalledWith({ tab: tab, @@ -1132,7 +1135,7 @@ describe("AutofillService", () => { const result = await autofillService.doAutoFillOnTab(pageDetails, tab, fromCommand); - expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url); + expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url, mockUserId); expect(cipherService.updateLastUsedIndexForUrl).toHaveBeenCalledWith(tab.url); expect(autofillService.doAutoFill).toHaveBeenCalledWith({ tab: tab, @@ -1163,7 +1166,7 @@ describe("AutofillService", () => { const result = await autofillService.doAutoFillOnTab(pageDetails, tab, true); - expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url); + expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url, mockUserId); expect(userVerificationService.hasMasterPasswordAndMasterKeyHash).toHaveBeenCalled(); expect(autofillService["openVaultItemPasswordRepromptPopout"]).toHaveBeenCalledWith(tab, { cipherId: cipher.id, @@ -1189,7 +1192,7 @@ describe("AutofillService", () => { const result = await autofillService.doAutoFillOnTab(pageDetails, tab, true); - expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url); + expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url, mockUserId); expect(autofillService["openVaultItemPasswordRepromptPopout"]).not.toHaveBeenCalled(); expect(autofillService.doAutoFill).not.toHaveBeenCalled(); expect(result).toBeNull(); diff --git a/apps/browser/src/autofill/services/autofill.service.ts b/apps/browser/src/autofill/services/autofill.service.ts index 6d0e9954ade..ed8b2033209 100644 --- a/apps/browser/src/autofill/services/autofill.service.ts +++ b/apps/browser/src/autofill/services/autofill.service.ts @@ -1,13 +1,23 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { filter, firstValueFrom, merge, Observable, ReplaySubject, scan, startWith } from "rxjs"; -import { pairwise } from "rxjs/operators"; +import { + filter, + firstValueFrom, + merge, + Observable, + ReplaySubject, + scan, + startWith, + timer, +} from "rxjs"; +import { map, pairwise, share, takeUntil } from "rxjs/operators"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { getOptionalUserId } from "@bitwarden/common/auth/services/account.service"; import { AutofillOverlayVisibility, CardExpiryDateDelimiters, @@ -145,7 +155,19 @@ export default class AutofillService implements AutofillServiceInterface { pageDetailsFallback$.next([]); } - return merge(pageDetailsFromTab$, pageDetailsFallback$); + // Share the pageDetailsFromTab$ observable so that multiple subscribers don't trigger multiple executions. + const sharedPageDetailsFromTab$ = pageDetailsFromTab$.pipe(share()); + + // Create a timeout observable that emits an empty array if pageDetailsFromTab$ hasn't emitted within 1 second. + const pageDetailsTimeout$ = timer(1000).pipe( + map(() => []), + takeUntil(sharedPageDetailsFromTab$), + ); + + // Merge the responses so that if pageDetailsFromTab$ emits, that value is used. + // Otherwise, if it doesn't emit in time, the timeout observable emits an empty array. + // Also, pageDetailsFallback$ will emit in error cases. + return merge(sharedPageDetailsFromTab$, pageDetailsFallback$, pageDetailsTimeout$); } /** @@ -464,7 +486,7 @@ export default class AutofillService implements AutofillServiceInterface { didAutofill = true; if (!options.skipLastUsed) { - await this.cipherService.updateLastUsedDate(options.cipher.id); + await this.cipherService.updateLastUsedDate(options.cipher.id, activeAccount.id); } // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. @@ -493,7 +515,7 @@ export default class AutofillService implements AutofillServiceInterface { const shouldAutoCopyTotp = await this.getShouldAutoCopyTotp(); totp = shouldAutoCopyTotp - ? await this.totpService.getCode(options.cipher.login.totp) + ? (await firstValueFrom(this.totpService.getCode$(options.cipher.login.totp))).code : null; }), ); @@ -527,17 +549,29 @@ export default class AutofillService implements AutofillServiceInterface { autoSubmitLogin = false, ): Promise { let cipher: CipherView; + + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(getOptionalUserId), + ); + if (activeUserId == null) { + return null; + } + if (fromCommand) { - cipher = await this.cipherService.getNextCipherForUrl(tab.url); + cipher = await this.cipherService.getNextCipherForUrl(tab.url, activeUserId); } else { - const lastLaunchedCipher = await this.cipherService.getLastLaunchedForUrl(tab.url, true); + const lastLaunchedCipher = await this.cipherService.getLastLaunchedForUrl( + tab.url, + activeUserId, + true, + ); if ( lastLaunchedCipher && Date.now().valueOf() - lastLaunchedCipher.localData?.lastLaunched?.valueOf() < 30000 ) { cipher = lastLaunchedCipher; } else { - cipher = await this.cipherService.getLastUsedForUrl(tab.url, true); + cipher = await this.cipherService.getLastUsedForUrl(tab.url, activeUserId, true); } } @@ -626,12 +660,19 @@ export default class AutofillService implements AutofillServiceInterface { let cipher: CipherView; let cacheKey = ""; + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(getOptionalUserId), + ); + if (activeUserId == null) { + return null; + } + if (cipherType === CipherType.Card) { cacheKey = "cardCiphers"; - cipher = await this.cipherService.getNextCardCipher(); + cipher = await this.cipherService.getNextCardCipher(activeUserId); } else { cacheKey = "identityCiphers"; - cipher = await this.cipherService.getNextIdentityCipher(); + cipher = await this.cipherService.getNextIdentityCipher(activeUserId); } if (!cipher || !cacheKey || (cipher.reprompt === CipherRepromptType.Password && !fromCommand)) { @@ -972,7 +1013,10 @@ export default class AutofillService implements AutofillServiceInterface { } filledFields[t.opid] = t; - let totpValue = await this.totpService.getCode(login.totp); + const totpResponse = await firstValueFrom( + this.totpService.getCode$(options.cipher.login.totp), + ); + let totpValue = totpResponse.code; if (totpValue.length == totps.length) { totpValue = totpValue.charAt(i); } @@ -1389,7 +1433,6 @@ export default class AutofillService implements AutofillServiceInterface { let doesContainValue = false; CreditCardAutoFillConstants.CardAttributesExtended.forEach((attributeName) => { - // eslint-disable-next-line no-prototype-builtins if (doesContainValue || !field[attributeName]) { return; } diff --git a/apps/browser/src/autofill/services/collect-autofill-content.service.spec.ts b/apps/browser/src/autofill/services/collect-autofill-content.service.spec.ts index 7471c298917..5f3bc4dfff9 100644 --- a/apps/browser/src/autofill/services/collect-autofill-content.service.spec.ts +++ b/apps/browser/src/autofill/services/collect-autofill-content.service.spec.ts @@ -317,6 +317,7 @@ describe("CollectAutofillContentService", () => { __form__0: { opid: "__form__0", htmlAction: formAction, + htmlClass: null, htmlName: formName, htmlID: formId, htmlMethod: formMethod, @@ -544,6 +545,7 @@ describe("CollectAutofillContentService", () => { __form__0: { opid: "__form__0", htmlAction: formAction1, + htmlClass: null, htmlName: formName1, htmlID: formId1, htmlMethod: formMethod1, @@ -551,6 +553,7 @@ describe("CollectAutofillContentService", () => { __form__1: { opid: "__form__1", htmlAction: formAction2, + htmlClass: null, htmlName: formName2, htmlID: formId2, htmlMethod: formMethod2, diff --git a/apps/browser/src/autofill/services/collect-autofill-content.service.ts b/apps/browser/src/autofill/services/collect-autofill-content.service.ts index 13c546bccdb..0f9c8993014 100644 --- a/apps/browser/src/autofill/services/collect-autofill-content.service.ts +++ b/apps/browser/src/autofill/services/collect-autofill-content.service.ts @@ -228,6 +228,7 @@ export class CollectAutofillContentService implements CollectAutofillContentServ opid: formElement.opid, htmlAction: this.getFormActionAttribute(formElement), htmlName: this.getPropertyOrAttribute(formElement, "name"), + htmlClass: this.getPropertyOrAttribute(formElement, "class"), htmlID: this.getPropertyOrAttribute(formElement, "id"), htmlMethod: this.getPropertyOrAttribute(formElement, "method"), }); diff --git a/apps/browser/src/autofill/services/dom-query.service.ts b/apps/browser/src/autofill/services/dom-query.service.ts index 0dbc246b235..16310397a03 100644 --- a/apps/browser/src/autofill/services/dom-query.service.ts +++ b/apps/browser/src/autofill/services/dom-query.service.ts @@ -235,6 +235,8 @@ export class DomQueryService implements DomQueryServiceInterface { if ((chrome as any).dom?.openOrClosedShadowRoot) { try { return (chrome as any).dom.openOrClosedShadowRoot(node); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { return null; } diff --git a/apps/browser/src/autofill/services/inline-menu-field-qualification.service.spec.ts b/apps/browser/src/autofill/services/inline-menu-field-qualification.service.spec.ts index dc59e05a18b..f3047947c82 100644 --- a/apps/browser/src/autofill/services/inline-menu-field-qualification.service.spec.ts +++ b/apps/browser/src/autofill/services/inline-menu-field-qualification.service.spec.ts @@ -21,8 +21,7 @@ describe("InlineMenuFieldQualificationService", () => { }); describe("isFieldForLoginForm", () => { - it("does not disqualify totp fields for premium users with flag set to true", () => { - inlineMenuFieldQualificationService["inlineMenuTotpFeatureFlag"] = true; + it("does not disqualify totp fields for premium users", () => { inlineMenuFieldQualificationService["premiumEnabled"] = true; const field = mock({ type: "text", @@ -37,24 +36,7 @@ describe("InlineMenuFieldQualificationService", () => { ); }); - it("disqualifies totp fields for premium users with flag set to false", () => { - inlineMenuFieldQualificationService["inlineMenuTotpFeatureFlag"] = false; - inlineMenuFieldQualificationService["inlineMenuTotpFeatureFlag"] = true; - const field = mock({ - type: "text", - autoCompleteType: "one-time-code", - htmlName: "totp", - htmlID: "totp", - placeholder: "totp", - }); - - expect(inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails)).toBe( - false, - ); - }); - - it("disqualifies totp fields for non-premium users with flag set to true", () => { - inlineMenuFieldQualificationService["inlineMenuTotpFeatureFlag"] = true; + it("disqualifies totp fields for non-premium users", () => { inlineMenuFieldQualificationService["premiumEnabled"] = false; const field = mock({ type: "text", 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 43efd338c6e..046381d956c 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 @@ -56,6 +56,7 @@ export class InlineMenuFieldQualificationService "neuer benutzer", "neues passwort", "neue e-mail", + "pwdcheck", ]; private updatePasswordFieldKeywords = [ "update password", @@ -150,17 +151,14 @@ export class InlineMenuFieldQualificationService ]); private totpFieldAutocompleteValue = "one-time-code"; private inlineMenuFieldQualificationFlagSet = false; - private inlineMenuTotpFeatureFlag = false; private premiumEnabled = false; constructor() { void Promise.all([ sendExtensionMessage("getInlineMenuFieldQualificationFeatureFlag"), - sendExtensionMessage("getInlineMenuTotpFeatureFlag"), sendExtensionMessage("getUserPremiumStatus"), - ]).then(([fieldQualificationFlag, totpFeatureFlag, premiumStatus]) => { + ]).then(([fieldQualificationFlag, premiumStatus]) => { this.inlineMenuFieldQualificationFlagSet = !!fieldQualificationFlag?.result; - this.inlineMenuTotpFeatureFlag = !!totpFeatureFlag?.result; this.premiumEnabled = !!premiumStatus?.result; }); } @@ -179,7 +177,7 @@ export class InlineMenuFieldQualificationService /** * Totp inline menu is available only for premium users. */ - if (this.inlineMenuTotpFeatureFlag && this.premiumEnabled) { + if (this.premiumEnabled) { const isTotpField = this.isTotpField(field); // Autofill does not fill totp inputs with a "password" `type` attribute value const passwordType = field.type === "password"; diff --git a/apps/browser/src/autofill/shared/styles/variables.scss b/apps/browser/src/autofill/shared/styles/variables.scss index 12d55ad8be6..ae6a060798a 100644 --- a/apps/browser/src/autofill/shared/styles/variables.scss +++ b/apps/browser/src/autofill/shared/styles/variables.scss @@ -1,6 +1,4 @@ -@import "~nord/src/sass/nord.scss"; - -$dark-icon-themes: "theme_dark", "theme_solarizedDark", "theme_nord"; +$dark-icon-themes: "theme_dark"; $font-family-sans-serif: "DM Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; $font-family-source-code-pro: "Source Code Pro", monospace; @@ -34,14 +32,6 @@ $border-color: #ced4dc; $border-radius: 3px; $focus-outline-color: #1252a3; -$solarizedDarkBase0: #839496; -$solarizedDarkBase03: #002b36; -$solarizedDarkBase02: #073642; -$solarizedDarkBase01: #586e75; -$solarizedDarkBase2: #eee8d5; -$solarizedDarkCyan: #2aa198; -$solarizedDarkGreen: #859900; - $themes: ( light: ( textColor: $text-color-light, @@ -79,41 +69,6 @@ $themes: ( passwordSpecialColor: $password-special-color-dark, passwordNumberColor: $password-number-color-dark, ), - nord: ( - textColor: $nord5, - mutedTextColor: $nord4, - backgroundColor: $nord1, - backgroundOffsetColor: darken($nord1, 2.75%), - buttonPrimaryColor: $nord8, - primaryColor: $nord9, - textContrast: $nord2, - inputBorderColor: $nord0, - inputBackgroundColor: $nord2, - borderColor: $nord0, - focusOutlineColor: lighten($focus-outline-color, 25%), - successColor: $success-color-dark, - passkeysAuthenticating: $nord4, - passwordSpecialColor: $nord12, - passwordNumberColor: $nord8, - ), - solarizedDark: ( - textColor: $solarizedDarkBase2, - // Muted uses main text color to avoid contrast issues - mutedTextColor: $solarizedDarkBase2, - backgroundColor: $solarizedDarkBase03, - backgroundOffsetColor: darken($solarizedDarkBase03, 2.75%), - buttonPrimaryColor: $solarizedDarkCyan, - primaryColor: $solarizedDarkGreen, - textContrast: $solarizedDarkBase02, - inputBorderColor: rgba($solarizedDarkBase2, 0.2), - inputBackgroundColor: $solarizedDarkBase01, - borderColor: $solarizedDarkBase2, - focusOutlineColor: lighten($focus-outline-color, 15%), - successColor: $success-color-dark, - passkeysAuthenticating: $solarizedDarkBase2, - passwordSpecialColor: #b58900, - passwordNumberColor: $solarizedDarkCyan, - ), ); @mixin themify($themes: $themes) { diff --git a/apps/browser/src/autofill/spec/autofill-mocks.ts b/apps/browser/src/autofill/spec/autofill-mocks.ts index 6e175906d30..8279c98c5b4 100644 --- a/apps/browser/src/autofill/spec/autofill-mocks.ts +++ b/apps/browser/src/autofill/spec/autofill-mocks.ts @@ -103,6 +103,7 @@ export function createChromeTabMock(customFields = {}): chrome.tabs.Tab { selected: true, discarded: false, autoDiscardable: false, + frozen: false, groupId: 2, url: "https://jest-testing-website.com", ...customFields, diff --git a/apps/browser/src/autofill/types/index.ts b/apps/browser/src/autofill/types/index.ts index a14ef1330cc..30ebf38fef5 100644 --- a/apps/browser/src/autofill/types/index.ts +++ b/apps/browser/src/autofill/types/index.ts @@ -1,6 +1,5 @@ +import { VaultTimeout, VaultTimeoutAction } from "@bitwarden/common/key-management/vault-timeout"; import { Region } from "@bitwarden/common/platform/abstractions/environment.service"; -import { VaultTimeoutAction } from "@bitwarden/common/src/enums/vault-timeout-action.enum"; -import { VaultTimeout } from "@bitwarden/common/types/vault-timeout.type"; import { CipherType } from "@bitwarden/common/vault/enums"; export type UserSettings = { diff --git a/apps/browser/src/autofill/utils/index.ts b/apps/browser/src/autofill/utils/index.ts index 12d26914d82..614a5b014f2 100644 --- a/apps/browser/src/autofill/utils/index.ts +++ b/apps/browser/src/autofill/utils/index.ts @@ -1,5 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { FieldRect } from "../background/abstractions/overlay.background"; import { AutofillPort } from "../enums/autofill-port.enum"; import { FillableFormFieldElement, FormElementWithAttribute, FormFieldElement } from "../types"; @@ -36,7 +37,9 @@ export function requestIdleCallbackPolyfill( return globalThis.requestIdleCallback(() => callback(), options); } - return globalThis.setTimeout(() => callback(), 1); + const timeoutDelay = options?.timeout || 1; + + return globalThis.setTimeout(() => callback(), timeoutDelay); } /** @@ -545,6 +548,17 @@ export const specialCharacterToKeyMap: Record = { "/": "forwardSlashCharacterDescriptor", }; +/** + * Determines if the current rect values are not all 0. + */ +export function rectHasSize(rect: FieldRect): boolean { + if (rect.right > 0 && rect.left > 0 && rect.top > 0 && rect.bottom > 0) { + return true; + } + + return false; +} + /** * Checks if all the values corresponding to the specified keys in an object are null. * If no keys are specified, checks all keys in the object. diff --git a/apps/browser/src/background/commands.background.ts b/apps/browser/src/background/commands.background.ts index 5cf58b1653f..f09ebb6c8a1 100644 --- a/apps/browser/src/background/commands.background.ts +++ b/apps/browser/src/background/commands.background.ts @@ -1,9 +1,9 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; 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"; import { openUnlockPopout } from "../auth/popup/utils/auth-popout-window"; diff --git a/apps/browser/src/background/idle.background.ts b/apps/browser/src/background/idle.background.ts index 9e10ed974ba..90276eaea0a 100644 --- a/apps/browser/src/background/idle.background.ts +++ b/apps/browser/src/background/idle.background.ts @@ -2,12 +2,14 @@ // @ts-strict-ignore import { firstValueFrom } from "rxjs"; -import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; -import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; -import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type"; +import { + VaultTimeoutAction, + VaultTimeoutService, + VaultTimeoutSettingsService, + VaultTimeoutStringType, +} from "@bitwarden/common/key-management/vault-timeout"; +import { NotificationsService } from "@bitwarden/common/platform/notifications"; const IdleInterval = 60 * 5; // 5 minutes @@ -32,12 +34,8 @@ export default class IdleBackground { const idleHandler = (newState: string) => { if (newState === "active") { - // 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.notificationsService.reconnectFromActivity(); } else { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises this.notificationsService.disconnectFromInactivity(); } }; diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 4bec3d6cc0a..74fa6acdf79 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -1,6 +1,8 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Subject, filter, firstValueFrom, map, merge, timeout } from "rxjs"; +import "core-js/proposals/explicit-resource-management"; + +import { filter, firstValueFrom, map, merge, Subject, timeout } from "rxjs"; import { CollectionService, DefaultCollectionService } from "@bitwarden/admin-console/common"; import { @@ -18,25 +20,20 @@ import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstracti import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service"; import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service"; -import { NotificationsService as NotificationsServiceAbstraction } from "@bitwarden/common/abstractions/notifications.service"; import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abstractions/search.service"; -import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { InternalOrganizationServiceAbstraction } 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 as InternalPolicyServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { ProviderService as ProviderServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/services/organization/organization.service"; +import { DefaultOrganizationService } from "@bitwarden/common/admin-console/services/organization/default-organization.service"; import { PolicyApiService } from "@bitwarden/common/admin-console/services/policy/policy-api.service"; import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service"; import { ProviderService } from "@bitwarden/common/admin-console/services/provider.service"; import { AccountService as AccountServiceAbstraction } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service"; import { AvatarService as AvatarServiceAbstraction } from "@bitwarden/common/auth/abstractions/avatar.service"; -import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; -import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarden/common/auth/abstractions/key-connector.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/abstractions/token.service"; import { UserVerificationApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification-api.service.abstraction"; @@ -46,11 +43,8 @@ import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/for import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service"; import { AuthService } from "@bitwarden/common/auth/services/auth.service"; import { AvatarService } from "@bitwarden/common/auth/services/avatar.service"; -import { DeviceTrustService } from "@bitwarden/common/auth/services/device-trust.service.implementation"; import { DevicesServiceImplementation } from "@bitwarden/common/auth/services/devices/devices.service.implementation"; import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation"; -import { KeyConnectorService } from "@bitwarden/common/auth/services/key-connector.service"; -import { MasterPasswordService } from "@bitwarden/common/auth/services/master-password/master-password.service"; import { SsoLoginService } from "@bitwarden/common/auth/services/sso-login.service"; import { TokenService } from "@bitwarden/common/auth/services/token.service"; import { UserVerificationApiService } from "@bitwarden/common/auth/services/user-verification/user-verification-api.service"; @@ -71,17 +65,33 @@ import { UserNotificationSettingsService, UserNotificationSettingsServiceAbstraction, } from "@bitwarden/common/autofill/services/user-notification-settings.service"; +import { isUrlInList } from "@bitwarden/common/autofill/utils"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service"; import { ClientType } from "@bitwarden/common/enums"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { BulkEncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/bulk-encrypt.service.implementation"; +import { EncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/encrypt.service.implementation"; +import { FallbackBulkEncryptService } from "@bitwarden/common/key-management/crypto/services/fallback-bulk-encrypt.service"; +import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/multithread-encrypt.service.implementation"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; +import { DeviceTrustService } from "@bitwarden/common/key-management/device-trust/services/device-trust.service.implementation"; +import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; +import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/services/key-connector.service"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; +import { MasterPasswordService } from "@bitwarden/common/key-management/master-password/services/master-password.service"; import { DefaultProcessReloadService } from "@bitwarden/common/key-management/services/default-process-reload.service"; +import { + DefaultVaultTimeoutSettingsService, + VaultTimeoutSettingsService, + VaultTimeoutStringType, +} from "@bitwarden/common/key-management/vault-timeout"; import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service"; import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { RegionConfig } from "@bitwarden/common/platform/abstractions/environment.service"; import { Fido2ActiveRequestManager as Fido2ActiveRequestManagerAbstraction } from "@bitwarden/common/platform/abstractions/fido2/fido2-active-request-manager.abstraction"; import { Fido2AuthenticatorService as Fido2AuthenticatorServiceAbstraction } from "@bitwarden/common/platform/abstractions/fido2/fido2-authenticator.service.abstraction"; @@ -92,6 +102,7 @@ import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platfor import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service"; import { LogService as LogServiceAbstraction } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service"; import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service"; import { @@ -108,16 +119,21 @@ import { clearCaches } from "@bitwarden/common/platform/misc/sequentialize"; import { Account } from "@bitwarden/common/platform/models/domain/account"; import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { NotificationsService } from "@bitwarden/common/platform/notifications"; +// eslint-disable-next-line no-restricted-imports -- Needed for service creation +import { + DefaultNotificationsService, + SignalRConnectionService, + UnsupportedWebPushConnectionService, + WebPushNotificationsApiService, + WorkerWebPushConnectionService, +} from "@bitwarden/common/platform/notifications/internal"; import { ScheduledTaskNames } from "@bitwarden/common/platform/scheduling"; import { AppIdService } from "@bitwarden/common/platform/services/app-id.service"; import { ConfigApiService } from "@bitwarden/common/platform/services/config/config-api.service"; import { DefaultConfigService } from "@bitwarden/common/platform/services/config/default-config.service"; import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; import { ContainerService } from "@bitwarden/common/platform/services/container.service"; -import { BulkEncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/bulk-encrypt.service.implementation"; -import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation"; -import { FallbackBulkEncryptService } from "@bitwarden/common/platform/services/cryptography/fallback-bulk-encrypt.service"; -import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/multithread-encrypt.service.implementation"; import { Fido2ActiveRequestManager } from "@bitwarden/common/platform/services/fido2/fido2-active-request-manager"; import { Fido2AuthenticatorService } from "@bitwarden/common/platform/services/fido2/fido2-authenticator.service"; import { Fido2ClientService } from "@bitwarden/common/platform/services/fido2/fido2-client.service"; @@ -125,6 +141,7 @@ import { FileUploadService } from "@bitwarden/common/platform/services/file-uplo import { KeyGenerationService } from "@bitwarden/common/platform/services/key-generation.service"; import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service"; import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner"; +import { DefaultSdkClientFactory } from "@bitwarden/common/platform/services/sdk/default-sdk-client-factory"; import { DefaultSdkService } from "@bitwarden/common/platform/services/sdk/default-sdk.service"; import { NoopSdkClientFactory } from "@bitwarden/common/platform/services/sdk/noop-sdk-client-factory"; import { StateService } from "@bitwarden/common/platform/services/state.service"; @@ -157,9 +174,7 @@ import { ApiService } from "@bitwarden/common/services/api.service"; import { AuditService } from "@bitwarden/common/services/audit.service"; import { EventCollectionService } from "@bitwarden/common/services/event/event-collection.service"; import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service"; -import { NotificationsService } from "@bitwarden/common/services/notifications.service"; import { SearchService } from "@bitwarden/common/services/search.service"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vault-timeout/vault-timeout-settings.service"; import { PasswordStrengthService, PasswordStrengthServiceAbstraction, @@ -170,7 +185,6 @@ import { SendStateProvider } from "@bitwarden/common/tools/send/services/send-st import { SendService } from "@bitwarden/common/tools/send/services/send.service"; import { InternalSendService as InternalSendServiceAbstraction } from "@bitwarden/common/tools/send/services/send.service.abstraction"; import { UserId } from "@bitwarden/common/types/guid"; -import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type"; import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherFileUploadService as CipherFileUploadServiceAbstraction } from "@bitwarden/common/vault/abstractions/file-upload/cipher-file-upload.service"; import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; @@ -189,23 +203,23 @@ import { FolderService } from "@bitwarden/common/vault/services/folder/folder.se import { TotpService } from "@bitwarden/common/vault/services/totp.service"; import { VaultSettingsService } from "@bitwarden/common/vault/services/vault-settings/vault-settings.service"; import { - PasswordGenerationServiceAbstraction, - UsernameGenerationServiceAbstraction, legacyPasswordGenerationServiceFactory, legacyUsernameGenerationServiceFactory, + PasswordGenerationServiceAbstraction, + UsernameGenerationServiceAbstraction, } from "@bitwarden/generator-legacy"; import { ImportApiService, ImportApiServiceAbstraction, ImportService, ImportServiceAbstraction, -} from "@bitwarden/importer/core"; +} from "@bitwarden/importer-core"; import { - BiometricStateService, BiometricsService, + BiometricStateService, DefaultBiometricStateService, - DefaultKeyService, DefaultKdfConfigService, + DefaultKeyService, KdfConfigService, KeyService as KeyServiceAbstraction, } from "@bitwarden/key-management"; @@ -242,6 +256,7 @@ import AutofillService from "../autofill/services/autofill.service"; import { InlineMenuFieldQualificationService } from "../autofill/services/inline-menu-field-qualification.service"; import { SafariApp } from "../browser/safariApp"; import { BackgroundBrowserBiometricsService } from "../key-management/biometrics/background-browser-biometrics.service"; +import VaultTimeoutService from "../key-management/vault-timeout/vault-timeout.service"; import { BrowserApi } from "../platform/browser/browser-api"; import { flagEnabled } from "../platform/flags"; import { UpdateBadge } from "../platform/listeners/update-badge"; @@ -252,6 +267,7 @@ import { OffscreenDocumentService } from "../platform/offscreen-document/abstrac import { DefaultOffscreenDocumentService } from "../platform/offscreen-document/offscreen-document.service"; import { BrowserTaskSchedulerService } from "../platform/services/abstractions/browser-task-scheduler.service"; import { BrowserEnvironmentService } from "../platform/services/browser-environment.service"; +import BrowserInitialInstallService from "../platform/services/browser-initial-install.service"; import BrowserLocalStorageService from "../platform/services/browser-local-storage.service"; import BrowserMemoryStorageService from "../platform/services/browser-memory-storage.service"; import { BrowserScriptInjectorService } from "../platform/services/browser-script-injector.service"; @@ -260,15 +276,13 @@ import { LocalBackedSessionStorageService } from "../platform/services/local-bac import { BackgroundPlatformUtilsService } from "../platform/services/platform-utils/background-platform-utils.service"; import { BrowserPlatformUtilsService } from "../platform/services/platform-utils/browser-platform-utils.service"; import { PopupViewCacheBackgroundService } from "../platform/services/popup-view-cache-background.service"; -import { BrowserSdkClientFactory } from "../platform/services/sdk/browser-sdk-client-factory"; +import { BrowserSdkLoadService } from "../platform/services/sdk/browser-sdk-load.service"; import { BackgroundTaskSchedulerService } from "../platform/services/task-scheduler/background-task-scheduler.service"; import { BackgroundMemoryStorageService } from "../platform/storage/background-memory-storage.service"; import { BrowserStorageServiceProvider } from "../platform/storage/browser-storage-service.provider"; import { OffscreenStorageService } from "../platform/storage/offscreen-storage.service"; import { SyncServiceListener } from "../platform/sync/sync-service.listener"; import { fromChromeRuntimeMessaging } from "../platform/utils/from-chrome-runtime-messaging"; -import VaultTimeoutService from "../services/vault-timeout/vault-timeout.service"; -import FilelessImporterBackground from "../tools/background/fileless-importer.background"; import { VaultFilterService } from "../vault/services/vault-filter.service"; import CommandsBackground from "./commands.background"; @@ -299,7 +313,7 @@ export default class MainBackground { userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction; collectionService: CollectionService; vaultTimeoutService?: VaultTimeoutService; - vaultTimeoutSettingsService: VaultTimeoutSettingsServiceAbstraction; + vaultTimeoutSettingsService: VaultTimeoutSettingsService; passwordGenerationService: PasswordGenerationServiceAbstraction; syncService: SyncService; passwordStrengthService: PasswordStrengthServiceAbstraction; @@ -313,7 +327,7 @@ export default class MainBackground { importService: ImportServiceAbstraction; exportService: VaultExportServiceAbstraction; searchService: SearchServiceAbstraction; - notificationsService: NotificationsServiceAbstraction; + notificationsService: NotificationsService; stateService: StateServiceAbstraction; userNotificationSettingsService: UserNotificationSettingsServiceAbstraction; autofillSettingsService: AutofillSettingsServiceAbstraction; @@ -377,9 +391,13 @@ export default class MainBackground { kdfConfigService: KdfConfigService; offscreenDocumentService: OffscreenDocumentService; syncServiceListener: SyncServiceListener; + browserInitialInstallService: BrowserInitialInstallService; + + webPushConnectionService: WorkerWebPushConnectionService | UnsupportedWebPushConnectionService; themeStateService: DefaultThemeStateService; autoSubmitLoginBackground: AutoSubmitLoginBackground; sdkService: SdkService; + sdkLoadService: SdkLoadService; cipherAuthorizationService: CipherAuthorizationService; inlineMenuFieldQualificationService: InlineMenuFieldQualificationService; @@ -393,7 +411,6 @@ export default class MainBackground { private notificationBackground: NotificationBackground; private overlayBackground: OverlayBackgroundInterface; private overlayNotificationsBackground: OverlayNotificationsBackgroundInterface; - private filelessImporterBackground: FilelessImporterBackground; private runtimeBackground: RuntimeBackground; private tabsBackground: TabsBackground; private webRequestBackground: WebRequestBackground; @@ -407,11 +424,6 @@ export default class MainBackground { constructor() { // Services const lockedCallback = async (userId?: string) => { - if (this.notificationsService != null) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.notificationsService.updateConnection(false); - } await this.refreshBadge(); await this.refreshMenu(true); if (this.systemService != null) { @@ -556,6 +568,7 @@ export default class MainBackground { this.messagingService, this.logService, this.globalStateProvider, + this.singleUserStateProvider, ); this.activeUserStateProvider = new DefaultActiveUserStateProvider( this.accountService, @@ -601,6 +614,7 @@ export default class MainBackground { this.popupViewCacheBackgroundService = new PopupViewCacheBackgroundService( messageListener, this.globalStateProvider, + this.taskSchedulerService, ); const migrationRunner = new MigrationRunner( @@ -632,11 +646,6 @@ export default class MainBackground { this.i18nService = new I18nService(BrowserApi.getUILanguage(), this.globalStateProvider); - this.biometricsService = new BackgroundBrowserBiometricsService( - runtimeNativeMessagingBackground, - this.logService, - ); - this.kdfConfigService = new DefaultKdfConfigService(this.stateProvider); this.pinService = new PinService( @@ -665,13 +674,21 @@ export default class MainBackground { this.kdfConfigService, ); + this.biometricsService = new BackgroundBrowserBiometricsService( + runtimeNativeMessagingBackground, + this.logService, + this.keyService, + this.biometricStateService, + this.messagingService, + ); + this.appIdService = new AppIdService(this.storageService, this.logService); this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider); - this.organizationService = new OrganizationService(this.stateProvider); + this.organizationService = new DefaultOrganizationService(this.stateProvider); this.policyService = new PolicyService(this.stateProvider, this.organizationService); - this.vaultTimeoutSettingsService = new VaultTimeoutSettingsService( + this.vaultTimeoutSettingsService = new DefaultVaultTimeoutSettingsService( this.accountService, this.pinService, this.userDecryptionOptionsService, @@ -729,8 +746,9 @@ export default class MainBackground { ); const sdkClientFactory = flagEnabled("sdk") - ? new BrowserSdkClientFactory(this.logService) + ? new DefaultSdkClientFactory() : new NoopSdkClientFactory(); + this.sdkLoadService = new BrowserSdkLoadService(this.logService); this.sdkService = new DefaultSdkService( sdkClientFactory, this.environmentService, @@ -799,7 +817,7 @@ export default class MainBackground { this.apiService, ); - this.ssoLoginService = new SsoLoginService(this.stateProvider); + this.ssoLoginService = new SsoLoginService(this.stateProvider, this.logService); this.userVerificationApiService = new UserVerificationApiService(this.apiService); @@ -818,10 +836,7 @@ export default class MainBackground { this.configService, ); - this.themeStateService = new DefaultThemeStateService( - this.globalStateProvider, - this.configService, - ); + this.themeStateService = new DefaultThemeStateService(this.globalStateProvider); this.bulkEncryptService = new FallbackBulkEncryptService(this.encryptService); @@ -962,7 +977,7 @@ export default class MainBackground { this.authService, this.accountService, ); - this.totpService = new TotpService(this.cryptoFunctionService, this.logService); + this.totpService = new TotpService(this.sdkService); this.scriptInjectorService = new BrowserScriptInjectorService( this.domainSettingsService, @@ -999,6 +1014,7 @@ export default class MainBackground { this.encryptService, this.pinService, this.accountService, + this.sdkService, ); this.individualVaultExportService = new IndividualVaultExportService( @@ -1029,17 +1045,36 @@ export default class MainBackground { this.organizationVaultExportService, ); - this.notificationsService = new NotificationsService( + this.browserInitialInstallService = new BrowserInitialInstallService(this.stateProvider); + + if (BrowserApi.isManifestVersion(3)) { + const registration = (self as unknown as { registration: ServiceWorkerRegistration }) + ?.registration; + + if (registration != null) { + this.webPushConnectionService = new WorkerWebPushConnectionService( + this.configService, + new WebPushNotificationsApiService(this.apiService, this.appIdService), + registration, + ); + } else { + this.webPushConnectionService = new UnsupportedWebPushConnectionService(); + } + } else { + this.webPushConnectionService = new UnsupportedWebPushConnectionService(); + } + + this.notificationsService = new DefaultNotificationsService( this.logService, this.syncService, this.appIdService, - this.apiService, this.environmentService, logoutCallback, - this.stateService, - this.authService, this.messagingService, - this.taskSchedulerService, + this.accountService, + new SignalRConnectionService(this.apiService, this.logService), + this.authService, + this.webPushConnectionService, ); this.fido2UserInterfaceService = new BrowserFido2UserInterfaceService(this.authService); @@ -1115,6 +1150,7 @@ export default class MainBackground { this.accountService, lockService, this.billingAccountProfileStateService, + this.browserInitialInstallService, ); this.nativeMessagingBackground = new NativeMessagingBackground( this.keyService, @@ -1137,18 +1173,19 @@ export default class MainBackground { () => this.generatePasswordToClipboard(), ); this.notificationBackground = new NotificationBackground( + this.accountService, + this.authService, this.autofillService, this.cipherService, - this.authService, - this.policyService, - this.folderService, - this.userNotificationSettingsService, + this.configService, this.domainSettingsService, this.environmentService, + this.folderService, this.logService, + this.organizationService, + this.policyService, this.themeStateService, - this.configService, - this.accountService, + this.userNotificationSettingsService, ); this.overlayNotificationsBackground = new OverlayNotificationsBackground( @@ -1157,16 +1194,6 @@ export default class MainBackground { this.notificationBackground, ); - this.filelessImporterBackground = new FilelessImporterBackground( - this.configService, - this.authService, - this.policyService, - this.notificationBackground, - this.importService, - this.syncService, - this.scriptInjectorService, - ); - this.autoSubmitLoginBackground = new AutoSubmitLoginBackground( this.logService, this.autofillService, @@ -1241,6 +1268,7 @@ export default class MainBackground { this.mainContextMenuHandler, this.authService, this.cipherService, + this.accountService, ); if (chrome.webRequest != null && chrome.webRequest.onAuthRequired != null) { @@ -1248,6 +1276,7 @@ export default class MainBackground { this.platformUtilsService, this.cipherService, this.authService, + this.accountService, chrome.webRequest, ); } @@ -1257,14 +1286,20 @@ export default class MainBackground { this.cipherAuthorizationService = new DefaultCipherAuthorizationService( this.collectionService, this.organizationService, + this.accountService, + this.configService, ); this.inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService(); } async bootstrap() { + if (this.webPushConnectionService instanceof WorkerWebPushConnectionService) { + this.webPushConnectionService.start(); + } this.containerService.attachToGlobal(self); + await this.sdkLoadService.loadAndInit(); // Only the "true" background should run migrations await this.stateService.init({ runMigrations: true }); @@ -1286,14 +1321,13 @@ export default class MainBackground { await (this.i18nService as I18nService).init(); (this.eventUploadService as EventUploadService).init(true); - this.popupViewCacheBackgroundService.startObservingTabChanges(); + this.popupViewCacheBackgroundService.startObservingMessages(); await this.vaultTimeoutService.init(true); this.fido2Background.init(); await this.runtimeBackground.init(); await this.notificationBackground.init(); this.overlayNotificationsBackground.init(); - this.filelessImporterBackground.init(); this.commandsBackground.init(); this.contextMenusBackground?.init(); this.idleBackground.init(); @@ -1328,12 +1362,7 @@ export default class MainBackground { setTimeout(async () => { await this.refreshBadge(); await this.fullSync(false); - this.taskSchedulerService.setInterval( - ScheduledTaskNames.scheduleNextSyncInterval, - 5 * 60 * 1000, // check every 5 minutes - ); - setTimeout(() => this.notificationsService.init(), 2500); - await this.taskSchedulerService.verifyAlarmsState(); + this.notificationsService.startListening(); resolve(); }, 500); }); @@ -1356,17 +1385,34 @@ export default class MainBackground { return; } - await this.mainContextMenuHandler?.init(); + const contextMenuIsEnabled = await this.mainContextMenuHandler?.init(); + if (!contextMenuIsEnabled) { + this.onUpdatedRan = this.onReplacedRan = false; + return; + } const tab = await BrowserApi.getTabFromCurrentWindow(); + if (tab) { - await this.cipherContextMenuHandler?.update(tab.url); + const currentUrlIsBlocked = await firstValueFrom( + this.domainSettingsService.blockedInteractionsUris$.pipe( + map((blockedInteractionsUrls) => { + if (blockedInteractionsUrls && tab?.url?.length) { + return isUrlInList(tab.url, blockedInteractionsUrls); + } + + return false; + }), + ), + ); + + await this.cipherContextMenuHandler?.update(tab.url, currentUrlIsBlocked); this.onUpdatedRan = this.onReplacedRan = false; } } async updateOverlayCiphers() { - // overlayBackground null in popup only contexts + // `overlayBackground` is null in popup only contexts if (this.overlayBackground) { await this.overlayBackground.updateOverlayCiphers(); } @@ -1412,7 +1458,6 @@ export default class MainBackground { ForceSetPasswordReason.None; await this.systemService.clearPendingClipboard(); - await this.notificationsService.updateConnection(false); if (nextAccountStatus === AuthenticationStatus.LoggedOut) { this.messagingService.send("goHome"); @@ -1516,7 +1561,6 @@ export default class MainBackground { } await this.refreshBadge(); await this.mainContextMenuHandler?.noAccess(); - await this.notificationsService.updateConnection(false); await this.systemService.clearPendingClipboard(); await this.processReloadService.startProcessReload(this.authService); } @@ -1552,13 +1596,16 @@ export default class MainBackground { } async openPopup() { - // Chrome APIs cannot open popup + const browserAction = BrowserApi.getBrowserAction(); - // TODO: Do we need to open this popup? - if (!this.isSafari) { + if ("openPopup" in browserAction && typeof browserAction.openPopup === "function") { + await browserAction.openPopup(); return; } - await SafariApp.sendMessageToApp("showPopover", null, true); + + if (this.isSafari) { + await SafariApp.sendMessageToApp("showPopover", null, true); + } } async reseedStorage() { @@ -1622,6 +1669,7 @@ export default class MainBackground { this.i18nService, this.platformUtilsService, this.themeStateService, + this.accountService, ); } else { this.overlayBackground = new OverlayBackground( @@ -1639,6 +1687,7 @@ export default class MainBackground { this.inlineMenuFieldQualificationService, this.themeStateService, this.totpService, + this.accountService, () => this.generatePassword(), (password) => this.addPasswordToHistory(password), ); diff --git a/apps/browser/src/background/nativeMessaging.background.ts b/apps/browser/src/background/nativeMessaging.background.ts index 116d048d2e8..8100ff3cffa 100644 --- a/apps/browser/src/background/nativeMessaging.background.ts +++ b/apps/browser/src/background/nativeMessaging.background.ts @@ -1,12 +1,10 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { delay, filter, firstValueFrom, from, map, race, timer } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; 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"; @@ -57,26 +55,29 @@ type ReceiveMessageOuter = { messageId?: number; // Should only have one of these. - message?: EncString; + message?: ReceiveMessage | EncString; sharedSecret?: string; }; type Callback = { - resolver: any; - rejecter: any; + resolver: (value?: unknown) => void; + rejecter: (reason?: any) => void; +}; + +type SecureChannel = { + privateKey: Uint8Array; + publicKey: Uint8Array; + sharedSecret?: SymmetricCryptoKey; + setupResolve: (value?: unknown) => void; }; export class NativeMessagingBackground { connected = false; - private connecting: boolean; - private port: browser.runtime.Port | chrome.runtime.Port; + private connecting: boolean = false; + private port?: browser.runtime.Port | chrome.runtime.Port; + private appId?: string; - private privateKey: Uint8Array = null; - private publicKey: Uint8Array = null; - private secureSetupResolve: any = null; - private sharedSecret: SymmetricCryptoKey; - private appId: string; - private validatingFingerprint: boolean; + private secureChannel?: SecureChannel; private messageId = 0; private callbacks = new Map(); @@ -108,11 +109,13 @@ export class NativeMessagingBackground { async connect() { this.logService.info("[Native Messaging IPC] Connecting to Bitwarden Desktop app..."); - this.appId = await this.appIdService.getAppId(); + const appId = await this.appIdService.getAppId(); + this.appId = appId; await this.biometricStateService.setFingerprintValidated(false); return new Promise((resolve, reject) => { - this.port = BrowserApi.connectNative("com.8bit.bitwarden"); + const port = BrowserApi.connectNative("com.8bit.bitwarden"); + this.port = port; this.connecting = true; @@ -131,7 +134,8 @@ export class NativeMessagingBackground { connectedCallback(); } - this.port.onMessage.addListener(async (message: ReceiveMessageOuter) => { + port.onMessage.addListener(async (messageRaw: unknown) => { + const message = messageRaw as ReceiveMessageOuter; switch (message.command) { case "connected": connectedCallback(); @@ -142,7 +146,7 @@ export class NativeMessagingBackground { reject(new Error("startDesktop")); } this.connected = false; - this.port.disconnect(); + port.disconnect(); // reject all for (const callback of this.callbacks.values()) { callback.rejecter("disconnected"); @@ -151,22 +155,31 @@ export class NativeMessagingBackground { break; case "setupEncryption": { // Ignore since it belongs to another device - if (message.appId !== this.appId) { + if (message.appId !== appId) { + return; + } + + if (message.sharedSecret == null) { + this.logService.info( + "[Native Messaging IPC] Unable to create secureChannel channel, no shared secret", + ); + return; + } + if (this.secureChannel == null) { + this.logService.info( + "[Native Messaging IPC] Unable to create secureChannel channel, no secureChannel communication setup", + ); return; } const encrypted = Utils.fromB64ToArray(message.sharedSecret); const decrypted = await this.cryptoFunctionService.rsaDecrypt( encrypted, - this.privateKey, + this.secureChannel.privateKey, HashAlgorithmForEncryption, ); - if (this.validatingFingerprint) { - this.validatingFingerprint = false; - await this.biometricStateService.setFingerprintValidated(true); - } - this.sharedSecret = new SymmetricCryptoKey(decrypted); + this.secureChannel.sharedSecret = new SymmetricCryptoKey(decrypted); this.logService.info("[Native Messaging IPC] Secure channel established"); if ("messageId" in message) { @@ -177,56 +190,70 @@ export class NativeMessagingBackground { this.isConnectedToOutdatedDesktopClient = true; } - this.secureSetupResolve(); + this.secureChannel.setupResolve(); break; } case "invalidateEncryption": // Ignore since it belongs to another device - if (message.appId !== this.appId) { + if (message.appId !== appId) { return; } this.logService.warning( "[Native Messaging IPC] Secure channel encountered an error; disconnecting and wiping keys...", ); - this.sharedSecret = null; - this.privateKey = null; + this.secureChannel = undefined; this.connected = false; - if (this.callbacks.has(message.messageId)) { - this.callbacks.get(message.messageId).rejecter({ - message: "invalidateEncryption", - }); + if (message.messageId != null) { + if (this.callbacks.has(message.messageId)) { + this.callbacks.get(message.messageId)?.rejecter({ + message: "invalidateEncryption", + }); + } } return; case "verifyFingerprint": { - if (this.sharedSecret == null) { - this.logService.info( - "[Native Messaging IPC] Desktop app requested trust verification by fingerprint.", - ); - this.validatingFingerprint = true; - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.showFingerprintDialog(); - } + this.logService.info("[Native Messaging IPC] Legacy app is requesting fingerprint"); + this.messagingService.send("showUpdateDesktopAppOrDisableFingerprintDialog", {}); + break; + } + case "verifyDesktopIPCFingerprint": { + this.logService.info( + "[Native Messaging IPC] Desktop app requested trust verification by fingerprint.", + ); + await this.showFingerprintDialog(); + break; + } + case "verifiedDesktopIPCFingerprint": { + await this.biometricStateService.setFingerprintValidated(true); + this.messagingService.send("hideNativeMessagingFingerprintDialog", {}); + break; + } + case "rejectedDesktopIPCFingerprint": { + this.messagingService.send("hideNativeMessagingFingerprintDialog", {}); break; } case "wrongUserId": - if (this.callbacks.has(message.messageId)) { - this.callbacks.get(message.messageId).rejecter({ - message: "wrongUserId", - }); + if (message.messageId != null) { + if (this.callbacks.has(message.messageId)) { + this.callbacks.get(message.messageId)?.rejecter({ + message: "wrongUserId", + }); + } } return; default: // Ignore since it belongs to another device - if (!this.platformUtilsService.isSafari() && message.appId !== this.appId) { + if (!this.platformUtilsService.isSafari() && message.appId !== appId) { return; } - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.onMessage(message.message); + if (message.message != null) { + // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this.onMessage(message.message); + } } }); @@ -235,16 +262,15 @@ export class NativeMessagingBackground { if (BrowserApi.isWebExtensionsApi) { error = p.error.message; } else { - error = chrome.runtime.lastError.message; + error = chrome.runtime.lastError?.message; } - this.sharedSecret = null; - this.privateKey = null; + this.secureChannel = undefined; this.connected = false; this.logService.error("NativeMessaging port disconnected because of error: " + error); - const reason = error != null ? "desktopIntegrationDisabled" : null; + const reason = error != null ? "desktopIntegrationDisabled" : undefined; reject(new Error(reason)); }); }); @@ -257,7 +283,7 @@ export class NativeMessagingBackground { message.command == BiometricsCommands.Unlock || message.command == BiometricsCommands.IsAvailable ) { - // TODO remove after 2025.01 + // TODO remove after 2025.3 // wait until there is no other callbacks, or timeout const call = await firstValueFrom( race( @@ -288,13 +314,13 @@ export class NativeMessagingBackground { ); const callback = this.callbacks.get(messageId); this.callbacks.delete(messageId); - callback.rejecter("errorConnecting"); + callback?.rejecter("errorConnecting"); } setTimeout(() => { if (this.callbacks.has(messageId)) { this.logService.info("[Native Messaging IPC] Message timed out and received no response"); - this.callbacks.get(messageId).rejecter({ + this.callbacks.get(messageId)!.rejecter({ message: "timeout", }); this.callbacks.delete(messageId); @@ -315,16 +341,19 @@ export class NativeMessagingBackground { if (this.platformUtilsService.isSafari()) { this.postMessage(message as any); } else { - this.postMessage({ appId: this.appId, message: await this.encryptMessage(message) }); + this.postMessage({ appId: this.appId!, message: await this.encryptMessage(message) }); } } async encryptMessage(message: Message) { - if (this.sharedSecret == null) { + if (this.secureChannel?.sharedSecret == null) { await this.secureCommunication(); } - return await this.encryptService.encrypt(JSON.stringify(message), this.sharedSecret); + return await this.encryptService.encrypt( + JSON.stringify(message), + this.secureChannel!.sharedSecret!, + ); } private postMessage(message: OuterMessage, messageId?: number) { @@ -341,32 +370,38 @@ export class NativeMessagingBackground { mac: message.message.mac, }; } - this.port.postMessage(msg); + this.port!.postMessage(msg); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.logService.info( "[Native Messaging IPC] Disconnected from Bitwarden Desktop app because of the native port disconnecting.", ); - this.sharedSecret = null; - this.privateKey = null; + this.secureChannel = undefined; this.connected = false; - if (this.callbacks.has(messageId)) { - this.callbacks.get(messageId).rejecter("invalidateEncryption"); + if (messageId != null && this.callbacks.has(messageId)) { + this.callbacks.get(messageId)!.rejecter("invalidateEncryption"); } } } private async onMessage(rawMessage: ReceiveMessage | EncString) { - let message = rawMessage as ReceiveMessage; + let message: ReceiveMessage; if (!this.platformUtilsService.isSafari()) { + if (this.secureChannel?.sharedSecret == null) { + return; + } message = JSON.parse( await this.encryptService.decryptToUtf8( rawMessage as EncString, - this.sharedSecret, + this.secureChannel.sharedSecret, "ipc-desktop-ipc-channel-key", ), ); + } else { + message = rawMessage as ReceiveMessage; } if (Math.abs(message.timestamp - Date.now()) > MessageValidTimeout) { @@ -383,15 +418,17 @@ export class NativeMessagingBackground { this.logService.info( `[Native Messaging IPC] Received legacy message of type ${message.command}`, ); - const messageId = this.callbacks.keys().next().value; - const resolver = this.callbacks.get(messageId); - this.callbacks.delete(messageId); - resolver.resolver(message); + const messageId: number | undefined = this.callbacks.keys().next().value; + if (messageId != null) { + const resolver = this.callbacks.get(messageId); + this.callbacks.delete(messageId); + resolver!.resolver(message); + } return; } if (this.callbacks.has(messageId)) { - this.callbacks.get(messageId).resolver(message); + this.callbacks.get(messageId)!.resolver(message); } else { this.logService.info("[Native Messaging IPC] Received message without a callback", message); } @@ -399,8 +436,6 @@ export class NativeMessagingBackground { private async secureCommunication() { const [publicKey, privateKey] = await this.cryptoFunctionService.rsaGenerateKeyPair(2048); - this.publicKey = publicKey; - this.privateKey = privateKey; const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. @@ -412,7 +447,13 @@ export class NativeMessagingBackground { messageId: this.messageId++, }); - return new Promise((resolve, reject) => (this.secureSetupResolve = resolve)); + return new Promise((resolve) => { + this.secureChannel = { + publicKey, + privateKey, + setupResolve: resolve, + }; + }); } private async sendUnencrypted(message: Message) { @@ -422,16 +463,19 @@ export class NativeMessagingBackground { message.timestamp = Date.now(); - this.postMessage({ appId: this.appId, message: message }); + this.postMessage({ appId: this.appId!, message: message }); } private async showFingerprintDialog() { + if (this.secureChannel?.publicKey == null) { + return; + } const fingerprint = await this.keyService.getFingerprint( - (await firstValueFrom(this.accountService.activeAccount$))?.id, - this.publicKey, + this.appId!, + this.secureChannel.publicKey, ); - this.messagingService.send("showNativeMessagingFinterprintDialog", { + this.messagingService.send("showNativeMessagingFingerprintDialog", { fingerprint: fingerprint, }); } diff --git a/apps/browser/src/background/runtime.background.ts b/apps/browser/src/background/runtime.background.ts index 863ca26b36e..7db72f38139 100644 --- a/apps/browser/src/background/runtime.background.ts +++ b/apps/browser/src/background/runtime.background.ts @@ -3,7 +3,6 @@ import { firstValueFrom, map, mergeMap } from "rxjs"; import { LockService } from "@bitwarden/auth/common"; -import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AutofillOverlayVisibility, ExtensionCommand } from "@bitwarden/common/autofill/constants"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; @@ -16,18 +15,20 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag import { MessageListener, isExternalMessage } from "@bitwarden/common/platform/messaging"; import { devFlagEnabled } from "@bitwarden/common/platform/misc/flags"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { NotificationsService } from "@bitwarden/common/platform/notifications"; import { CipherType } from "@bitwarden/common/vault/enums"; import { BiometricsCommands } from "@bitwarden/key-management"; import { closeUnlockPopout, openSsoAuthResultPopout, - openTwoFactorAuthPopout, + openTwoFactorAuthWebAuthnPopout, } from "../auth/popup/utils/auth-popout-window"; import { LockedVaultPendingNotificationsData } from "../autofill/background/abstractions/notification.background"; import { AutofillService } from "../autofill/services/abstractions/autofill.service"; import { BrowserApi } from "../platform/browser/browser-api"; import { BrowserEnvironmentService } from "../platform/services/browser-environment.service"; +import BrowserInitialInstallService from "../platform/services/browser-initial-install.service"; import { BrowserPlatformUtilsService } from "../platform/services/platform-utils/browser-platform-utils.service"; import MainBackground from "./main.background"; @@ -53,6 +54,7 @@ export default class RuntimeBackground { private accountService: AccountService, private readonly lockService: LockService, private billingAccountProfileStateService: BillingAccountProfileStateService, + private browserInitialInstallService: BrowserInitialInstallService, ) { // onInstalled listener must be wired up before anything else, so we do it in the ctor chrome.runtime.onInstalled.addListener((details: any) => { @@ -78,7 +80,6 @@ export default class RuntimeBackground { BiometricsCommands.GetBiometricsStatusForUser, "getUseTreeWalkerApiForPageDetailsCollectionFeatureFlag", "getInlineMenuFieldQualificationFeatureFlag", - "getInlineMenuTotpFeatureFlag", "getUserPremiumStatus", ]; @@ -217,9 +218,6 @@ export default class RuntimeBackground { ); return result; } - case "getInlineMenuTotpFeatureFlag": { - return await this.configService.getFeatureFlag(FeatureFlag.InlineMenuTotp); - } } } @@ -240,7 +238,6 @@ export default class RuntimeBackground { await closeUnlockPopout(); } - await this.notificationsService.updateConnection(msg.command === "loggedIn"); this.processReloadSerivce.cancelProcessReload(); if (item) { @@ -259,9 +256,7 @@ export default class RuntimeBackground { await this.main.refreshBadge(); await this.main.refreshMenu(false); - if (await this.configService.getFeatureFlag(FeatureFlag.ExtensionRefresh)) { - await this.autofillService.setAutoFillOnPageLoadOrgPolicy(); - } + await this.autofillService.setAutoFillOnPageLoadOrgPolicy(); break; } case "addToLockedVaultPendingNotifications": @@ -288,13 +283,11 @@ export default class RuntimeBackground { await this.configService.ensureConfigFetched(); await this.main.updateOverlayCiphers(); - if (await this.configService.getFeatureFlag(FeatureFlag.ExtensionRefresh)) { - await this.autofillService.setAutoFillOnPageLoadOrgPolicy(); - } + await this.autofillService.setAutoFillOnPageLoadOrgPolicy(); } break; case "openPopup": - await this.main.openPopup(); + await this.openPopup(); break; case "bgUpdateContextMenu": case "editedCipher": @@ -337,7 +330,7 @@ export default class RuntimeBackground { return; } - await openTwoFactorAuthPopout(msg); + await openTwoFactorAuthWebAuthnPopout(msg); break; } case "reloadPopup": @@ -391,7 +384,10 @@ export default class RuntimeBackground { void this.autofillService.loadAutofillScriptsOnInstall(); if (this.onInstalledReason != null) { - if (this.onInstalledReason === "install") { + if ( + this.onInstalledReason === "install" && + !(await firstValueFrom(this.browserInitialInstallService.extensionInstalled$)) + ) { if (!devFlagEnabled("skipWelcomeOnInstall")) { void BrowserApi.createNewTab("https://bitwarden.com/browser-start/"); } @@ -403,6 +399,7 @@ export default class RuntimeBackground { if (await this.environmentService.hasManagedEnvironment()) { await this.environmentService.setUrlsToManagedEnvironment(); } + await this.browserInitialInstallService.setExtensionInstalled(true); } this.onInstalledReason = null; @@ -410,13 +407,40 @@ export default class RuntimeBackground { }, 100); } + /** Returns the browser tabs that have the web vault open */ + private async getBwTabs() { + const env = await firstValueFrom(this.environmentService.environment$); + const vaultUrl = env.getWebVaultUrl(); + const urlObj = new URL(vaultUrl); + + return await BrowserApi.tabsQuery({ url: `${urlObj.href}*` }); + } + + private async openPopup() { + await this.main.openPopup(); + + const announcePopupOpen = async () => { + const isOpen = await this.platformUtilsService.isViewOpen(); + const tabs = await this.getBwTabs(); + + if (isOpen && tabs.length > 0) { + // Send message to all vault tabs that the extension has opened + for (const tab of tabs) { + await BrowserApi.executeScriptInTab(tab.id, { + file: "content/send-popup-open-message.js", + runAt: "document_end", + }); + } + } + }; + + // Give the popup a buffer to open + setTimeout(announcePopupOpen, 100); + } + async sendBwInstalledMessageToVault() { try { - const env = await firstValueFrom(this.environmentService.environment$); - const vaultUrl = env.getWebVaultUrl(); - const urlObj = new URL(vaultUrl); - - const tabs = await BrowserApi.tabsQuery({ url: `${urlObj.href}*` }); + const tabs = await this.getBwTabs(); if (!tabs?.length) { return; 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 f578de8ae7a..4f87a0f6781 100644 --- a/apps/browser/src/billing/popup/settings/premium-v2.component.html +++ b/apps/browser/src/billing/popup/settings/premium-v2.component.html @@ -45,14 +45,14 @@ #refreshBtn type="button" (click)="refresh()" - [disabled]="$any(refreshBtn).loading" + [disabled]="$any(refreshBtn).loading()" [appApiAction]="refreshPromise" bitButton > - {{ "premiumRefresh" | i18n }} + {{ "premiumRefresh" | i18n }} diff --git a/apps/browser/src/billing/popup/settings/premium-v2.component.ts b/apps/browser/src/billing/popup/settings/premium-v2.component.ts index f658f71a209..ff0e8efd646 100644 --- a/apps/browser/src/billing/popup/settings/premium-v2.component.ts +++ b/apps/browser/src/billing/popup/settings/premium-v2.component.ts @@ -4,12 +4,11 @@ import { CommonModule, CurrencyPipe, Location } from "@angular/common"; import { Component } from "@angular/core"; import { RouterModule } from "@angular/router"; +import { PremiumComponent as BasePremiumComponent } from "@bitwarden/angular/billing/components/premium.component"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { PremiumComponent as BasePremiumComponent } from "@bitwarden/angular/vault/components/premium.component"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -20,6 +19,7 @@ import { DialogService, ItemModule, SectionComponent, + ToastService, } from "@bitwarden/components"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; @@ -50,24 +50,24 @@ export class PremiumV2Component extends BasePremiumComponent { i18nService: I18nService, platformUtilsService: PlatformUtilsService, apiService: ApiService, - configService: ConfigService, logService: LogService, private location: Location, private currencyPipe: CurrencyPipe, dialogService: DialogService, environmentService: EnvironmentService, billingAccountProfileStateService: BillingAccountProfileStateService, + toastService: ToastService, accountService: AccountService, ) { super( i18nService, platformUtilsService, apiService, - configService, logService, dialogService, environmentService, billingAccountProfileStateService, + toastService, accountService, ); diff --git a/apps/browser/src/services/families-policy.service.spec.ts b/apps/browser/src/billing/services/families-policy.service.spec.ts similarity index 80% rename from apps/browser/src/services/families-policy.service.spec.ts rename to apps/browser/src/billing/services/families-policy.service.spec.ts index 19291bcd825..65a861038bf 100644 --- a/apps/browser/src/services/families-policy.service.spec.ts +++ b/apps/browser/src/billing/services/families-policy.service.spec.ts @@ -6,6 +6,10 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { FamiliesPolicyService } from "./families-policy.service"; // Adjust the import as necessary @@ -13,16 +17,20 @@ describe("FamiliesPolicyService", () => { let service: FamiliesPolicyService; let organizationService: MockProxy; let policyService: MockProxy; + let accountService: FakeAccountService; + const userId = Utils.newGuid() as UserId; beforeEach(() => { organizationService = mock(); policyService = mock(); + accountService = mockAccountServiceWith(userId); TestBed.configureTestingModule({ providers: [ FamiliesPolicyService, { provide: OrganizationService, useValue: organizationService }, { provide: PolicyService, useValue: policyService }, + { provide: AccountService, useValue: accountService }, ], }); @@ -40,7 +48,7 @@ describe("FamiliesPolicyService", () => { jest.spyOn(service, "hasSingleEnterpriseOrg$").mockReturnValue(of(true)); const organizations = [{ id: "org1", canManageSponsorships: true }] as Organization[]; - organizationService.getAll$.mockReturnValue(of(organizations)); + organizationService.organizations$.mockReturnValue(of(organizations)); const policies = [{ organizationId: "org1", enabled: true }] as Policy[]; policyService.getAll$.mockReturnValue(of(policies)); @@ -53,7 +61,7 @@ describe("FamiliesPolicyService", () => { jest.spyOn(service, "hasSingleEnterpriseOrg$").mockReturnValue(of(true)); const organizations = [{ id: "org1", canManageSponsorships: true }] as Organization[]; - organizationService.getAll$.mockReturnValue(of(organizations)); + organizationService.organizations$.mockReturnValue(of(organizations)); const policies = [{ organizationId: "org1", enabled: false }] as Policy[]; policyService.getAll$.mockReturnValue(of(policies)); @@ -64,7 +72,7 @@ describe("FamiliesPolicyService", () => { it("should return true when there is exactly one enterprise organization that can manage sponsorships", async () => { const organizations = [{ id: "org1", canManageSponsorships: true }] as Organization[]; - organizationService.getAll$.mockReturnValue(of(organizations)); + organizationService.organizations$.mockReturnValue(of(organizations)); const result = await firstValueFrom(service.hasSingleEnterpriseOrg$()); expect(result).toBe(true); @@ -75,7 +83,7 @@ describe("FamiliesPolicyService", () => { { id: "org1", canManageSponsorships: true }, { id: "org2", canManageSponsorships: true }, ] as Organization[]; - organizationService.getAll$.mockReturnValue(of(organizations)); + organizationService.organizations$.mockReturnValue(of(organizations)); const result = await firstValueFrom(service.hasSingleEnterpriseOrg$()); expect(result).toBe(false); diff --git a/apps/browser/src/billing/services/families-policy.service.ts b/apps/browser/src/billing/services/families-policy.service.ts new file mode 100644 index 00000000000..755d3e84591 --- /dev/null +++ b/apps/browser/src/billing/services/families-policy.service.ts @@ -0,0 +1,65 @@ +import { Injectable } from "@angular/core"; +import { map, Observable, of, switchMap } from "rxjs"; + +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; + +@Injectable({ providedIn: "root" }) +export class FamiliesPolicyService { + constructor( + private policyService: PolicyService, + private organizationService: OrganizationService, + private accountService: AccountService, + ) {} + + hasSingleEnterpriseOrg$(): Observable { + // Retrieve all organizations the user is part of + return getUserId(this.accountService.activeAccount$).pipe( + switchMap((userId) => + this.organizationService.organizations$(userId).pipe( + map((organizations) => { + // Filter to only those organizations that can manage sponsorships + const sponsorshipOrgs = organizations.filter((org) => org.canManageSponsorships); + + // Check if there is exactly one organization that can manage sponsorships. + // This is important because users that are part of multiple organizations + // may always access free bitwarden family menu. We want to restrict access + // to the policy only when there is a single enterprise organization and the free family policy is turn. + return sponsorshipOrgs.length === 1; + }), + ), + ), + ); + } + + isFreeFamilyPolicyEnabled$(): Observable { + return this.hasSingleEnterpriseOrg$().pipe( + switchMap((hasSingleEnterpriseOrg) => { + if (!hasSingleEnterpriseOrg) { + return of(false); + } + return getUserId(this.accountService.activeAccount$).pipe( + switchMap((userId) => + this.organizationService.organizations$(userId).pipe( + map((organizations) => organizations.find((org) => org.canManageSponsorships)?.id), + switchMap((enterpriseOrgId) => + this.policyService + .getAll$(PolicyType.FreeFamiliesSponsorshipPolicy, userId) + .pipe( + map( + (policies) => + policies.find((policy) => policy.organizationId === enterpriseOrgId) + ?.enabled ?? false, + ), + ), + ), + ), + ), + ); + }), + ); + } +} diff --git a/apps/browser/src/images/at-risk-password-carousel/generate_password.dark.png b/apps/browser/src/images/at-risk-password-carousel/generate_password.dark.png new file mode 100644 index 00000000000..8d0fbbdec56 Binary files /dev/null and b/apps/browser/src/images/at-risk-password-carousel/generate_password.dark.png differ diff --git a/apps/browser/src/images/at-risk-password-carousel/generate_password.light.png b/apps/browser/src/images/at-risk-password-carousel/generate_password.light.png new file mode 100644 index 00000000000..4e46ce2adc3 Binary files /dev/null and b/apps/browser/src/images/at-risk-password-carousel/generate_password.light.png differ diff --git a/apps/browser/src/images/at-risk-password-carousel/review_at-risk_logins.dark.png b/apps/browser/src/images/at-risk-password-carousel/review_at-risk_logins.dark.png new file mode 100644 index 00000000000..47e1d40bd4e Binary files /dev/null and b/apps/browser/src/images/at-risk-password-carousel/review_at-risk_logins.dark.png differ diff --git a/apps/browser/src/images/at-risk-password-carousel/review_at-risk_logins.light.png b/apps/browser/src/images/at-risk-password-carousel/review_at-risk_logins.light.png new file mode 100644 index 00000000000..a248f04c978 Binary files /dev/null and b/apps/browser/src/images/at-risk-password-carousel/review_at-risk_logins.light.png differ diff --git a/apps/browser/src/images/at-risk-password-carousel/update_login.dark.png b/apps/browser/src/images/at-risk-password-carousel/update_login.dark.png new file mode 100644 index 00000000000..a04d3e369ba Binary files /dev/null and b/apps/browser/src/images/at-risk-password-carousel/update_login.dark.png differ diff --git a/apps/browser/src/images/at-risk-password-carousel/update_login.light.png b/apps/browser/src/images/at-risk-password-carousel/update_login.light.png new file mode 100644 index 00000000000..3bc176f8acd Binary files /dev/null and b/apps/browser/src/images/at-risk-password-carousel/update_login.light.png differ diff --git a/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts b/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts index 8e6fc562d14..3031134dc34 100644 --- a/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts +++ b/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts @@ -1,9 +1,18 @@ import { Injectable } from "@angular/core"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { UserId } from "@bitwarden/common/types/guid"; import { UserKey } from "@bitwarden/common/types/key"; -import { BiometricsService, BiometricsCommands, BiometricsStatus } from "@bitwarden/key-management"; +import { + BiometricsService, + BiometricsCommands, + BiometricsStatus, + KeyService, + BiometricStateService, +} from "@bitwarden/key-management"; import { NativeMessagingBackground } from "../../background/nativeMessaging.background"; import { BrowserApi } from "../../platform/browser/browser-api"; @@ -13,6 +22,9 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { constructor( private nativeMessagingBackground: () => NativeMessagingBackground, private logService: LogService, + private keyService: KeyService, + private biometricStateService: BiometricStateService, + private messagingService: MessagingService, ) { super(); } @@ -65,6 +77,8 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { } } return BiometricsStatus.Available; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return BiometricsStatus.DesktopDisconnected; } @@ -74,12 +88,21 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { try { await this.ensureConnected(); + // todo remove after 2025.3 if (this.nativeMessagingBackground().isConnectedToOutdatedDesktopClient) { const response = await this.nativeMessagingBackground().callCommand({ command: BiometricsCommands.Unlock, }); if (response.response == "unlocked") { - return response.userKeyB64; + const decodedUserkey = Utils.fromB64ToArray(response.userKeyB64); + const userKey = new SymmetricCryptoKey(decodedUserkey) as UserKey; + if (await this.keyService.validateUserKey(userKey, userId)) { + await this.biometricStateService.setBiometricUnlockEnabled(true); + await this.keyService.setUserKey(userKey, userId); + // to update badge and other things + this.messagingService.send("switchAccount", { userId }); + return userKey; + } } else { return null; } @@ -89,7 +112,16 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { userId: userId, }); if (response.response) { - return response.userKeyB64; + // In case the requesting foreground context dies (popup), the userkey should still be set, so the user is unlocked / the setting should be enabled + const decodedUserkey = Utils.fromB64ToArray(response.userKeyB64); + const userKey = new SymmetricCryptoKey(decodedUserkey) as UserKey; + if (await this.keyService.validateUserKey(userKey, userId)) { + await this.biometricStateService.setBiometricUnlockEnabled(true); + await this.keyService.setUserKey(userKey, userId); + // to update badge and other things + this.messagingService.send("switchAccount", { userId }); + return userKey; + } } else { return null; } @@ -98,6 +130,8 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { this.logService.info("Biometric unlock for user failed", e); throw new Error("Biometric unlock failed"); } + + return null; } async getBiometricsStatusForUser(id: UserId): Promise { @@ -114,6 +148,8 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { userId: id, }) ).response; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return BiometricsStatus.DesktopDisconnected; } diff --git a/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts b/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts index 0235ad5bd9c..d248a630cc6 100644 --- a/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts +++ b/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts @@ -29,13 +29,13 @@ export class ForegroundBrowserBiometricsService extends BiometricsService { async unlockWithBiometricsForUser(userId: UserId): Promise { const response = await BrowserApi.sendMessageWithResponse<{ - result: string; + result: UserKey; error: string; }>(BiometricsCommands.UnlockWithBiometricsForUser, { userId }); if (!response.result) { return null; } - return SymmetricCryptoKey.fromString(response.result) as UserKey; + return SymmetricCryptoKey.fromString(response.result.keyB64) as UserKey; } async getBiometricsStatusForUser(id: UserId): Promise { diff --git a/apps/browser/src/key-management/lock/services/extension-lock-component.service.spec.ts b/apps/browser/src/key-management/lock/services/extension-lock-component.service.spec.ts index 4b0323d5ebe..9afc723825c 100644 --- a/apps/browser/src/key-management/lock/services/extension-lock-component.service.spec.ts +++ b/apps/browser/src/key-management/lock/services/extension-lock-component.service.spec.ts @@ -6,11 +6,16 @@ import { PinServiceAbstraction, UserDecryptionOptionsServiceAbstraction, } from "@bitwarden/auth/common"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; +import { VaultTimeoutSettingsService } from "@bitwarden/common/key-management/vault-timeout"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { UserId } from "@bitwarden/common/types/guid"; -import { KeyService, BiometricsService, BiometricsStatus } from "@bitwarden/key-management"; -import { UnlockOptions } from "@bitwarden/key-management/angular"; +import { + KeyService, + BiometricsService, + BiometricsStatus, + BiometricStateService, +} from "@bitwarden/key-management"; +import { UnlockOptions } from "@bitwarden/key-management-ui"; import { BrowserRouterService } from "../../../platform/popup/services/browser-router.service"; @@ -26,6 +31,7 @@ describe("ExtensionLockComponentService", () => { let vaultTimeoutSettingsService: MockProxy; let keyService: MockProxy; let routerService: MockProxy; + let biometricStateService: MockProxy; beforeEach(() => { userDecryptionOptionsService = mock(); @@ -35,6 +41,7 @@ describe("ExtensionLockComponentService", () => { vaultTimeoutSettingsService = mock(); keyService = mock(); routerService = mock(); + biometricStateService = mock(); TestBed.configureTestingModule({ providers: [ @@ -67,6 +74,10 @@ describe("ExtensionLockComponentService", () => { provide: BrowserRouterService, useValue: routerService, }, + { + provide: BiometricStateService, + useValue: biometricStateService, + }, ], }); @@ -306,6 +317,7 @@ describe("ExtensionLockComponentService", () => { platformUtilsService.supportsSecureStorage.mockReturnValue( mockInputs.platformSupportsSecureStorage, ); + biometricStateService.biometricUnlockEnabled$ = of(true); // PIN pinService.isPinDecryptionAvailable.mockResolvedValue(mockInputs.pinDecryptionAvailable); diff --git a/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts b/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts index f21beb91cff..09a6f890e60 100644 --- a/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts +++ b/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts @@ -1,15 +1,17 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { inject } from "@angular/core"; -import { combineLatest, defer, map, Observable } from "rxjs"; +import { combineLatest, defer, firstValueFrom, map, Observable } from "rxjs"; import { PinServiceAbstraction, UserDecryptionOptionsServiceAbstraction, } from "@bitwarden/auth/common"; import { UserId } from "@bitwarden/common/types/guid"; -import { BiometricsService, BiometricsStatus } from "@bitwarden/key-management"; -import { LockComponentService, UnlockOptions } from "@bitwarden/key-management/angular"; +import { + BiometricsService, + BiometricsStatus, + BiometricStateService, +} from "@bitwarden/key-management"; +import { LockComponentService, UnlockOptions } from "@bitwarden/key-management-ui"; import { BiometricErrors, BiometricErrorTypes } from "../../../models/biometricErrors"; import { BrowserRouterService } from "../../../platform/popup/services/browser-router.service"; @@ -19,9 +21,10 @@ export class ExtensionLockComponentService implements LockComponentService { private readonly biometricsService = inject(BiometricsService); private readonly pinService = inject(PinServiceAbstraction); private readonly routerService = inject(BrowserRouterService); + private readonly biometricStateService = inject(BiometricStateService); getPreviousUrl(): string | null { - return this.routerService.getPreviousUrl(); + return this.routerService.getPreviousUrl() ?? null; } getBiometricsError(error: any): string | null { @@ -45,14 +48,28 @@ export class ExtensionLockComponentService implements LockComponentService { getAvailableUnlockOptions$(userId: UserId): Observable { return combineLatest([ // Note: defer is preferable b/c it delays the execution of the function until the observable is subscribed to - defer(async () => await this.biometricsService.getBiometricsStatusForUser(userId)), + defer(async () => { + if (!(await firstValueFrom(this.biometricStateService.biometricUnlockEnabled$))) { + return BiometricsStatus.NotEnabledLocally; + } else { + // TODO remove after 2025.3 + // remove after backward compatibility code for old biometrics ipc protocol is removed + const result: BiometricsStatus = (await Promise.race([ + this.biometricsService.getBiometricsStatusForUser(userId), + new Promise((resolve) => + setTimeout(() => resolve(BiometricsStatus.DesktopDisconnected), 1000), + ), + ])) as BiometricsStatus; + return result; + } + }), this.userDecryptionOptionsService.userDecryptionOptionsById$(userId), defer(() => this.pinService.isPinDecryptionAvailable(userId)), ]).pipe( map(([biometricsStatus, userDecryptionOptions, pinDecryptionAvailable]) => { const unlockOpts: UnlockOptions = { masterPassword: { - enabled: userDecryptionOptions.hasMasterPassword, + enabled: userDecryptionOptions?.hasMasterPassword, }, pin: { enabled: pinDecryptionAvailable, diff --git a/apps/browser/src/services/vault-timeout/foreground-vault-timeout.service.ts b/apps/browser/src/key-management/vault-timeout/foreground-vault-timeout.service.ts similarity index 72% rename from apps/browser/src/services/vault-timeout/foreground-vault-timeout.service.ts rename to apps/browser/src/key-management/vault-timeout/foreground-vault-timeout.service.ts index 6270b3503b5..5003dfd5b29 100644 --- a/apps/browser/src/services/vault-timeout/foreground-vault-timeout.service.ts +++ b/apps/browser/src/key-management/vault-timeout/foreground-vault-timeout.service.ts @@ -1,8 +1,8 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { VaultTimeoutService as BaseVaultTimeoutService } from "@bitwarden/common/src/abstractions/vault-timeout/vault-timeout.service"; -import { MessagingService } from "@bitwarden/common/src/platform/abstractions/messaging.service"; -import { UserId } from "@bitwarden/common/src/types/guid"; +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) {} diff --git a/apps/browser/src/services/vault-timeout/vault-timeout.service.ts b/apps/browser/src/key-management/vault-timeout/vault-timeout.service.ts similarity index 93% rename from apps/browser/src/services/vault-timeout/vault-timeout.service.ts rename to apps/browser/src/key-management/vault-timeout/vault-timeout.service.ts index e0b9db5422b..51f90fb98a6 100644 --- a/apps/browser/src/services/vault-timeout/vault-timeout.service.ts +++ b/apps/browser/src/key-management/vault-timeout/vault-timeout.service.ts @@ -1,4 +1,4 @@ -import { VaultTimeoutService as BaseVaultTimeoutService } from "@bitwarden/common/services/vault-timeout/vault-timeout.service"; +import { VaultTimeoutService as BaseVaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout/services/vault-timeout.service"; import { SafariApp } from "../../browser/safariApp"; diff --git a/apps/browser/src/manifest.json b/apps/browser/src/manifest.json index 9e4eb78291a..4510c2f342d 100644 --- a/apps/browser/src/manifest.json +++ b/apps/browser/src/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "__MSG_extName__", "short_name": "__MSG_appName__", - "version": "2025.1.0", + "version": "2025.3.1", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", @@ -29,12 +29,6 @@ "matches": ["*://*/*", "file:///*"], "exclude_matches": ["*://*/*.xml*", "file:///*.xml*"], "run_at": "document_start" - }, - { - "all_frames": false, - "js": ["content/lp-fileless-importer.js"], - "matches": ["https://lastpass.com/export.php"], - "run_at": "document_start" } ], "background": { @@ -140,7 +134,6 @@ }, "web_accessible_resources": [ "content/fido2-page-script.js", - "content/lp-suppress-import-download.js", "notification/bar.html", "images/icon38.png", "images/icon38_locked.png", diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json index ef7ede6b056..fc897c1b1c3 100644 --- a/apps/browser/src/manifest.v3.json +++ b/apps/browser/src/manifest.v3.json @@ -3,7 +3,7 @@ "minimum_chrome_version": "102.0", "name": "__MSG_extName__", "short_name": "__MSG_appName__", - "version": "2025.1.0", + "version": "2025.3.1", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", @@ -26,16 +26,10 @@ { "all_frames": true, "css": ["content/autofill.css"], - "js": ["content/trigger-autofill-script-injection.js", "content/misc-utils.js"], + "js": ["content/trigger-autofill-script-injection.js"], "matches": ["*://*/*", "file:///*"], "exclude_matches": ["*://*/*.xml*", "file:///*.xml*"], "run_at": "document_start" - }, - { - "all_frames": false, - "js": ["content/lp-fileless-importer.js"], - "matches": ["https://lastpass.com/export.php"], - "run_at": "document_start" } ], "background": { @@ -66,7 +60,24 @@ "unlimitedStorage", "webNavigation", "webRequest", - "webRequestAuthProvider" + "webRequestAuthProvider", + "notifications" + ], + "__firefox__permissions": [ + "activeTab", + "alarms", + "clipboardRead", + "clipboardWrite", + "contextMenus", + "idle", + "scripting", + "storage", + "tabs", + "unlimitedStorage", + "webNavigation", + "webRequest", + "webRequestAuthProvider", + "notifications" ], "__safari__permissions": [ "activeTab", @@ -91,7 +102,7 @@ "host_permissions": ["https://*/*", "http://*/*"], "content_security_policy": { "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'", - "sandbox": "sandbox allow-scripts; script-src 'self'" + "__chrome__sandbox": "sandbox allow-scripts; script-src 'self'" }, "sandbox": { "pages": [ @@ -117,6 +128,13 @@ }, "description": "__MSG_commandOpenSidebar__" }, + "__opera___execute_sidebar_action": { + "suggested_key": { + "default": "Alt+Shift+Y", + "linux": "Alt+Shift+U" + }, + "description": "__MSG_commandOpenSidebar__" + }, "autofill_login": { "suggested_key": { "default": "Ctrl+Shift+L" @@ -171,5 +189,12 @@ "storage": { "managed_schema": "managed_schema.json" }, - "__firefox__storage": null + "__firefox__storage": null, + "__opera__sidebar_action": { + "default_title": "Bitwarden", + "default_panel": "popup/index.html?uilocation=sidebar", + "default_icon": "images/icon19.png", + "open_at_install": false, + "browser_style": false + } } diff --git a/apps/browser/src/platform/browser/browser-api.spec.ts b/apps/browser/src/platform/browser/browser-api.spec.ts index 6e8a0f3002d..f6bf7d4069b 100644 --- a/apps/browser/src/platform/browser/browser-api.spec.ts +++ b/apps/browser/src/platform/browser/browser-api.spec.ts @@ -2,6 +2,8 @@ import { mock } from "jest-mock-extended"; import { BrowserApi } from "./browser-api"; +type ChromeSettingsGet = chrome.types.ChromeSetting["get"]; + describe("BrowserApi", () => { const executeScriptResult = ["value"]; @@ -468,19 +470,23 @@ describe("BrowserApi", () => { describe("browserAutofillSettingsOverridden", () => { it("returns true if the browser autofill settings are overridden", async () => { - const expectedDetails = { - value: false, - levelOfControl: "controlled_by_this_extension", - } as chrome.types.ChromeSettingGetResultDetails; - chrome.privacy.services.autofillAddressEnabled.get = jest.fn((details, callback) => - callback(expectedDetails), - ); - chrome.privacy.services.autofillCreditCardEnabled.get = jest.fn((details, callback) => - callback(expectedDetails), - ); - chrome.privacy.services.passwordSavingEnabled.get = jest.fn((details, callback) => - callback(expectedDetails), - ); + const mockFn = jest.fn< + void, + [ + details: chrome.types.ChromeSettingGetDetails, + callback: (details: chrome.types.ChromeSettingGetResult) => void, + ], + never + >((details, callback) => { + callback({ + value: false, + levelOfControl: "controlled_by_this_extension", + }); + }); + chrome.privacy.services.autofillAddressEnabled.get = mockFn as unknown as ChromeSettingsGet; + chrome.privacy.services.autofillCreditCardEnabled.get = + mockFn as unknown as ChromeSettingsGet; + chrome.privacy.services.passwordSavingEnabled.get = mockFn as unknown as ChromeSettingsGet; const result = await BrowserApi.browserAutofillSettingsOverridden(); @@ -488,19 +494,24 @@ describe("BrowserApi", () => { }); it("returns false if the browser autofill settings are not overridden", async () => { - const expectedDetails = { - value: true, - levelOfControl: "controlled_by_this_extension", - } as chrome.types.ChromeSettingGetResultDetails; - chrome.privacy.services.autofillAddressEnabled.get = jest.fn((details, callback) => - callback(expectedDetails), - ); - chrome.privacy.services.autofillCreditCardEnabled.get = jest.fn((details, callback) => - callback(expectedDetails), - ); - chrome.privacy.services.passwordSavingEnabled.get = jest.fn((details, callback) => - callback(expectedDetails), - ); + const mockFn = jest.fn< + void, + [ + details: chrome.types.ChromeSettingGetDetails, + callback: (details: chrome.types.ChromeSettingGetResult) => void, + ], + never + >((details, callback) => { + callback({ + value: true, + levelOfControl: "controlled_by_this_extension", + }); + }); + + chrome.privacy.services.autofillAddressEnabled.get = mockFn as unknown as ChromeSettingsGet; + chrome.privacy.services.autofillCreditCardEnabled.get = + mockFn as unknown as ChromeSettingsGet; + chrome.privacy.services.passwordSavingEnabled.get = mockFn as unknown as ChromeSettingsGet; const result = await BrowserApi.browserAutofillSettingsOverridden(); @@ -508,19 +519,23 @@ describe("BrowserApi", () => { }); it("returns false if the browser autofill settings are not controlled by the extension", async () => { - const expectedDetails = { - value: false, - levelOfControl: "controlled_by_other_extensions", - } as chrome.types.ChromeSettingGetResultDetails; - chrome.privacy.services.autofillAddressEnabled.get = jest.fn((details, callback) => - callback(expectedDetails), - ); - chrome.privacy.services.autofillCreditCardEnabled.get = jest.fn((details, callback) => - callback(expectedDetails), - ); - chrome.privacy.services.passwordSavingEnabled.get = jest.fn((details, callback) => - callback(expectedDetails), - ); + const mockFn = jest.fn< + void, + [ + details: chrome.types.ChromeSettingGetDetails, + callback: (details: chrome.types.ChromeSettingGetResult) => void, + ], + never + >((details, callback) => { + callback({ + value: false, + levelOfControl: "controlled_by_other_extensions", + }); + }); + chrome.privacy.services.autofillAddressEnabled.get = mockFn as unknown as ChromeSettingsGet; + chrome.privacy.services.autofillCreditCardEnabled.get = + mockFn as unknown as ChromeSettingsGet; + chrome.privacy.services.passwordSavingEnabled.get = mockFn as unknown as ChromeSettingsGet; const result = await BrowserApi.browserAutofillSettingsOverridden(); diff --git a/apps/browser/src/platform/browser/browser-api.ts b/apps/browser/src/platform/browser/browser-api.ts index 1d8ff65c17d..ec16c883bfb 100644 --- a/apps/browser/src/platform/browser/browser-api.ts +++ b/apps/browser/src/platform/browser/browser-api.ts @@ -504,7 +504,9 @@ export class BrowserApi { * * @param permissions - The permissions to check. */ - static async permissionsGranted(permissions: string[]): Promise { + static async permissionsGranted( + permissions: chrome.runtime.ManifestPermissions[], + ): Promise { return new Promise((resolve) => chrome.permissions.contains({ permissions }, (result) => resolve(result)), ); @@ -530,10 +532,15 @@ export class BrowserApi { win: Window & typeof globalThis, ): OperaSidebarAction | FirefoxSidebarAction | null { const deviceType = BrowserPlatformUtilsService.getDevice(win); - if (deviceType !== DeviceType.FirefoxExtension && deviceType !== DeviceType.OperaExtension) { - return null; + if (deviceType === DeviceType.FirefoxExtension) { + return browser.sidebarAction; } - return win.opr?.sidebarAction || browser.sidebarAction; + + if (deviceType === DeviceType.OperaExtension) { + return win.opr?.sidebarAction; + } + + return null; } static captureVisibleTab(): Promise { @@ -589,7 +596,7 @@ export class BrowserApi { * Identifies if the browser autofill settings are overridden by the extension. */ static async browserAutofillSettingsOverridden(): Promise { - const checkOverrideStatus = (details: chrome.types.ChromeSettingGetResultDetails) => + const checkOverrideStatus = (details: chrome.types.ChromeSettingGetResult) => details.levelOfControl === "controlled_by_this_extension" && !details.value; const autofillAddressOverridden: boolean = await new Promise((resolve) => @@ -618,10 +625,10 @@ export class BrowserApi { * * @param value - Determines whether to enable or disable the autofill settings. */ - static updateDefaultBrowserAutofillSettings(value: boolean) { - chrome.privacy.services.autofillAddressEnabled.set({ value }); - chrome.privacy.services.autofillCreditCardEnabled.set({ value }); - chrome.privacy.services.passwordSavingEnabled.set({ value }); + static async updateDefaultBrowserAutofillSettings(value: boolean) { + await chrome.privacy.services.autofillAddressEnabled.set({ value }); + await chrome.privacy.services.autofillCreditCardEnabled.set({ value }); + await chrome.privacy.services.passwordSavingEnabled.set({ value }); } /** diff --git a/apps/browser/src/platform/flags.ts b/apps/browser/src/platform/flags.ts index 383e982f065..2b1040bcd8a 100644 --- a/apps/browser/src/platform/flags.ts +++ b/apps/browser/src/platform/flags.ts @@ -11,13 +11,11 @@ import { GroupPolicyEnvironment } from "../admin-console/types/group-policy-envi import { BrowserApi } from "./browser/browser-api"; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type Flags = { accountSwitching?: boolean; } & SharedFlags; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type DevFlags = { managedEnvironment?: GroupPolicyEnvironment; } & SharedDevFlags; diff --git a/apps/browser/src/platform/listeners/update-badge.ts b/apps/browser/src/platform/listeners/update-badge.ts index f67b96c847f..cd74b9ebf7d 100644 --- a/apps/browser/src/platform/listeners/update-badge.ts +++ b/apps/browser/src/platform/listeners/update-badge.ts @@ -2,8 +2,10 @@ // @ts-strict-ignore import { firstValueFrom } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { getOptionalUserId } from "@bitwarden/common/auth/services/account.service"; import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -22,6 +24,7 @@ export class UpdateBadge { private authService: AuthService; private badgeSettingsService: BadgeSettingsServiceAbstraction; private cipherService: CipherService; + private accountService: AccountService; private badgeAction: typeof chrome.action | typeof chrome.browserAction; private sidebarAction: OperaSidebarAction | FirefoxSidebarAction; private win: Window & typeof globalThis; @@ -34,6 +37,7 @@ export class UpdateBadge { this.badgeSettingsService = services.badgeSettingsService; this.authService = services.authService; this.cipherService = services.cipherService; + this.accountService = services.accountService; } async run(opts?: { tabId?: number; windowId?: number }): Promise { @@ -87,7 +91,14 @@ export class UpdateBadge { return; } - const ciphers = await this.cipherService.getAllDecryptedForUrl(opts?.tab?.url); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(getOptionalUserId), + ); + if (!activeUserId) { + return; + } + + const ciphers = await this.cipherService.getAllDecryptedForUrl(opts?.tab?.url, activeUserId); let countText = ciphers.length == 0 ? "" : ciphers.length.toString(); if (ciphers.length > 9) { countText = "9+"; @@ -165,6 +176,13 @@ export class UpdateBadge { return; } + if ("opr" in this.win && BrowserApi.isManifestVersion(3)) { + // setIcon API is currenly broken for Opera MV3 extensions + // https://forums.opera.com/topic/75680/opr-sidebaraction-seticon-api-is-broken-access-to-extension-api-denied?_=1738349261570 + // The API currently crashes on MacOS + return; + } + if (this.isOperaSidebar(this.sidebarAction)) { await new Promise((resolve) => (this.sidebarAction as OperaSidebarAction).setIcon(options, () => resolve()), diff --git a/apps/browser/src/platform/offscreen-document/offscreen-document.spec.ts b/apps/browser/src/platform/offscreen-document/offscreen-document.spec.ts index 4065f2e46d7..37731f17fbe 100644 --- a/apps/browser/src/platform/offscreen-document/offscreen-document.spec.ts +++ b/apps/browser/src/platform/offscreen-document/offscreen-document.spec.ts @@ -3,12 +3,19 @@ import { BrowserApi } from "../browser/browser-api"; import BrowserClipboardService from "../services/browser-clipboard.service"; describe("OffscreenDocument", () => { - const browserApiMessageListenerSpy = jest.spyOn(BrowserApi, "messageListener"); - const browserClipboardServiceCopySpy = jest.spyOn(BrowserClipboardService, "copy"); - const browserClipboardServiceReadSpy = jest.spyOn(BrowserClipboardService, "read"); - const consoleErrorSpy = jest.spyOn(console, "error"); + let browserClipboardServiceCopySpy: jest.SpyInstance; + let browserClipboardServiceReadSpy: jest.SpyInstance; + let browserApiMessageListenerSpy: jest.SpyInstance; + let consoleErrorSpy: jest.SpyInstance; - require("../offscreen-document/offscreen-document"); + beforeEach(async () => { + browserApiMessageListenerSpy = jest.spyOn(BrowserApi, "messageListener"); + browserClipboardServiceCopySpy = jest.spyOn(BrowserClipboardService, "copy"); + browserClipboardServiceReadSpy = jest.spyOn(BrowserClipboardService, "read"); + consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(); + + await import("./offscreen-document"); + }); describe("init", () => { it("sets up a `chrome.runtime.onMessage` listener", () => { @@ -45,6 +52,7 @@ describe("OffscreenDocument", () => { it("copies the message text", async () => { const text = "test"; + browserClipboardServiceCopySpy.mockResolvedValueOnce(undefined); sendMockExtensionMessage({ command: "offscreenCopyToClipboard", text }); await flushPromises(); @@ -54,6 +62,7 @@ describe("OffscreenDocument", () => { describe("handleOffscreenReadFromClipboard", () => { it("reads the value from the clipboard service", async () => { + browserClipboardServiceReadSpy.mockResolvedValueOnce(""); sendMockExtensionMessage({ command: "offscreenReadFromClipboard" }); await flushPromises(); diff --git a/apps/browser/src/platform/popup/layout/popup-back.directive.ts b/apps/browser/src/platform/popup/layout/popup-back.directive.ts new file mode 100644 index 00000000000..95f82588640 --- /dev/null +++ b/apps/browser/src/platform/popup/layout/popup-back.directive.ts @@ -0,0 +1,26 @@ +import { Directive, Optional } from "@angular/core"; + +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; +import { BitActionDirective, ButtonLikeAbstraction } from "@bitwarden/components"; + +import { PopupRouterCacheService } from "../view-cache/popup-router-cache.service"; + +/** Navigate the browser popup to the previous page when the component is clicked. */ +@Directive({ + selector: "[popupBackAction]", + standalone: true, +}) +export class PopupBackBrowserDirective extends BitActionDirective { + constructor( + buttonComponent: ButtonLikeAbstraction, + private router: PopupRouterCacheService, + @Optional() validationService?: ValidationService, + @Optional() logService?: LogService, + ) { + super(buttonComponent, validationService, logService); + + // override `bitAction` input; the parent handles the rest + this.handler = () => this.router.back(); + } +} diff --git a/apps/browser/src/platform/popup/layout/popup-layout.mdx b/apps/browser/src/platform/popup/layout/popup-layout.mdx index 5723bef44b1..6a28f7f4f73 100644 --- a/apps/browser/src/platform/popup/layout/popup-layout.mdx +++ b/apps/browser/src/platform/popup/layout/popup-layout.mdx @@ -4,9 +4,6 @@ import * as stories from "./popup-layout.stories"; -Please note that because these stories use `router-outlet`, there are issues with rendering content -when Light & Dark mode is selected. The stories are best viewed by selecting one color mode. - # Popup Tab Navigation The popup tab navigation component composes together the popup page and the bottom tab navigation @@ -100,9 +97,7 @@ Usage example: ### Transparent header - - - + Common interactive elements to insert into the `end` slot are: @@ -113,9 +108,7 @@ Common interactive elements to insert into the `end` slot are: ### Notice - - - + Common interactive elements to insert into the `full-width-notice` slot are: @@ -160,29 +153,21 @@ View the story source code to see examples of how to construct these types of pa Example of wrapping an extension page in the `popup-tab-navigation` component. - - - + ## Extension Page Examples of using just the `popup-page` component, without and with a footer. - - - + - - - + ## Popped out When the browser extension is popped out, the "popout" button should not be passed to the header. - - - + # Other stories @@ -190,14 +175,10 @@ When the browser extension is popped out, the "popout" button should not be pass An example of how to center the default content. - - - + ## Loading An example of what the loading state looks like. - - - + 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 c1ac8823261..79412e6bce8 100644 --- a/apps/browser/src/platform/popup/layout/popup-layout.stories.ts +++ b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts @@ -45,7 +45,7 @@ class ExtensionContainerComponent {} - - + + @@ -302,9 +302,9 @@ export default { title: "Browser/Popup Layout", component: PopupPageComponent, parameters: { - chromatic: { - // Disable tests while we troubleshoot their flaky-ness - disableSnapshot: true, + design: { + type: "figma", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-38889&t=k6OTDDPZOTtypRqo-11", }, }, decorators: [ 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 ba9a108a504..94f0846a852 100644 --- a/apps/browser/src/platform/popup/layout/popup-page.component.html +++ b/apps/browser/src/platform/popup/layout/popup-page.component.html @@ -13,13 +13,13 @@
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 a4ae3161b47..bed4eac3f90 100644 --- a/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.html +++ b/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.html @@ -7,7 +7,7 @@
  • + -
+ + diff --git a/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.ts b/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.ts index f730fef24b3..1e5bdee09b9 100644 --- a/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.ts +++ b/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.ts @@ -1,20 +1,24 @@ +import { DialogRef } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; -import { Component } from "@angular/core"; +import { Component, ElementRef, ViewChild } from "@angular/core"; import { Observable, combineLatest, defer, map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; -import { ButtonModule, DialogModule } from "@bitwarden/components"; +import { ButtonModule, DialogModule, ToastService, TypographyModule } from "@bitwarden/components"; @Component({ templateUrl: "about-dialog.component.html", standalone: true, - imports: [CommonModule, JslibModule, DialogModule, ButtonModule], + imports: [CommonModule, JslibModule, DialogModule, ButtonModule, TypographyModule], }) export class AboutDialogComponent { + @ViewChild("version") protected version!: ElementRef; + protected year = new Date().getFullYear(); protected version$: Observable; @@ -26,11 +30,23 @@ export class AboutDialogComponent { protected sdkVersion$ = this.sdkService.version$; constructor( + private dialogRef: DialogRef, private configService: ConfigService, private environmentService: EnvironmentService, private platformUtilsService: PlatformUtilsService, + private toastService: ToastService, + private i18nService: I18nService, private sdkService: SdkService, ) { this.version$ = defer(() => this.platformUtilsService.getApplicationVersion()); } + + protected copyVersion() { + this.platformUtilsService.copyToClipboard(this.version.nativeElement.innerText); + this.toastService.showToast({ + message: this.i18nService.t("copySuccessful"), + variant: "success", + }); + this.dialogRef.close(); + } } diff --git a/apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page-v2.component.ts b/apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page-v2.component.ts index 8b880e88671..a3d1c553977 100644 --- a/apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page-v2.component.ts +++ b/apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page-v2.component.ts @@ -6,15 +6,16 @@ import { Observable, firstValueFrom, of, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { DialogService, ItemModule } from "@bitwarden/components"; +import { FamiliesPolicyService } from "../../../../billing/services/families-policy.service"; import { BrowserApi } from "../../../../platform/browser/browser-api"; import { PopOutComponent } from "../../../../platform/popup/components/pop-out.component"; import { PopupHeaderComponent } from "../../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page.component"; -import { FamiliesPolicyService } from "../../../../services/families-policy.service"; @Component({ templateUrl: "more-from-bitwarden-page-v2.component.html", @@ -43,6 +44,9 @@ export class MoreFromBitwardenPageV2Component { private familiesPolicyService: FamiliesPolicyService, private accountService: AccountService, ) { + this.familySponsorshipAvailable$ = getUserId(this.accountService.activeAccount$).pipe( + switchMap((userId) => this.organizationService.familySponsorshipAvailable$(userId)), + ); this.canAccessPremium$ = this.accountService.activeAccount$.pipe( switchMap((account) => account @@ -50,7 +54,6 @@ export class MoreFromBitwardenPageV2Component { : of(false), ), ); - this.familySponsorshipAvailable$ = this.organizationService.familySponsorshipAvailable$; this.hasSingleEnterpriseOrg$ = this.familiesPolicyService.hasSingleEnterpriseOrg$(); this.isFreeFamilyPolicyEnabled$ = this.familiesPolicyService.isFreeFamilyPolicyEnabled$(); } diff --git a/apps/browser/src/tools/popup/settings/export/export-browser-v2.component.html b/apps/browser/src/tools/popup/settings/export/export-browser-v2.component.html index 9ad6ed36835..8493fa5fee7 100644 --- a/apps/browser/src/tools/popup/settings/export/export-browser-v2.component.html +++ b/apps/browser/src/tools/popup/settings/export/export-browser-v2.component.html @@ -23,5 +23,8 @@ > {{ "exportVault" | i18n }} + diff --git a/apps/browser/src/tools/popup/settings/export/export-browser-v2.component.ts b/apps/browser/src/tools/popup/settings/export/export-browser-v2.component.ts index 86131176a6e..27147b75d39 100644 --- a/apps/browser/src/tools/popup/settings/export/export-browser-v2.component.ts +++ b/apps/browser/src/tools/popup/settings/export/export-browser-v2.component.ts @@ -7,6 +7,7 @@ import { AsyncActionsModule, ButtonModule, DialogModule } from "@bitwarden/compo import { ExportComponent } from "@bitwarden/vault-export-ui"; import { PopOutComponent } from "../../../../platform/popup/components/pop-out.component"; +import { PopupBackBrowserDirective } from "../../../../platform/popup/layout/popup-back.directive"; import { PopupFooterComponent } from "../../../../platform/popup/layout/popup-footer.component"; import { PopupHeaderComponent } from "../../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page.component"; @@ -25,6 +26,7 @@ import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page PopupFooterComponent, PopupHeaderComponent, PopOutComponent, + PopupBackBrowserDirective, ], }) export class ExportBrowserV2Component { @@ -34,6 +36,6 @@ export class ExportBrowserV2Component { constructor(private router: Router) {} protected async onSuccessfulExport(organizationId: string): Promise { - await this.router.navigate(["/vault-settings"]); + await this.router.navigate(["/tabs/settings"]); } } diff --git a/apps/browser/src/tools/popup/settings/import/import-browser-v2.component.ts b/apps/browser/src/tools/popup/settings/import/import-browser-v2.component.ts index 66cb5c62f48..1c5558bd90e 100644 --- a/apps/browser/src/tools/popup/settings/import/import-browser-v2.component.ts +++ b/apps/browser/src/tools/popup/settings/import/import-browser-v2.component.ts @@ -4,7 +4,7 @@ import { Router } from "@angular/router"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AsyncActionsModule, ButtonModule, DialogModule } from "@bitwarden/components"; -import { ImportComponent } from "@bitwarden/importer/ui"; +import { ImportComponent } from "@bitwarden/importer-ui"; import { PopOutComponent } from "../../../../platform/popup/components/pop-out.component"; import { PopupFooterComponent } from "../../../../platform/popup/layout/popup-footer.component"; diff --git a/apps/browser/src/tools/popup/settings/settings-v2.component.html b/apps/browser/src/tools/popup/settings/settings-v2.component.html index 7ff958e26ac..26aeea4f20a 100644 --- a/apps/browser/src/tools/popup/settings/settings-v2.component.html +++ b/apps/browser/src/tools/popup/settings/settings-v2.component.html @@ -8,38 +8,44 @@ - {{ "accountSecurity" | i18n }} + + + {{ "accountSecurity" | i18n }} - {{ "autofill" | i18n }} + + + {{ "autofill" | i18n }} - {{ "notifications" | i18n }} + + + {{ "notifications" | i18n }} - {{ "vault" | i18n }} + + + {{ "vault" | i18n }} - {{ "appearance" | i18n }} + + + {{ "appearance" | i18n }} - {{ "about" | i18n }} + + + {{ "about" | i18n }} diff --git a/apps/browser/src/vault/content/send-on-installed-message.ts b/apps/browser/src/vault/content/send-on-installed-message.ts index 9df15eb0d51..8da9e250249 100644 --- a/apps/browser/src/vault/content/send-on-installed-message.ts +++ b/apps/browser/src/vault/content/send-on-installed-message.ts @@ -1,5 +1,5 @@ -import { VaultOnboardingMessages } from "@bitwarden/common/vault/enums/vault-onboarding.enum"; +import { VaultMessages } from "@bitwarden/common/vault/enums/vault-messages.enum"; (function (globalContext) { - globalContext.postMessage({ command: VaultOnboardingMessages.HasBwInstalled }); + globalContext.postMessage({ command: VaultMessages.HasBwInstalled }); })(window); diff --git a/apps/browser/src/vault/content/send-popup-open-message.ts b/apps/browser/src/vault/content/send-popup-open-message.ts new file mode 100644 index 00000000000..6889d24f7f6 --- /dev/null +++ b/apps/browser/src/vault/content/send-popup-open-message.ts @@ -0,0 +1,6 @@ +import { VaultMessages } from "@bitwarden/common/vault/enums/vault-messages.enum"; + +(function (globalContext) { + // Send a message to the window that the popup opened + globalContext.postMessage({ command: VaultMessages.PopupOpened }); +})(window); diff --git a/apps/browser/src/vault/guards/at-risk-passwords.guard.ts b/apps/browser/src/vault/guards/at-risk-passwords.guard.ts new file mode 100644 index 00000000000..ee991c81239 --- /dev/null +++ b/apps/browser/src/vault/guards/at-risk-passwords.guard.ts @@ -0,0 +1,29 @@ +import { inject } from "@angular/core"; +import { CanActivateFn } from "@angular/router"; +import { switchMap, tap } from "rxjs"; + +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { ToastService } from "@bitwarden/components"; +import { filterOutNullish, TaskService } from "@bitwarden/vault"; + +export const canAccessAtRiskPasswords: CanActivateFn = () => { + const accountService = inject(AccountService); + const taskService = inject(TaskService); + const toastService = inject(ToastService); + const i18nService = inject(I18nService); + + return accountService.activeAccount$.pipe( + filterOutNullish(), + switchMap((user) => taskService.tasksEnabled$(user.id)), + tap((tasksEnabled) => { + if (!tasksEnabled) { + toastService.showToast({ + variant: "error", + title: "", + message: i18nService.t("accessDenied"), + }); + } + }), + ); +}; diff --git a/apps/browser/src/vault/popup/components/at-risk-callout/at-risk-password-callout.component.html b/apps/browser/src/vault/popup/components/at-risk-callout/at-risk-password-callout.component.html new file mode 100644 index 00000000000..16d9b6a322a --- /dev/null +++ b/apps/browser/src/vault/popup/components/at-risk-callout/at-risk-password-callout.component.html @@ -0,0 +1,9 @@ + + + + {{ + (taskCount === 1 ? "reviewAndChangeAtRiskPassword" : "reviewAndChangeAtRiskPasswordsPlural") + | i18n: taskCount.toString() + }} + + diff --git a/apps/browser/src/vault/popup/components/at-risk-callout/at-risk-password-callout.component.ts b/apps/browser/src/vault/popup/components/at-risk-callout/at-risk-password-callout.component.ts new file mode 100644 index 00000000000..5e46f3cd3d9 --- /dev/null +++ b/apps/browser/src/vault/popup/components/at-risk-callout/at-risk-password-callout.component.ts @@ -0,0 +1,32 @@ +import { CommonModule } from "@angular/common"; +import { Component, inject } from "@angular/core"; +import { RouterModule } from "@angular/router"; +import { map, switchMap } from "rxjs"; + +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { AnchorLinkDirective, CalloutModule } from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; +import { filterOutNullish, SecurityTaskType, TaskService } from "@bitwarden/vault"; + +// TODO: This component will need to be reworked to use the new EndUserNotificationService in PM-10609 + +@Component({ + selector: "vault-at-risk-password-callout", + standalone: true, + imports: [CommonModule, AnchorLinkDirective, RouterModule, CalloutModule, I18nPipe], + templateUrl: "./at-risk-password-callout.component.html", +}) +export class AtRiskPasswordCalloutComponent { + private taskService = inject(TaskService); + private activeAccount$ = inject(AccountService).activeAccount$.pipe(filterOutNullish()); + + protected pendingTasks$ = this.activeAccount$.pipe( + switchMap((user) => + this.taskService + .pendingTasks$(user.id) + .pipe( + map((tasks) => tasks.filter((t) => t.type === SecurityTaskType.UpdateAtRiskCredential)), + ), + ), + ); +} diff --git a/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.html b/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.html new file mode 100644 index 00000000000..aee456a8f2b --- /dev/null +++ b/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.html @@ -0,0 +1,54 @@ + +
+ + + +

{{ "reviewAtRiskLogins" | i18n }}

+

+ {{ "reviewAtRiskLoginsSlideDesc" | i18n }} +

+
+ + +

{{ "generatePassword" | i18n }}

+

+ {{ "generatePasswordSlideDesc" | i18n }} +

+
+ + +

{{ "updateInBitwarden" | i18n }}

+

+ {{ "updateInBitwardenSlideDesc" | i18n }} +

+
+
+
+
+ +
+
diff --git a/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.ts b/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.ts new file mode 100644 index 00000000000..9af9f0aeda3 --- /dev/null +++ b/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.ts @@ -0,0 +1,46 @@ +import { DialogRef } from "@angular/cdk/dialog"; +import { Component, inject, signal } from "@angular/core"; + +import { ButtonModule, DialogModule, DialogService, TypographyModule } from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; +import { DarkImageSourceDirective, VaultCarouselModule } from "@bitwarden/vault"; + +export enum AtRiskCarouselDialogResult { + Dismissed = "dismissed", +} + +@Component({ + selector: "vault-at-risk-carousel-dialog", + templateUrl: "./at-risk-carousel-dialog.component.html", + imports: [ + DialogModule, + VaultCarouselModule, + TypographyModule, + ButtonModule, + DarkImageSourceDirective, + I18nPipe, + ], + standalone: true, +}) +export class AtRiskCarouselDialogComponent { + private dialogRef = inject(DialogRef); + + protected dismissBtnEnabled = signal(false); + + protected async dismiss() { + this.dialogRef.close(AtRiskCarouselDialogResult.Dismissed); + } + + protected onSlideChange(slideIndex: number) { + // Only enable the dismiss button on the last slide + if (slideIndex === 2) { + this.dismissBtnEnabled.set(true); + } + } + + static open(dialogService: DialogService) { + return dialogService.open(AtRiskCarouselDialogComponent, { + disableClose: true, + }); + } +} diff --git a/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-password-page.service.ts b/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-password-page.service.ts new file mode 100644 index 00000000000..4f3c235dc3f --- /dev/null +++ b/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-password-page.service.ts @@ -0,0 +1,54 @@ +import { inject, Injectable } from "@angular/core"; +import { map, Observable } from "rxjs"; + +import { + AT_RISK_PASSWORDS_PAGE_DISK, + StateProvider, + UserKeyDefinition, +} from "@bitwarden/common/platform/state"; +import { UserId } from "@bitwarden/common/types/guid"; + +const AUTOFILL_CALLOUT_DISMISSED_KEY = new UserKeyDefinition( + AT_RISK_PASSWORDS_PAGE_DISK, + "autofillCalloutDismissed", + { + deserializer: (bannersDismissed) => bannersDismissed, + clearOn: [], // Do not clear dismissed callout + }, +); + +const GETTING_STARTED_CAROUSEL_DISMISSED_KEY = new UserKeyDefinition( + AT_RISK_PASSWORDS_PAGE_DISK, + "gettingStartedCarouselDismissed", + { + deserializer: (bannersDismissed) => bannersDismissed, + clearOn: [], // Do not clear dismissed carousel + }, +); + +@Injectable() +export class AtRiskPasswordPageService { + private stateProvider = inject(StateProvider); + + isCalloutDismissed(userId: UserId): Observable { + return this.stateProvider + .getUser(userId, AUTOFILL_CALLOUT_DISMISSED_KEY) + .state$.pipe(map((dismissed) => !!dismissed)); + } + + async dismissCallout(userId: UserId): Promise { + await this.stateProvider.getUser(userId, AUTOFILL_CALLOUT_DISMISSED_KEY).update(() => true); + } + + isGettingStartedDismissed(userId: UserId): Observable { + return this.stateProvider + .getUser(userId, GETTING_STARTED_CAROUSEL_DISMISSED_KEY) + .state$.pipe(map((dismissed) => !!dismissed)); + } + + async dismissGettingStarted(userId: UserId): Promise { + await this.stateProvider + .getUser(userId, GETTING_STARTED_CAROUSEL_DISMISSED_KEY) + .update(() => true); + } +} diff --git a/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.html b/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.html new file mode 100644 index 00000000000..cd93401c861 --- /dev/null +++ b/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.html @@ -0,0 +1,82 @@ + + + + + + + + +

{{ "changeAtRiskPasswordsFasterDesc" | i18n }}

+ + +
+ + +

{{ pageDescription$ | async }}

+ + + + + + + + + +
+
diff --git a/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.spec.ts b/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.spec.ts new file mode 100644 index 00000000000..3bf786ad5b7 --- /dev/null +++ b/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.spec.ts @@ -0,0 +1,323 @@ +import { Component, Input } from "@angular/core"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { By } from "@angular/platform-browser"; +import { mock } from "jest-mock-extended"; +import { BehaviorSubject, firstValueFrom, of } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { IconComponent } from "@bitwarden/angular/vault/components/icon.component"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants"; +import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; +import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { DialogService, ToastService } from "@bitwarden/components"; +import { + ChangeLoginPasswordService, + DefaultChangeLoginPasswordService, + PasswordRepromptService, + SecurityTask, + SecurityTaskType, + TaskService, +} from "@bitwarden/vault"; + +import { PopupHeaderComponent } from "../../../../platform/popup/layout/popup-header.component"; +import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page.component"; +import { AtRiskCarouselDialogResult } from "../at-risk-carousel-dialog/at-risk-carousel-dialog.component"; + +import { AtRiskPasswordPageService } from "./at-risk-password-page.service"; +import { AtRiskPasswordsComponent } from "./at-risk-passwords.component"; + +@Component({ + standalone: true, + selector: "popup-header", + template: ``, +}) +class MockPopupHeaderComponent { + @Input() pageTitle: string | undefined; + @Input() backAction: (() => void) | undefined; +} + +@Component({ + standalone: true, + selector: "popup-page", + template: ``, +}) +class MockPopupPageComponent { + @Input() loading: boolean | undefined; +} + +@Component({ + standalone: true, + selector: "app-vault-icon", + template: ``, +}) +class MockAppIcon { + @Input() cipher: CipherView | undefined; +} + +describe("AtRiskPasswordsComponent", () => { + let component: AtRiskPasswordsComponent; + let fixture: ComponentFixture; + + let mockTasks$: BehaviorSubject; + let mockCiphers$: BehaviorSubject; + let mockOrgs$: BehaviorSubject; + let mockInlineMenuVisibility$: BehaviorSubject; + let calloutDismissed$: BehaviorSubject; + const setInlineMenuVisibility = jest.fn(); + const mockToastService = mock(); + const mockAtRiskPasswordPageService = mock(); + const mockChangeLoginPasswordService = mock(); + const mockDialogService = mock(); + + beforeEach(async () => { + mockTasks$ = new BehaviorSubject([ + { + id: "task", + organizationId: "org", + cipherId: "cipher", + type: SecurityTaskType.UpdateAtRiskCredential, + } as SecurityTask, + ]); + mockCiphers$ = new BehaviorSubject([ + { + id: "cipher", + organizationId: "org", + name: "Item 1", + } as CipherView, + { + id: "cipher2", + organizationId: "org", + name: "Item 2", + } as CipherView, + ]); + mockOrgs$ = new BehaviorSubject([ + { + id: "org", + name: "Org 1", + } as Organization, + ]); + + mockInlineMenuVisibility$ = new BehaviorSubject( + AutofillOverlayVisibility.Off, + ); + + calloutDismissed$ = new BehaviorSubject(false); + setInlineMenuVisibility.mockClear(); + mockToastService.showToast.mockClear(); + mockDialogService.open.mockClear(); + mockAtRiskPasswordPageService.isCalloutDismissed.mockReturnValue(calloutDismissed$); + + await TestBed.configureTestingModule({ + imports: [AtRiskPasswordsComponent], + providers: [ + { + provide: TaskService, + useValue: { + pendingTasks$: () => mockTasks$, + }, + }, + { + provide: OrganizationService, + useValue: { + organizations$: () => mockOrgs$, + }, + }, + { + provide: CipherService, + useValue: { + cipherViews$: () => mockCiphers$, + }, + }, + { provide: I18nService, useValue: { t: (key: string) => key } }, + { provide: AccountService, useValue: { activeAccount$: of({ id: "user" }) } }, + { provide: PlatformUtilsService, useValue: mock() }, + { provide: PasswordRepromptService, useValue: mock() }, + { + provide: AutofillSettingsServiceAbstraction, + useValue: { + inlineMenuVisibility$: mockInlineMenuVisibility$, + setInlineMenuVisibility: setInlineMenuVisibility, + }, + }, + { provide: ToastService, useValue: mockToastService }, + ], + }) + .overrideModule(JslibModule, { + remove: { + imports: [IconComponent], + exports: [IconComponent], + }, + add: { + imports: [MockAppIcon], + exports: [MockAppIcon], + }, + }) + .overrideComponent(AtRiskPasswordsComponent, { + remove: { + imports: [PopupHeaderComponent, PopupPageComponent], + providers: [ + AtRiskPasswordPageService, + { provide: ChangeLoginPasswordService, useClass: DefaultChangeLoginPasswordService }, + DialogService, + ], + }, + add: { + imports: [MockPopupHeaderComponent, MockPopupPageComponent], + providers: [ + { provide: AtRiskPasswordPageService, useValue: mockAtRiskPasswordPageService }, + { provide: ChangeLoginPasswordService, useValue: mockChangeLoginPasswordService }, + { provide: DialogService, useValue: mockDialogService }, + ], + }, + }) + .compileComponents(); + + fixture = TestBed.createComponent(AtRiskPasswordsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); + + describe("pending atRiskItems$", () => { + it("should list pending at risk item tasks", async () => { + const items = await firstValueFrom(component["atRiskItems$"]); + expect(items).toHaveLength(1); + expect(items[0].name).toBe("Item 1"); + }); + }); + + describe("pageDescription$", () => { + it("should use single org description when tasks belong to one org", async () => { + // Single task + let description = await firstValueFrom(component["pageDescription$"]); + expect(description).toBe("atRiskPasswordDescSingleOrg"); + + // Multiple tasks + mockTasks$.next([ + { + id: "task", + organizationId: "org", + cipherId: "cipher", + type: SecurityTaskType.UpdateAtRiskCredential, + } as SecurityTask, + { + id: "task2", + organizationId: "org", + cipherId: "cipher2", + type: SecurityTaskType.UpdateAtRiskCredential, + } as SecurityTask, + ]); + description = await firstValueFrom(component["pageDescription$"]); + expect(description).toBe("atRiskPasswordsDescSingleOrgPlural"); + }); + + it("should use multiple org description when tasks belong to multiple orgs", async () => { + mockTasks$.next([ + { + id: "task", + organizationId: "org", + cipherId: "cipher", + type: SecurityTaskType.UpdateAtRiskCredential, + } as SecurityTask, + { + id: "task2", + organizationId: "org2", + cipherId: "cipher2", + type: SecurityTaskType.UpdateAtRiskCredential, + } as SecurityTask, + ]); + const description = await firstValueFrom(component["pageDescription$"]); + expect(description).toBe("atRiskPasswordsDescMultiOrgPlural"); + }); + }); + + describe("autofill callout", () => { + it("should show the callout if inline autofill is disabled", async () => { + mockInlineMenuVisibility$.next(AutofillOverlayVisibility.Off); + calloutDismissed$.next(false); + fixture.detectChanges(); + const callout = fixture.debugElement.query(By.css('[data-testid="autofill-callout"]')); + + expect(callout).toBeTruthy(); + }); + + it("should hide the callout if inline autofill is enabled", async () => { + mockInlineMenuVisibility$.next(AutofillOverlayVisibility.OnButtonClick); + calloutDismissed$.next(false); + fixture.detectChanges(); + const callout = fixture.debugElement.query(By.css('[data-testid="autofill-callout"]')); + + expect(callout).toBeFalsy(); + }); + + it("should hide the callout if the user has previously dismissed it", async () => { + mockInlineMenuVisibility$.next(AutofillOverlayVisibility.Off); + calloutDismissed$.next(true); + fixture.detectChanges(); + const callout = fixture.debugElement.query(By.css('[data-testid="autofill-callout"]')); + + expect(callout).toBeFalsy(); + }); + + it("should call dismissCallout when the dismiss callout button is clicked", async () => { + mockInlineMenuVisibility$.next(AutofillOverlayVisibility.Off); + fixture.detectChanges(); + const dismissButton = fixture.debugElement.query( + By.css('[data-testid="dismiss-callout-button"]'), + ); + dismissButton.nativeElement.click(); + expect(mockAtRiskPasswordPageService.dismissCallout).toHaveBeenCalled(); + }); + + describe("turn on autofill button", () => { + it("should call the service to turn on inline autofill and show a toast", () => { + const button = fixture.debugElement.query( + By.css('[data-testid="turn-on-autofill-button"]'), + ); + button.nativeElement.click(); + + expect(setInlineMenuVisibility).toHaveBeenCalledWith( + AutofillOverlayVisibility.OnButtonClick, + ); + expect(mockToastService.showToast).toHaveBeenCalled(); + }); + }); + }); + + describe("getting started carousel", () => { + it("should open the carousel automatically if the user has not dismissed it", async () => { + mockAtRiskPasswordPageService.isGettingStartedDismissed.mockReturnValue(of(false)); + mockDialogService.open.mockReturnValue({ closed: of(undefined) } as any); + await component.ngOnInit(); + expect(mockDialogService.open).toHaveBeenCalled(); + }); + + it("should not open the carousel automatically if the user has already dismissed it", async () => { + mockDialogService.open.mockClear(); // Need to clear the mock since the component is already initialized once + mockAtRiskPasswordPageService.isGettingStartedDismissed.mockReturnValue(of(true)); + mockDialogService.open.mockReturnValue({ closed: of(undefined) } as any); + await component.ngOnInit(); + expect(mockDialogService.open).not.toHaveBeenCalled(); + }); + + it("should mark the carousel as dismissed when the user dismisses it", async () => { + mockAtRiskPasswordPageService.isGettingStartedDismissed.mockReturnValue(of(false)); + mockDialogService.open.mockReturnValue({ + closed: of(AtRiskCarouselDialogResult.Dismissed), + } as any); + await component.ngOnInit(); + expect(mockDialogService.open).toHaveBeenCalled(); + expect(mockAtRiskPasswordPageService.dismissGettingStarted).toHaveBeenCalled(); + }); + }); +}); diff --git a/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.ts b/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.ts new file mode 100644 index 00000000000..dd3d53fed7d --- /dev/null +++ b/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.ts @@ -0,0 +1,226 @@ +import { CommonModule } from "@angular/common"; +import { Component, inject, OnInit, signal } from "@angular/core"; +import { Router } from "@angular/router"; +import { combineLatest, firstValueFrom, map, of, shareReplay, startWith, switchMap } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { + getOrganizationById, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants"; +import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { + BadgeModule, + ButtonModule, + CalloutModule, + DialogModule, + DialogService, + ItemModule, + ToastService, + TypographyModule, +} from "@bitwarden/components"; +import { + ChangeLoginPasswordService, + DefaultChangeLoginPasswordService, + filterOutNullish, + PasswordRepromptService, + SecurityTaskType, + TaskService, + VaultCarouselModule, +} from "@bitwarden/vault"; + +import { PopOutComponent } from "../../../../platform/popup/components/pop-out.component"; +import { PopupHeaderComponent } from "../../../../platform/popup/layout/popup-header.component"; +import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page.component"; +import { + AtRiskCarouselDialogComponent, + AtRiskCarouselDialogResult, +} from "../at-risk-carousel-dialog/at-risk-carousel-dialog.component"; + +import { AtRiskPasswordPageService } from "./at-risk-password-page.service"; + +@Component({ + imports: [ + PopupPageComponent, + PopupHeaderComponent, + PopOutComponent, + ItemModule, + CommonModule, + JslibModule, + TypographyModule, + CalloutModule, + ButtonModule, + BadgeModule, + DialogModule, + VaultCarouselModule, + ], + providers: [ + AtRiskPasswordPageService, + { provide: ChangeLoginPasswordService, useClass: DefaultChangeLoginPasswordService }, + ], + selector: "vault-at-risk-passwords", + standalone: true, + templateUrl: "./at-risk-passwords.component.html", +}) +export class AtRiskPasswordsComponent implements OnInit { + private taskService = inject(TaskService); + private organizationService = inject(OrganizationService); + private cipherService = inject(CipherService); + private i18nService = inject(I18nService); + private accountService = inject(AccountService); + private passwordRepromptService = inject(PasswordRepromptService); + private router = inject(Router); + private autofillSettingsService = inject(AutofillSettingsServiceAbstraction); + private toastService = inject(ToastService); + private atRiskPasswordPageService = inject(AtRiskPasswordPageService); + private changeLoginPasswordService = inject(ChangeLoginPasswordService); + private platformUtilsService = inject(PlatformUtilsService); + private dialogService = inject(DialogService); + + /** + * The cipher that is currently being launched. Used to show a loading spinner on the badge button. + * The UI utilize a bitBadge which does not support async actions (like bitButton does). + * @protected + */ + protected launchingCipher = signal(null); + + private activeUserData$ = this.accountService.activeAccount$.pipe( + filterOutNullish(), + switchMap((user) => + combineLatest([ + this.taskService.pendingTasks$(user.id), + this.cipherService.cipherViews$(user.id).pipe( + filterOutNullish(), + map((ciphers) => Object.fromEntries(ciphers.map((c) => [c.id, c]))), + ), + of(user), + ]), + ), + map(([tasks, ciphers, user]) => ({ + tasks, + ciphers, + userId: user.id, + })), + shareReplay({ bufferSize: 1, refCount: true }), + ); + + protected loading$ = this.activeUserData$.pipe( + map(() => false), + startWith(true), + ); + + private calloutDismissed$ = this.activeUserData$.pipe( + switchMap(({ userId }) => this.atRiskPasswordPageService.isCalloutDismissed(userId)), + ); + private inlineAutofillSettingEnabled$ = this.autofillSettingsService.inlineMenuVisibility$.pipe( + map((setting) => setting !== AutofillOverlayVisibility.Off), + ); + + protected showAutofillCallout$ = combineLatest([ + this.calloutDismissed$, + this.inlineAutofillSettingEnabled$, + ]).pipe( + map(([calloutDismissed, inlineAutofillSettingEnabled]) => { + return !calloutDismissed && !inlineAutofillSettingEnabled; + }), + startWith(false), + ); + + protected atRiskItems$ = this.activeUserData$.pipe( + map(({ tasks, ciphers }) => + tasks + .filter( + (t) => + t.type === SecurityTaskType.UpdateAtRiskCredential && + t.cipherId != null && + ciphers[t.cipherId] != null, + ) + .map((t) => ciphers[t.cipherId!]), + ), + ); + + protected pageDescription$ = this.activeUserData$.pipe( + switchMap(({ tasks, userId }) => { + const orgIds = new Set(tasks.map((t) => t.organizationId)); + if (orgIds.size === 1) { + const [orgId] = orgIds; + return this.organizationService.organizations$(userId).pipe( + getOrganizationById(orgId), + map((org) => + this.i18nService.t( + tasks.length === 1 + ? "atRiskPasswordDescSingleOrg" + : "atRiskPasswordsDescSingleOrgPlural", + org?.name, + tasks.length, + ), + ), + ); + } + + return of(this.i18nService.t("atRiskPasswordsDescMultiOrgPlural", tasks.length)); + }), + ); + + async ngOnInit() { + const { userId } = await firstValueFrom(this.activeUserData$); + const gettingStartedDismissed = await firstValueFrom( + this.atRiskPasswordPageService.isGettingStartedDismissed(userId), + ); + if (!gettingStartedDismissed) { + const ref = AtRiskCarouselDialogComponent.open(this.dialogService); + + const result = await firstValueFrom(ref.closed); + if (result === AtRiskCarouselDialogResult.Dismissed) { + await this.atRiskPasswordPageService.dismissGettingStarted(userId); + } + } + } + + async viewCipher(cipher: CipherView) { + const repromptPassed = await this.passwordRepromptService.passwordRepromptCheck(cipher); + if (!repromptPassed) { + return; + } + await this.router.navigate(["/view-cipher"], { + queryParams: { cipherId: cipher.id, type: cipher.type }, + }); + } + + async activateInlineAutofillMenuVisibility() { + await this.autofillSettingsService.setInlineMenuVisibility( + AutofillOverlayVisibility.OnButtonClick, + ); + this.toastService.showToast({ + variant: "success", + message: this.i18nService.t("turnedOnAutofill"), + title: "", + }); + } + + async dismissCallout() { + const { userId } = await firstValueFrom(this.activeUserData$); + await this.atRiskPasswordPageService.dismissCallout(userId); + } + + launchChangePassword = async (cipher: CipherView) => { + try { + this.launchingCipher.set(cipher); + const url = await this.changeLoginPasswordService.getChangePasswordUrl(cipher); + + if (url == null) { + return; + } + + this.platformUtilsService.launchUri(url); + } finally { + this.launchingCipher.set(null); + } + }; +} diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.html index a46f5a6955b..21b298fb30a 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.html @@ -2,7 +2,7 @@ @@ -26,5 +26,19 @@ + + + + diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.spec.ts index ebfb1ff765f..6974e6f7359 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.spec.ts @@ -4,14 +4,19 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { EventType } from "@bitwarden/common/enums"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { AddEditCipherInfo } from "@bitwarden/common/vault/types/add-edit-cipher-info"; import { CipherFormConfig, @@ -40,7 +45,7 @@ describe("AddEditV2Component", () => { const buildConfigResponse = { originalCipher: {} } as CipherFormConfig; const buildConfig = jest.fn((mode: CipherFormMode) => - Promise.resolve({ mode, ...buildConfigResponse }), + Promise.resolve({ ...buildConfigResponse, mode }), ); const queryParams$ = new BehaviorSubject({}); const disable = jest.fn(); @@ -55,9 +60,10 @@ describe("AddEditV2Component", () => { back.mockClear(); collect.mockClear(); - addEditCipherInfo$ = new BehaviorSubject(null); - cipherServiceMock = mock(); - cipherServiceMock.addEditCipherInfo$ = addEditCipherInfo$.asObservable(); + addEditCipherInfo$ = new BehaviorSubject(null); + cipherServiceMock = mock({ + addEditCipherInfo$: jest.fn().mockReturnValue(addEditCipherInfo$), + }); await TestBed.configureTestingModule({ imports: [AddEditV2Component], @@ -71,6 +77,14 @@ describe("AddEditV2Component", () => { { provide: I18nService, useValue: { t: (key: string) => key } }, { provide: CipherService, useValue: cipherServiceMock }, { provide: EventCollectionService, useValue: { collect } }, + { provide: LogService, useValue: mock() }, + { + provide: CipherAuthorizationService, + useValue: { + canDeleteCipher$: jest.fn().mockReturnValue(true), + }, + }, + { provide: AccountService, useValue: mockAccountServiceWith("UserId" as UserId) }, ], }) .overrideProvider(CipherFormConfigService, { @@ -92,7 +106,7 @@ describe("AddEditV2Component", () => { tick(); - expect(buildConfig.mock.lastCall[0]).toBe("add"); + expect(buildConfig.mock.lastCall![0]).toBe("add"); expect(component.config.mode).toBe("add"); })); @@ -101,7 +115,7 @@ describe("AddEditV2Component", () => { tick(); - expect(buildConfig.mock.lastCall[0]).toBe("clone"); + expect(buildConfig.mock.lastCall![0]).toBe("clone"); expect(component.config.mode).toBe("clone"); })); @@ -111,7 +125,7 @@ describe("AddEditV2Component", () => { tick(); - expect(buildConfig.mock.lastCall[0]).toBe("edit"); + expect(buildConfig.mock.lastCall![0]).toBe("edit"); expect(component.config.mode).toBe("edit"); })); @@ -121,7 +135,7 @@ describe("AddEditV2Component", () => { tick(); - expect(buildConfig.mock.lastCall[0]).toBe("edit"); + expect(buildConfig.mock.lastCall![0]).toBe("edit"); expect(component.config.mode).toBe("partial-edit"); })); }); @@ -218,7 +232,7 @@ describe("AddEditV2Component", () => { tick(); - expect(component.config.initialValues.username).toBe("identity-username"); + expect(component.config.initialValues!.username).toBe("identity-username"); })); it("overrides query params with `addEditCipherInfo` values", fakeAsync(() => { @@ -231,7 +245,7 @@ describe("AddEditV2Component", () => { tick(); - expect(component.config.initialValues.name).toBe("AddEditCipherName"); + expect(component.config.initialValues!.name).toBe("AddEditCipherName"); })); it("clears `addEditCipherInfo` after initialization", fakeAsync(() => { @@ -326,4 +340,30 @@ describe("AddEditV2Component", () => { expect(back).toHaveBeenCalled(); }); }); + + describe("delete", () => { + it("dialogService openSimpleDialog called when deleteBtn is hit", async () => { + const dialogSpy = jest + .spyOn(component["dialogService"], "openSimpleDialog") + .mockResolvedValue(true); + + await component.delete(); + expect(dialogSpy).toHaveBeenCalled(); + }); + + it("should call deleteCipher when user confirms deletion", async () => { + const deleteCipherSpy = jest.spyOn(component as any, "deleteCipher"); + jest.spyOn(component["dialogService"], "openSimpleDialog").mockResolvedValue(true); + + await component.delete(); + expect(deleteCipherSpy).toHaveBeenCalled(); + }); + + it("navigates to vault tab after deletion", async () => { + jest.spyOn(component["dialogService"], "openSimpleDialog").mockResolvedValue(true); + await component.delete(); + + expect(navigate).toHaveBeenCalledWith(["/tabs/vault"]); + }); + }); }); diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts index 2d8c4857c1c..f986bdfca31 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts @@ -5,18 +5,29 @@ import { Component, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormsModule } from "@angular/forms"; import { ActivatedRoute, Params, Router } from "@angular/router"; -import { firstValueFrom, map, switchMap } from "rxjs"; +import { firstValueFrom, map, Observable, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { EventType } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { CipherId, CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { AddEditCipherInfo } from "@bitwarden/common/vault/types/add-edit-cipher-info"; -import { AsyncActionsModule, ButtonModule, SearchModule } from "@bitwarden/components"; +import { + AsyncActionsModule, + ButtonModule, + SearchModule, + IconButtonModule, + DialogService, + ToastService, +} from "@bitwarden/components"; import { CipherFormConfig, CipherFormConfigService, @@ -131,11 +142,13 @@ export type AddEditQueryParams = Partial>; CipherFormModule, AsyncActionsModule, PopOutComponent, + IconButtonModule, ], }) export class AddEditV2Component implements OnInit { headerText: string; config: CipherFormConfig; + canDeleteCipher$: Observable; get loading() { return this.config == null; @@ -165,6 +178,11 @@ export class AddEditV2Component implements OnInit { private router: Router, private cipherService: CipherService, private eventCollectionService: EventCollectionService, + private logService: LogService, + private toastService: ToastService, + private dialogService: DialogService, + protected cipherAuthorizationService: CipherAuthorizationService, + private accountService: AccountService, ) { this.subscribeToParams(); } @@ -194,7 +212,7 @@ export class AddEditV2Component implements OnInit { /** * Handle back button */ - async handleBackButton() { + handleBackButton = async () => { if (this.inFido2PopoutWindow) { this.popupCloseWarningService.disable(); BrowserFido2UserInterfaceSession.abortPopout(this.fido2PopoutSessionData.sessionId); @@ -207,7 +225,7 @@ export class AddEditV2Component implements OnInit { } await this.popupRouterCacheService.back(); - } + }; async onCipherSaved(cipher: CipherView) { if (BrowserPopupUtils.inPopout(window)) { @@ -266,9 +284,15 @@ export class AddEditV2Component implements OnInit { config.initialValues = this.setInitialValuesFromParams(params); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(getUserId), + ); + // The browser notification bar and overlay use addEditCipherInfo$ to pass modified cipher details to the form // Attempt to fetch them here and overwrite the initialValues if present - const cachedCipherInfo = await firstValueFrom(this.cipherService.addEditCipherInfo$); + const cachedCipherInfo = await firstValueFrom( + this.cipherService.addEditCipherInfo$(activeUserId), + ); if (cachedCipherInfo != null) { // Cached cipher info has priority over queryParams @@ -277,10 +301,14 @@ export class AddEditV2Component implements OnInit { ...mapAddEditCipherInfoToInitialValues(cachedCipherInfo), }; // Be sure to clear the "cached" cipher info, so it doesn't get used again - await this.cipherService.setAddEditCipherInfo(null); + await this.cipherService.setAddEditCipherInfo(null, activeUserId); } if (["edit", "partial-edit"].includes(config.mode) && config.originalCipher?.id) { + this.canDeleteCipher$ = this.cipherAuthorizationService.canDeleteCipher$( + config.originalCipher, + ); + await this.eventCollectionService.collect( EventType.Cipher_ClientViewed, config.originalCipher.id, @@ -337,6 +365,44 @@ export class AddEditV2Component implements OnInit { return this.i18nService.t(partOne, this.i18nService.t("typeSshKey")); } } + + delete = async () => { + const confirmed = await this.dialogService.openSimpleDialog({ + title: { key: "deleteItem" }, + content: { + key: "deleteItemConfirmation", + }, + type: "warning", + }); + + if (!confirmed) { + return false; + } + + try { + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + await this.deleteCipher(activeUserId); + } catch (e) { + this.logService.error(e); + return false; + } + + await this.router.navigate(["/tabs/vault"]); + + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("deletedItem"), + }); + + return true; + }; + + protected deleteCipher(userId: UserId) { + return this.config.originalCipher.deletedDate + ? this.cipherService.deleteWithServer(this.config.originalCipher.id, userId) + : this.cipherService.softDeleteWithServer(this.config.originalCipher.id, userId); + } } /** @@ -374,6 +440,32 @@ const mapAddEditCipherInfoToInitialValues = ( initialValues.name = cipher.name; } + if (cipher.type === CipherType.Card) { + const card = cipher.card; + + if (card != null) { + if (card.cardholderName != null) { + initialValues.cardholderName = card.cardholderName; + } + + if (card.number != null) { + initialValues.number = card.number; + } + + if (card.expMonth != null) { + initialValues.expMonth = card.expMonth; + } + + if (card.expYear != null) { + initialValues.expYear = card.expYear; + } + + if (card.code != null) { + initialValues.code = card.code; + } + } + } + if (cipher.type === CipherType.Login) { const login = cipher.login; diff --git a/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts b/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts index 51ebe9bdb62..27f3b7e5e18 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts @@ -5,12 +5,13 @@ import { Component } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ReactiveFormsModule } from "@angular/forms"; import { ActivatedRoute } from "@angular/router"; -import { Observable, combineLatest, first, map, switchMap } from "rxjs"; +import { Observable, combineLatest, filter, first, map, switchMap } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { OrganizationId } from "@bitwarden/common/types/guid"; +import { OrgKey, UserKey } from "@bitwarden/common/types/key"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { @@ -58,16 +59,19 @@ export class AssignCollections { private accountService: AccountService, route: ActivatedRoute, ) { - const cipher$: Observable = route.queryParams.pipe( - switchMap(({ cipherId }) => this.cipherService.get(cipherId)), - switchMap((cipherDomain) => - this.accountService.activeAccount$.pipe( - map((account) => account?.id), - switchMap((userId) => - this.cipherService - .getKeyForCipherKeyDecryption(cipherDomain, userId) - .then(cipherDomain.decrypt.bind(cipherDomain)), - ), + const cipher$: Observable = this.accountService.activeAccount$.pipe( + map((account) => account?.id), + filter((userId) => userId != null), + switchMap((userId) => + route.queryParams.pipe( + switchMap(async ({ cipherId }) => { + const cipherDomain = await this.cipherService.get(cipherId, userId); + const key: UserKey | OrgKey = await this.cipherService.getKeyForCipherKeyDecryption( + cipherDomain, + userId, + ); + return cipherDomain.decrypt(key); + }), ), ), ); diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.html index 58ad816bfc5..08abcf1fd2b 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.html @@ -20,5 +20,8 @@ > {{ "upload" | i18n }} + diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts index 4f6c4aa07cf..66d9096cd5c 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts @@ -50,7 +50,7 @@ describe("OpenAttachmentsComponent", () => { } as Organization; const getCipher = jest.fn().mockResolvedValue(cipherDomain); - const getOrganization = jest.fn().mockResolvedValue(org); + const organizations$ = jest.fn().mockReturnValue(of([org])); const showFilePopoutMessage = jest.fn().mockReturnValue(false); const mockUserId = Utils.newGuid() as UserId; @@ -67,7 +67,7 @@ describe("OpenAttachmentsComponent", () => { openCurrentPagePopout.mockClear(); getCipher.mockClear(); showToast.mockClear(); - getOrganization.mockClear(); + organizations$.mockClear(); showFilePopoutMessage.mockClear(); hasPremiumFromAnySource$.next(true); @@ -89,7 +89,7 @@ describe("OpenAttachmentsComponent", () => { }, { provide: OrganizationService, - useValue: { get: getOrganization }, + useValue: { organizations$ }, }, { provide: FilePopoutUtilsService, @@ -148,11 +148,11 @@ describe("OpenAttachmentsComponent", () => { describe("Free Orgs", () => { beforeEach(() => { - component.cipherIsAPartOfFreeOrg = undefined; + component.cipherIsAPartOfFreeOrg = false; }); it("sets `cipherIsAPartOfFreeOrg` to false when the cipher is not a part of an organization", async () => { - cipherView.organizationId = null; + cipherView.organizationId = ""; await component.ngOnInit(); @@ -162,6 +162,7 @@ describe("OpenAttachmentsComponent", () => { it("sets `cipherIsAPartOfFreeOrg` to true when the cipher is a part of a free organization", async () => { cipherView.organizationId = "888-333-333"; org.productTierType = ProductTierType.Free; + org.id = cipherView.organizationId; await component.ngOnInit(); @@ -171,6 +172,7 @@ describe("OpenAttachmentsComponent", () => { it("sets `cipherIsAPartOfFreeOrg` to false when the organization is not free", async () => { cipherView.organizationId = "888-333-333"; org.productTierType = ProductTierType.Families; + org.id = cipherView.organizationId; await component.ngOnInit(); diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts index 5e27ccd5c41..1bc7e22e6d5 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts @@ -7,8 +7,12 @@ import { Router } from "@angular/router"; import { firstValueFrom, map, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { ProductTierType } from "@bitwarden/common/billing/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -73,10 +77,10 @@ export class OpenAttachmentsComponent implements OnInit { return; } - const cipherDomain = await this.cipherService.get(this.cipherId); const activeUserId = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); + const cipherDomain = await this.cipherService.get(this.cipherId, activeUserId); const cipher = await cipherDomain.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId), ); @@ -86,7 +90,12 @@ export class OpenAttachmentsComponent implements OnInit { return; } - const org = await this.organizationService.get(cipher.organizationId); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + const org = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(cipher.organizationId)), + ); this.cipherIsAPartOfFreeOrg = org.productTierType === ProductTierType.Free; } diff --git a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html index 7c4ea3e5b46..40f00ab4332 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html @@ -1,10 +1,12 @@ diff --git a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts index deba204bd71..03d84120785 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts @@ -1,5 +1,6 @@ import { CommonModule } from "@angular/common"; import { Component, OnInit } from "@angular/core"; +import { toSignal } from "@angular/core/rxjs-interop"; import { combineLatest, firstValueFrom, map, Observable } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; @@ -48,6 +49,10 @@ export class AutofillVaultListItemsComponent implements OnInit { clickItemsToAutofillVaultView = false; + protected groupByType = toSignal( + this.vaultPopupItemsService.hasFilterApplied$.pipe(map((hasFilter) => !hasFilter)), + ); + /** * Observable that determines whether the empty autofill tip should be shown. * The tip is shown when there are no login ciphers to autofill, no filter is applied, and autofill is allowed in @@ -65,6 +70,12 @@ export class AutofillVaultListItemsComponent implements OnInit { ), ); + /** + * Flag indicating that the current tab location is blocked + */ + currentURIIsBlocked$: Observable = + this.vaultPopupAutofillService.currentTabIsOnBlocklist$; + constructor( private vaultPopupItemsService: VaultPopupItemsService, private vaultPopupAutofillService: VaultPopupAutofillService, diff --git a/apps/browser/src/vault/popup/components/vault-v2/blocked-injection-banner/blocked-injection-banner.component.html b/apps/browser/src/vault/popup/components/vault-v2/blocked-injection-banner/blocked-injection-banner.component.html new file mode 100644 index 00000000000..05db600bd5a --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/blocked-injection-banner/blocked-injection-banner.component.html @@ -0,0 +1,10 @@ + + {{ "autofillBlockedNoticeV2" | i18n }} + + {{ "autofillBlockedNoticeGuidance" | i18n }} + + diff --git a/apps/browser/src/vault/popup/components/vault-v2/blocked-injection-banner/blocked-injection-banner.component.ts b/apps/browser/src/vault/popup/components/vault-v2/blocked-injection-banner/blocked-injection-banner.component.ts new file mode 100644 index 00000000000..3a17825f4fb --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/blocked-injection-banner/blocked-injection-banner.component.ts @@ -0,0 +1,53 @@ +import { CommonModule } from "@angular/common"; +import { Component, OnInit } from "@angular/core"; +import { RouterModule } from "@angular/router"; +import { Observable } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { + BannerModule, + IconButtonModule, + LinkModule, + TypographyModule, +} from "@bitwarden/components"; + +import { VaultPopupAutofillService } from "../../../services/vault-popup-autofill.service"; + +const blockedURISettingsRoute = "/blocked-domains"; + +@Component({ + standalone: true, + imports: [ + BannerModule, + CommonModule, + IconButtonModule, + JslibModule, + LinkModule, + RouterModule, + TypographyModule, + ], + selector: "blocked-injection-banner", + templateUrl: "blocked-injection-banner.component.html", +}) +export class BlockedInjectionBanner implements OnInit { + /** + * Flag indicating that the banner should be shown + */ + protected showCurrentTabIsBlockedBanner$: Observable = + this.vaultPopupAutofillService.showCurrentTabIsBlockedBanner$; + + /** + * Hostname for current tab + */ + protected currentTabHostname?: string; + + blockedURISettingsRoute: string = blockedURISettingsRoute; + + constructor(private vaultPopupAutofillService: VaultPopupAutofillService) {} + + async ngOnInit() {} + + async handleCurrentTabIsBlockedBannerDismiss() { + await this.vaultPopupAutofillService.dismissCurrentTabIsBlockedBanner(); + } +} diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.html b/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.html index fbfebe8efff..bb3a7b12096 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.html @@ -36,32 +36,46 @@ - - + - - + bitIconButton="bwi-clone" + size="small" + [appA11yTitle]=" + hasLoginValues ? ('copyInfoTitle' | i18n: cipher.name) : ('noValuesToCopy' | i18n) + " + [disabled]="!hasLoginValues" + [bitMenuTriggerFor]="loginOptions" + > + + + + + + @@ -92,52 +106,78 @@ - - - - + + + + + + + - - - - - - + + + + + + + + + diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.ts b/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.ts index 53439dc4abd..a51e5f5406a 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.ts @@ -4,6 +4,7 @@ import { CommonModule } from "@angular/common"; import { Component, Input, inject } from "@angular/core"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { IconButtonModule, ItemModule, MenuModule } from "@bitwarden/components"; @@ -11,6 +12,11 @@ import { CopyCipherFieldDirective } from "@bitwarden/vault"; import { VaultPopupCopyButtonsService } from "../../../services/vault-popup-copy-buttons.service"; +type CipherItem = { + value: string; + key: string; +}; + @Component({ standalone: true, selector: "app-item-copy-actions", @@ -37,6 +43,50 @@ export class ItemCopyActionsComponent { ); } + get singleCopiableLogin() { + const loginItems: CipherItem[] = [ + { value: this.cipher.login.username, key: "username" }, + { value: this.cipher.login.password, key: "password" }, + { value: this.cipher.login.totp, key: "totp" }, + ]; + // If both the password and username are visible but the password is hidden, return the username + if (!this.cipher.viewPassword && this.cipher.login.username && this.cipher.login.password) { + return { value: this.cipher.login.username, key: this.i18nService.t("username") }; + } + return this.findSingleCopiableItem(loginItems); + } + + get singleCopiableCard() { + const cardItems: CipherItem[] = [ + { value: this.cipher.card.code, key: "code" }, + { value: this.cipher.card.number, key: "number" }, + ]; + return this.findSingleCopiableItem(cardItems); + } + + get singleCopiableIdentity() { + const identityItems: CipherItem[] = [ + { value: this.cipher.identity.fullAddressForCopy, key: "address" }, + { value: this.cipher.identity.email, key: "email" }, + { value: this.cipher.identity.username, key: "username" }, + { value: this.cipher.identity.phone, key: "phone" }, + ]; + return this.findSingleCopiableItem(identityItems); + } + + /* + * Given a list of CipherItems, if there is only one item with a value, + * return it with the translated key. Otherwise return null + */ + findSingleCopiableItem(items: { value: string; key: string }[]): CipherItem | null { + const singleItemWithValue = items.find( + (key) => key.value && items.every((f) => f === key || !f.value), + ); + return singleItemWithValue + ? { value: singleItemWithValue.value, key: this.i18nService.t(singleItemWithValue.key) } + : null; + } + get hasCardValues() { return !!this.cipher.card.code || !!this.cipher.card.number; } @@ -62,5 +112,5 @@ export class ItemCopyActionsComponent { ); } - constructor() {} + constructor(private i18nService: I18nService) {} } diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.html b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.html index 4c7067df53a..6e6e30b359b 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.html @@ -27,7 +27,7 @@ - + {{ "clone" | i18n }} diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts index 5d3dee9018e..94b4c2b855b 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 @@ -9,6 +9,7 @@ import { filter } from "rxjs/operators"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherRepromptType, CipherType } from "@bitwarden/common/vault/enums"; @@ -88,13 +89,17 @@ export class ItemMoreOptionsComponent implements OnInit { ) {} async ngOnInit(): Promise { - this.hasOrganizations = await this.organizationService.hasOrganizations(); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + this.hasOrganizations = await firstValueFrom(this.organizationService.hasOrganizations(userId)); } get canEdit() { return this.cipher.edit; } + get canViewPassword() { + return this.cipher.viewPassword; + } /** * Determines if the cipher can be autofilled. */ diff --git a/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.html index 4d617ff7786..c952260a9a9 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.html @@ -27,12 +27,7 @@ {{ "note" | i18n }} - + {{ "typeSshKey" | i18n }} diff --git a/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.ts index d57b1d2fe36..bb452b89c7b 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.ts @@ -5,17 +5,15 @@ import { Component, Input, OnInit } from "@angular/core"; import { Router, RouterLink } from "@angular/router"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; import { CipherType } from "@bitwarden/common/vault/enums"; import { ButtonModule, DialogService, MenuModule, NoItemsModule } from "@bitwarden/components"; +import { AddEditFolderDialogComponent } from "@bitwarden/vault"; import { BrowserApi } from "../../../../../platform/browser/browser-api"; import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils"; import { AddEditQueryParams } from "../add-edit/add-edit-v2.component"; -import { AddEditFolderDialogComponent } from "../add-edit-folder-dialog/add-edit-folder-dialog.component"; export interface NewItemInitialValues { folderId?: string; @@ -40,13 +38,9 @@ export class NewItemDropdownV2Component implements OnInit { constructor( private router: Router, private dialogService: DialogService, - private configService: ConfigService, ) {} - sshKeysEnabled = false; - async ngOnInit() { - this.sshKeysEnabled = await this.configService.getFeatureFlag(FeatureFlag.SSHKeyVaultItem); this.tab = await BrowserApi.getTabFromCurrentWindow(); } @@ -72,6 +66,6 @@ export class NewItemDropdownV2Component implements OnInit { } openFolderDialog() { - this.dialogService.open(AddEditFolderDialogComponent); + AddEditFolderDialogComponent.open(this.dialogService); } } diff --git a/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.html b/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.html new file mode 100644 index 00000000000..6cc60eed6d5 --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.html @@ -0,0 +1,29 @@ + + + +
+ {{ "newCustomizationOptionsCalloutContent" | i18n }} + + {{ "newCustomizationOptionsCalloutLink" | i18n }} + +
+
+
diff --git a/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.ts b/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.ts new file mode 100644 index 00000000000..713dc21c424 --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.ts @@ -0,0 +1,81 @@ +import { CommonModule } from "@angular/common"; +import { Component, OnDestroy, OnInit } from "@angular/core"; +import { Router } from "@angular/router"; +import { firstValueFrom } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { UserId } from "@bitwarden/common/types/guid"; +import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; +import { ButtonModule, PopoverModule } from "@bitwarden/components"; + +import { VaultPopupCopyButtonsService } from "../../../services/vault-popup-copy-buttons.service"; +import { VaultPageService } from "../vault-page.service"; + +@Component({ + selector: "new-settings-callout", + templateUrl: "new-settings-callout.component.html", + standalone: true, + imports: [PopoverModule, JslibModule, CommonModule, ButtonModule], + providers: [VaultPageService], +}) +export class NewSettingsCalloutComponent implements OnInit, OnDestroy { + protected showNewCustomizationSettingsCallout = false; + protected activeUserId: UserId | null = null; + + constructor( + private accountService: AccountService, + private vaultProfileService: VaultProfileService, + private vaultPageService: VaultPageService, + private router: Router, + private logService: LogService, + private copyButtonService: VaultPopupCopyButtonsService, + private vaultSettingsService: VaultSettingsService, + ) {} + + async ngOnInit() { + this.activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + + const showQuickCopyActions = await firstValueFrom(this.copyButtonService.showQuickCopyActions$); + const clickItemsToAutofillVaultView = await firstValueFrom( + this.vaultSettingsService.clickItemsToAutofillVaultView$, + ); + + let profileCreatedDate: Date; + + try { + profileCreatedDate = await this.vaultProfileService.getProfileCreationDate(this.activeUserId); + } catch (e) { + this.logService.error("Error getting profile creation date", e); + // Default to before the cutoff date to ensure the callout is shown + profileCreatedDate = new Date("2024-12-24"); + } + + const hasCalloutBeenDismissed = await firstValueFrom( + this.vaultPageService.isCalloutDismissed(this.activeUserId), + ); + + this.showNewCustomizationSettingsCallout = + !showQuickCopyActions && + !clickItemsToAutofillVaultView && + !hasCalloutBeenDismissed && + profileCreatedDate < new Date("2024-12-25"); + } + + async goToAppearance() { + await this.router.navigate(["/appearance"]); + } + + async dismissCallout() { + if (this.activeUserId) { + await this.vaultPageService.dismissCallout(this.activeUserId); + } + } + + async ngOnDestroy() { + await this.dismissCallout(); + } +} diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.html index 72aaeea493d..ccff7313258 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.html @@ -3,12 +3,14 @@ slot="header" [backAction]="close" showBackButton - [pageTitle]="title" + [pageTitle]="titleKey | i18n" >
@@ -18,6 +20,7 @@ buttonType="primary" (click)="selectValue()" data-testid="select-button" + [disabled]="!(selectButtonText && generatedValue)" > {{ selectButtonText }} diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.spec.ts index d37bc367110..9c94f8fc63f 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.spec.ts @@ -1,15 +1,18 @@ import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog"; import { Component, EventEmitter, Input, Output } from "@angular/core"; import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { By } from "@angular/platform-browser"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; import { mock, MockProxy } from "jest-mock-extended"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { AlgorithmInfo } from "@bitwarden/generator-core"; import { CipherFormGeneratorComponent } from "@bitwarden/vault"; import { PopupRouterCacheService } from "../../../../../platform/popup/view-cache/popup-router-cache.service"; import { + GeneratorDialogAction, GeneratorDialogParams, GeneratorDialogResult, VaultGeneratorDialogComponent, @@ -21,7 +24,9 @@ import { standalone: true, }) class MockCipherFormGenerator { - @Input() type: "password" | "username"; + @Input() type: "password" | "username" = "password"; + @Output() algorithmSelected: EventEmitter = new EventEmitter(); + @Input() uri: string = ""; @Output() valueGenerated = new EventEmitter(); } @@ -52,34 +57,87 @@ describe("VaultGeneratorDialogComponent", () => { fixture = TestBed.createComponent(VaultGeneratorDialogComponent); component = fixture.componentInstance; - }); - - it("should create", () => { fixture.detectChanges(); - expect(component).toBeTruthy(); }); - it("should use the appropriate text based on generator type", () => { - expect(component["title"]).toBe("passwordGenerator"); - expect(component["selectButtonText"]).toBe("useThisPassword"); - - dialogData.type = "username"; - - fixture = TestBed.createComponent(VaultGeneratorDialogComponent); - component = fixture.componentInstance; - - expect(component["title"]).toBe("usernameGenerator"); - expect(component["selectButtonText"]).toBe("useThisUsername"); + it("should show password generator title", () => { + const header = fixture.debugElement.query(By.css("popup-header")).componentInstance; + expect(header.pageTitle).toBe("passwordGenerator"); }); - it("should close the dialog with the generated value when the user selects it", () => { - component["generatedValue"] = "generated-value"; + it("should pass type to cipher form generator", () => { + const generator = fixture.debugElement.query( + By.css("vault-cipher-form-generator"), + ).componentInstance; + expect(generator.type).toBe("password"); + }); - fixture.nativeElement.querySelector("button[data-testid='select-button']").click(); + it("should enable select button when value is generated", () => { + component.onAlgorithmSelected({ useGeneratedValue: "Test" } as any); + component.onValueGenerated("test-password"); + fixture.detectChanges(); + + const button = fixture.debugElement.query( + By.css("[data-testid='select-button']"), + ).nativeElement; + expect(button.disabled).toBe(false); + }); + + it("should disable the button if no value has been generated", () => { + const generator = fixture.debugElement.query( + By.css("vault-cipher-form-generator"), + ).componentInstance; + + generator.algorithmSelected.emit({ useGeneratedValue: "Use Password" } as any); + fixture.detectChanges(); + + const button = fixture.debugElement.query( + By.css("[data-testid='select-button']"), + ).nativeElement; + expect(button.disabled).toBe(true); + }); + + it("should disable the button if no algorithm is selected", () => { + const generator = fixture.debugElement.query( + By.css("vault-cipher-form-generator"), + ).componentInstance; + + generator.valueGenerated.emit("test-password"); + fixture.detectChanges(); + + const button = fixture.debugElement.query( + By.css("[data-testid='select-button']"), + ).nativeElement; + expect(button.disabled).toBe(true); + }); + + it("should update button text when algorithm is selected", () => { + component.onAlgorithmSelected({ useGeneratedValue: "Use This Password" } as any); + fixture.detectChanges(); + + const button = fixture.debugElement.query( + By.css("[data-testid='select-button']"), + ).nativeElement; + expect(button.textContent.trim()).toBe("Use This Password"); + }); + + it("should close with generated value when selected", () => { + component.onAlgorithmSelected({ useGeneratedValue: "Test" } as any); + component.onValueGenerated("test-password"); + fixture.detectChanges(); + + fixture.debugElement.query(By.css("[data-testid='select-button']")).nativeElement.click(); expect(mockDialogRef.close).toHaveBeenCalledWith({ - action: "selected", - generatedValue: "generated-value", + action: GeneratorDialogAction.Selected, + generatedValue: "test-password", + }); + }); + + it("should close with canceled action when dismissed", () => { + fixture.debugElement.query(By.css("popup-header")).componentInstance.backAction(); + expect(mockDialogRef.close).toHaveBeenCalledWith({ + action: GeneratorDialogAction.Canceled, }); }); }); diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.ts index d050312211b..0eeb2e95a29 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.ts @@ -7,6 +7,8 @@ import { Component, Inject } from "@angular/core"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ButtonModule, DialogService } from "@bitwarden/components"; +import { AlgorithmInfo } from "@bitwarden/generator-core"; +import { I18nPipe } from "@bitwarden/ui-common"; import { CipherFormGeneratorComponent } from "@bitwarden/vault"; import { PopupFooterComponent } from "../../../../../platform/popup/layout/popup-footer.component"; @@ -15,6 +17,7 @@ import { PopupPageComponent } from "../../../../../platform/popup/layout/popup-p export interface GeneratorDialogParams { type: "password" | "username"; + uri?: string; } export interface GeneratorDialogResult { @@ -38,13 +41,12 @@ export enum GeneratorDialogAction { CommonModule, CipherFormGeneratorComponent, ButtonModule, + I18nPipe, ], }) export class VaultGeneratorDialogComponent { - protected title = this.i18nService.t(this.isPassword ? "passwordGenerator" : "usernameGenerator"); - protected selectButtonText = this.i18nService.t( - this.isPassword ? "useThisPassword" : "useThisUsername", - ); + protected selectButtonText: string | undefined; + protected titleKey = this.isPassword ? "passwordGenerator" : "usernameGenerator"; /** * Whether the dialog is generating a password/passphrase. If false, it is generating a username. @@ -60,11 +62,15 @@ export class VaultGeneratorDialogComponent { */ protected generatedValue: string = ""; + protected uri: string; + constructor( @Inject(DIALOG_DATA) protected params: GeneratorDialogParams, private dialogRef: DialogRef, private i18nService: I18nService, - ) {} + ) { + this.uri = params.uri; + } /** * Close the dialog without selecting a value. @@ -87,6 +93,16 @@ export class VaultGeneratorDialogComponent { this.generatedValue = value; } + onAlgorithmSelected = (selected?: AlgorithmInfo) => { + if (selected) { + this.selectButtonText = selected.useGeneratedValue; + } else { + // default to email + this.selectButtonText = this.i18nService.t("useThisEmail"); + } + this.generatedValue = undefined; + }; + /** * Opens the vault generator dialog in a full screen dialog. */ diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.html index 5f958433c6d..91feaa433a9 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.html @@ -20,7 +20,7 @@

{{ numberOfAppliedFilters$ | async }} diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.spec.ts index 38ec6056d19..cda055176e8 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.spec.ts @@ -59,7 +59,9 @@ describe("VaultHeaderV2Component", () => { providers: [ { provide: CipherService, - useValue: mock({ cipherViews$: new BehaviorSubject([]) }), + useValue: mock({ + cipherViews$: jest.fn().mockReturnValue(new BehaviorSubject([])), + }), }, { provide: VaultSettingsService, useValue: mock() }, { provide: FolderService, useValue: mock() }, @@ -75,7 +77,7 @@ describe("VaultHeaderV2Component", () => { { provide: LogService, useValue: mock() }, { provide: VaultPopupItemsService, - useValue: mock({ latestSearchText$: new BehaviorSubject("") }), + useValue: mock({ searchText$: new BehaviorSubject("") }), }, { provide: SyncService, @@ -152,7 +154,7 @@ describe("VaultHeaderV2Component", () => { it("defaults the initial state to true", (done) => { // The initial value of the `state$` variable above is undefined component["initialDisclosureVisibility$"].subscribe((initialVisibility) => { - expect(initialVisibility).toBeTrue(); + expect(initialVisibility).toBe(true); done(); }); diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.ts index 8ec04045e6c..bcea2e76190 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.ts @@ -6,9 +6,12 @@ import { combineLatest, map, take } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { DisclosureTriggerForDirective, IconButtonModule } from "@bitwarden/components"; +import { + DisclosureComponent, + DisclosureTriggerForDirective, + IconButtonModule, +} from "@bitwarden/components"; -import { DisclosureComponent } from "../../../../../../../../libs/components/src/disclosure/disclosure.component"; import { runInsideAngular } from "../../../../../platform/browser/run-inside-angular.operator"; import { VaultPopupListFiltersService } from "../../../../../vault/popup/services/vault-popup-list-filters.service"; import { VaultListFiltersComponent } from "../vault-list-filters/vault-list-filters.component"; diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-filters/vault-list-filters.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-list-filters/vault-list-filters.component.html index 56f35c41f6d..c61562f9f90 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-filters/vault-list-filters.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-filters/vault-list-filters.component.html @@ -2,8 +2,9 @@
- + - + - + { + return { + organizations, + collections, + folders, + }; + }), + shareReplay({ bufferSize: 1, refCount: false }), + ); + constructor(private vaultPopupListFiltersService: VaultPopupListFiltersService) {} } 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 b5d92a386b3..cbce4bf2961 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html @@ -1,88 +1,159 @@ - -
- -

- {{ title }} -

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

+ {{ title }} +

+ + + + {{ ciphers().length }} + + + + + +
+
+ + +
{{ description }}
+
+ + - - - - - - - - - - - - - - - + + +

+ {{ group.subHeaderKey | i18n }} +

+
+ + + + + + + + + + + + + + + + + +
- +
diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts index 5bcdaf56bbd..9d70c0ba236 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { ScrollingModule } from "@angular/cdk/scrolling"; +import { CdkVirtualScrollViewport, ScrollingModule } from "@angular/cdk/scrolling"; import { CommonModule } from "@angular/common"; import { AfterViewInit, @@ -10,21 +10,32 @@ import { inject, Input, Output, + Signal, signal, + ViewChild, + computed, + OnInit, + ChangeDetectionStrategy, + input, } from "@angular/core"; -import { Router, RouterLink } from "@angular/router"; -import { map } from "rxjs"; +import { Router } from "@angular/router"; +import { firstValueFrom, Observable, map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { BadgeModule, ButtonModule, CompactModeService, + DisclosureComponent, + DisclosureTriggerForDirective, DialogService, IconButtonModule, ItemModule, @@ -41,6 +52,7 @@ import { import { BrowserApi } from "../../../../../platform/browser/browser-api"; import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils"; import { VaultPopupAutofillService } from "../../../services/vault-popup-autofill.service"; +import { VaultPopupSectionService } from "../../../services/vault-popup-section.service"; import { PopupCipherView } from "../../../views/popup-cipher.view"; import { ItemCopyActionsComponent } from "../item-copy-action/item-copy-actions.component"; import { ItemMoreOptionsComponent } from "../item-more-options/item-more-options.component"; @@ -56,19 +68,30 @@ import { ItemMoreOptionsComponent } from "../item-more-options/item-more-options TypographyModule, JslibModule, SectionHeaderComponent, - RouterLink, ItemCopyActionsComponent, ItemMoreOptionsComponent, OrgIconDirective, ScrollingModule, + DisclosureComponent, + DisclosureTriggerForDirective, DecryptionFailureDialogComponent, ], selector: "app-vault-list-items-container", templateUrl: "vault-list-items-container.component.html", standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, }) -export class VaultListItemsContainerComponent implements AfterViewInit { +export class VaultListItemsContainerComponent implements OnInit, AfterViewInit { private compactModeService = inject(CompactModeService); + private vaultPopupSectionService = inject(VaultPopupSectionService); + + @ViewChild(CdkVirtualScrollViewport, { static: false }) viewPort: CdkVirtualScrollViewport; + @ViewChild(DisclosureComponent) disclosure: DisclosureComponent; + + /** + * Indicates whether the section should be open or closed if collapsibleKey is provided + */ + protected sectionOpenState: Signal | undefined; /** * The class used to set the height of a bit item's inner content. @@ -94,11 +117,51 @@ export class VaultListItemsContainerComponent implements AfterViewInit { */ private viewCipherTimeout: number | null; + ciphers = input([]); + /** - * The list of ciphers to display. + * If true, we will group ciphers by type (Login, Card, Identity) + * within subheadings in a single container, converted to a WritableSignal. */ - @Input() - ciphers: PopupCipherView[] = []; + groupByType = input(false); + + /** + * Computed signal for a grouped list of ciphers with an optional header + */ + cipherGroups$ = computed< + { + subHeaderKey?: string | null; + ciphers: PopupCipherView[]; + }[] + >(() => { + const groups: { [key: string]: CipherView[] } = {}; + + this.ciphers().forEach((cipher) => { + let groupKey; + + if (this.groupByType()) { + switch (cipher.type) { + case CipherType.Card: + groupKey = "cards"; + break; + case CipherType.Identity: + groupKey = "identities"; + break; + } + } + + if (!groups[groupKey]) { + groups[groupKey] = []; + } + + groups[groupKey].push(cipher); + }); + + return Object.keys(groups).map((key) => ({ + subHeaderKey: this.groupByType ? key : "", + ciphers: groups[key], + })); + }); /** * Title for the vault list item section. @@ -106,6 +169,15 @@ export class VaultListItemsContainerComponent implements AfterViewInit { @Input() title: string; + /** + * Optionally allow the items to be collapsed. + * + * The key must be added to the state definition in `vault-popup-section.service.ts` since the + * collapsed state is stored locally. + */ + @Input() + collapsibleKey: "favorites" | "allItems" | undefined; + /** * Optional description for the vault list item section. Will be shown below the title even when * no ciphers are available. @@ -125,12 +197,41 @@ export class VaultListItemsContainerComponent implements AfterViewInit { @Output() onRefresh = new EventEmitter(); + /** + * Flag indicating that the current tab location is blocked + */ + currentURIIsBlocked$: Observable = + this.vaultPopupAutofillService.currentTabIsOnBlocklist$; + + /** + * Resolved i18n key to use for suggested cipher items + */ + cipherItemTitleKey = this.currentURIIsBlocked$.pipe( + map((uriIsBlocked) => + this.primaryActionAutofill && !uriIsBlocked ? "autofillTitle" : "viewItemTitle", + ), + ); + /** * Option to show the autofill button for each item. */ @Input({ transform: booleanAttribute }) showAutofillButton: boolean; + /** + * Flag indicating whether the suggested cipher item autofill button should be shown or not + */ + hideAutofillButton$ = this.currentURIIsBlocked$.pipe( + map((uriIsBlocked) => !this.showAutofillButton || uriIsBlocked || this.primaryActionAutofill), + ); + + /** + * Flag indicating whether the cipher item autofill options should be shown or not + */ + hideAutofillOptions$: Observable = this.currentURIIsBlocked$.pipe( + map((uriIsBlocked) => uriIsBlocked || this.showAutofillButton), + ); + /** * Option to perform autofill operation as the primary action for autofill suggestions. */ @@ -144,6 +245,12 @@ export class VaultListItemsContainerComponent implements AfterViewInit { @Input({ transform: booleanAttribute }) disableSectionMargin: boolean = false; + /** + * Remove the description margin + */ + @Input({ transform: booleanAttribute }) + disableDescriptionMargin: boolean = false; + /** * The tooltip text for the organization icon for ciphers that belong to an organization. * @param cipher @@ -166,8 +273,19 @@ export class VaultListItemsContainerComponent implements AfterViewInit { private router: Router, private platformUtilsService: PlatformUtilsService, private dialogService: DialogService, + private accountService: AccountService, ) {} + ngOnInit(): void { + if (!this.collapsibleKey) { + return; + } + + this.sectionOpenState = this.vaultPopupSectionService.getOpenDisplayStateForSection( + this.collapsibleKey, + ); + } + async ngAfterViewInit() { const autofillShortcut = await this.platformUtilsService.getAutofillKeyboardShortcut(); @@ -180,6 +298,14 @@ export class VaultListItemsContainerComponent implements AfterViewInit { } } + async primaryActionOnSelect(cipher: CipherView) { + const isBlocked = await firstValueFrom(this.currentURIIsBlocked$); + + return this.primaryActionAutofill && !isBlocked + ? this.doAutofill(cipher) + : this.onViewCipher(cipher); + } + /** * Launches the login cipher in a new browser tab. */ @@ -194,7 +320,8 @@ export class VaultListItemsContainerComponent implements AfterViewInit { this.viewCipherTimeout = null; } - await this.cipherService.updateLastLaunchedDate(cipher.id); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + await this.cipherService.updateLastLaunchedDate(cipher.id, activeUserId); await BrowserApi.createNewTab(cipher.login.launchUri); @@ -239,4 +366,30 @@ export class VaultListItemsContainerComponent implements AfterViewInit { cipher.canLaunch ? 200 : 0, ); } + + /** + * Update section open/close state based on user action + */ + async toggleSectionOpen() { + if (!this.collapsibleKey) { + return; + } + + await this.vaultPopupSectionService.updateSectionOpenStoredState( + this.collapsibleKey, + this.disclosure.open, + ); + } + + /** + * Force virtual scroll to update its viewport size to avoid display bugs + * + * Angular CDK scroll has a bug when used with conditional rendering: + * https://github.com/angular/components/issues/24362 + */ + protected rerenderViewport() { + setTimeout(() => { + this.viewPort.checkViewportSize(); + }); + } } diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-page.service.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-page.service.ts new file mode 100644 index 00000000000..a7c52ed4c51 --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-page.service.ts @@ -0,0 +1,35 @@ +import { inject, Injectable } from "@angular/core"; +import { map, Observable } from "rxjs"; + +import { + BANNERS_DISMISSED_DISK, + StateProvider, + UserKeyDefinition, +} from "@bitwarden/common/platform/state"; +import { UserId } from "@bitwarden/common/types/guid"; + +export const NEW_CUSTOMIZATION_OPTIONS_CALLOUT_DISMISSED_KEY = new UserKeyDefinition( + BANNERS_DISMISSED_DISK, + "newCustomizationOptionsCalloutDismissed", + { + deserializer: (calloutDismissed) => calloutDismissed, + clearOn: [], // Do not clear dismissed callouts + }, +); + +@Injectable() +export class VaultPageService { + private stateProvider = inject(StateProvider); + + isCalloutDismissed(userId: UserId): Observable { + return this.stateProvider + .getUser(userId, NEW_CUSTOMIZATION_OPTIONS_CALLOUT_DISMISSED_KEY) + .state$.pipe(map((dismissed) => !!dismissed)); + } + + async dismissCallout(userId: UserId): Promise { + await this.stateProvider + .getUser(userId, NEW_CUSTOMIZATION_OPTIONS_CALLOUT_DISMISSED_KEY) + .update(() => true); + } +} diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.spec.ts index 9ac17b49386..838ce2e9426 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.spec.ts @@ -1,13 +1,15 @@ import { ComponentFixture, fakeAsync, TestBed, tick } from "@angular/core/testing"; import { ActivatedRoute } from "@angular/router"; import { mock } from "jest-mock-extended"; -import { BehaviorSubject, Subject } from "rxjs"; +import { Subject } from "rxjs"; import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; -import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -19,6 +21,7 @@ import { PasswordHistoryV2Component } from "./vault-password-history-v2.componen describe("PasswordHistoryV2Component", () => { let fixture: ComponentFixture; const params$ = new Subject(); + const mockUserId = "acct-1" as UserId; const mockCipherView = { id: "111-222-333", @@ -45,9 +48,7 @@ describe("PasswordHistoryV2Component", () => { { provide: CipherService, useValue: mock({ get: getCipher }) }, { provide: AccountService, - useValue: mock({ - activeAccount$: new BehaviorSubject({ id: "acct-1" } as Account), - }), + useValue: mockAccountServiceWith(mockUserId), }, { provide: PopupRouterCacheService, useValue: { back } }, { provide: ActivatedRoute, useValue: { queryParams: params$ } }, @@ -64,7 +65,7 @@ describe("PasswordHistoryV2Component", () => { tick(100); - expect(getCipher).toHaveBeenCalledWith(mockCipherView.id); + expect(getCipher).toHaveBeenCalledWith(mockCipherView.id, mockUserId); })); it("navigates back when a cipherId is not in the params", () => { diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts index c8f590ced57..5d315775b10 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts @@ -11,8 +11,8 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { PasswordHistoryViewComponent } from "@bitwarden/vault"; -import { PasswordHistoryViewComponent } from "../../../../../../../../libs/vault/src/components/password-history-view/password-history-view.component"; import { PopOutComponent } from "../../../../../platform/popup/components/pop-out.component"; import { PopupHeaderComponent } from "../../../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../../../platform/popup/layout/popup-page.component"; @@ -58,8 +58,6 @@ export class PasswordHistoryV2Component implements OnInit { /** Load the cipher based on the given Id */ private async loadCipher(cipherId: string) { - const cipher = await this.cipherService.get(cipherId); - const activeAccount = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a: { id: string | undefined }) => a)), ); @@ -69,6 +67,8 @@ export class PasswordHistoryV2Component implements OnInit { } const activeUserId = activeAccount.id as UserId; + + const cipher = await this.cipherService.get(cipherId, activeUserId); this.cipher = await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts index d9b310da231..32f5611f436 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts @@ -1,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 { takeUntilDestroyed } from "@angular/core/rxjs-interop"; @@ -20,7 +18,7 @@ const SearchTextDebounceInterval = 200; templateUrl: "vault-v2-search.component.html", }) export class VaultV2SearchComponent { - searchText: string; + searchText: string = ""; private searchText$ = new Subject(); @@ -30,11 +28,11 @@ export class VaultV2SearchComponent { } onSearchTextChanged() { - this.searchText$.next(this.searchText); + this.vaultPopupItemsService.applyFilter(this.searchText); } subscribeToLatestSearchText(): Subscription { - return this.vaultPopupItemsService.latestSearchText$ + return this.vaultPopupItemsService.searchText$ .pipe( takeUntilDestroyed(), filter((data) => !!data), diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-ui-onboarding/vault-ui-onboarding.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-ui-onboarding/vault-ui-onboarding.component.ts deleted file mode 100644 index 20b39c5a88d..00000000000 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-ui-onboarding/vault-ui-onboarding.component.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { Component } from "@angular/core"; - -import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { - ButtonModule, - DialogModule, - DialogService, - IconModule, - svgIcon, -} from "@bitwarden/components"; - -const announcementIcon = svgIcon` - - - - - - - - - - - - - - - - -`; - -@Component({ - standalone: true, - selector: "app-vault-ui-onboarding", - template: ` - -
- -
- - {{ "bitwardenNewLook" | i18n }} - - - {{ "bitwardenNewLookDesc" | i18n }} - - - - - - -
- `, - imports: [CommonModule, DialogModule, ButtonModule, JslibModule, IconModule], -}) -export class VaultUiOnboardingComponent { - icon = announcementIcon; - - static open(dialogService: DialogService) { - return dialogService.open(VaultUiOnboardingComponent); - } - - navigateToLink = async () => { - window.open( - "https://bitwarden.com/blog/bringing-intuitive-workflows-and-visual-updates-to-the-bitwarden-browser/", - "_blank", - ); - }; -} diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html index 4798ddf4dfb..2a50eb43960 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html @@ -22,11 +22,16 @@
+ + - + + @@ -64,15 +69,18 @@
+ diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts index a3d8f3ffe31..92276ef633f 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts @@ -1,12 +1,23 @@ -import { ScrollingModule } from "@angular/cdk/scrolling"; +import { CdkVirtualScrollableElement, ScrollingModule } from "@angular/cdk/scrolling"; import { CommonModule } from "@angular/common"; -import { Component, DestroyRef, OnDestroy, OnInit } from "@angular/core"; +import { AfterViewInit, Component, DestroyRef, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { RouterLink } from "@angular/router"; -import { combineLatest, Observable, shareReplay, switchMap } from "rxjs"; -import { filter, map, take } from "rxjs/operators"; +import { + combineLatest, + filter, + map, + firstValueFrom, + Observable, + shareReplay, + switchMap, + take, + startWith, +} from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -17,15 +28,20 @@ import { CurrentAccountComponent } from "../../../../auth/popup/account-switchin import { PopOutComponent } from "../../../../platform/popup/components/pop-out.component"; import { PopupHeaderComponent } from "../../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page.component"; +import { VaultPopupCopyButtonsService } from "../../services/vault-popup-copy-buttons.service"; import { VaultPopupItemsService } from "../../services/vault-popup-items.service"; import { VaultPopupListFiltersService } from "../../services/vault-popup-list-filters.service"; -import { VaultUiOnboardingService } from "../../services/vault-ui-onboarding.service"; +import { VaultPopupScrollPositionService } from "../../services/vault-popup-scroll-position.service"; +import { AtRiskPasswordCalloutComponent } from "../at-risk-callout/at-risk-password-callout.component"; +import { BlockedInjectionBanner } from "./blocked-injection-banner/blocked-injection-banner.component"; import { NewItemDropdownV2Component, NewItemInitialValues, } from "./new-item-dropdown/new-item-dropdown-v2.component"; +import { NewSettingsCalloutComponent } from "./new-settings-callout/new-settings-callout.component"; import { VaultHeaderV2Component } from "./vault-header/vault-header-v2.component"; +import { VaultPageService } from "./vault-page.service"; import { AutofillVaultListItemsComponent, VaultListItemsContainerComponent } from "."; @@ -40,6 +56,7 @@ enum VaultState { templateUrl: "vault-v2.component.html", standalone: true, imports: [ + BlockedInjectionBanner, PopupPageComponent, PopupHeaderComponent, PopOutComponent, @@ -50,20 +67,33 @@ enum VaultState { AutofillVaultListItemsComponent, VaultListItemsContainerComponent, ButtonModule, - RouterLink, NewItemDropdownV2Component, ScrollingModule, VaultHeaderV2Component, - DecryptionFailureDialogComponent, + AtRiskPasswordCalloutComponent, + NewSettingsCalloutComponent, ], - providers: [VaultUiOnboardingService], + providers: [VaultPageService], }) -export class VaultV2Component implements OnInit, OnDestroy { +export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { + @ViewChild(CdkVirtualScrollableElement) virtualScrollElement?: CdkVirtualScrollableElement; + cipherType = CipherType; protected favoriteCiphers$ = this.vaultPopupItemsService.favoriteCiphers$; protected remainingCiphers$ = this.vaultPopupItemsService.remainingCiphers$; - protected loading$ = this.vaultPopupItemsService.loading$; + protected allFilters$ = this.vaultPopupListFiltersService.allFilters$; + + protected loading$ = combineLatest([ + this.vaultPopupItemsService.loading$, + this.allFilters$, + // Added as a dependency to avoid flashing the copyActions on slower devices + this.vaultCopyButtonsService.showQuickCopyActions$, + ]).pipe( + map(([itemsLoading, filters]) => itemsLoading || !filters), + shareReplay({ bufferSize: 1, refCount: true }), + startWith(true), + ); protected newItemItemValues$: Observable = this.vaultPopupListFiltersService.filters$.pipe( @@ -87,14 +117,17 @@ export class VaultV2Component implements OnInit, OnDestroy { protected noResultsIcon = Icons.NoResults; protected VaultStateEnum = VaultState; + protected showNewCustomizationSettingsCallout = false; constructor( private vaultPopupItemsService: VaultPopupItemsService, private vaultPopupListFiltersService: VaultPopupListFiltersService, - private vaultUiOnboardingService: VaultUiOnboardingService, + private vaultScrollPositionService: VaultPopupScrollPositionService, + private accountService: AccountService, private destroyRef: DestroyRef, private cipherService: CipherService, private dialogService: DialogService, + private vaultCopyButtonsService: VaultPopupCopyButtonsService, ) { combineLatest([ this.vaultPopupItemsService.emptyVault$, @@ -120,10 +153,22 @@ export class VaultV2Component implements OnInit, OnDestroy { }); } - async ngOnInit() { - await this.vaultUiOnboardingService.showOnboardingDialog(); + ngAfterViewInit(): void { + if (this.virtualScrollElement) { + // The filters component can cause the size of the virtual scroll element to change, + // which can cause the scroll position to be land in the wrong spot. To fix this, + // wait until all filters are populated before restoring the scroll position. + this.allFilters$.pipe(take(1), takeUntilDestroyed(this.destroyRef)).subscribe(() => { + this.vaultScrollPositionService.start(this.virtualScrollElement!); + }); + } + } - this.cipherService.failedToDecryptCiphers$ + async ngOnInit() { + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + + this.cipherService + .failedToDecryptCiphers$(activeUserId) .pipe( map((ciphers) => ciphers.filter((c) => !c.isDeleted)), filter((ciphers) => ciphers.length > 0), @@ -137,5 +182,9 @@ export class VaultV2Component implements OnInit, OnDestroy { }); } - ngOnDestroy(): void {} + ngOnDestroy() { + this.vaultScrollPositionService.stop(); + } + + protected readonly FeatureFlag = FeatureFlag; } diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts index 7ee15aa833b..17464c025af 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts @@ -21,12 +21,15 @@ import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/sp import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; +import { DialogService, ToastService } from "@bitwarden/components"; import { CopyCipherFieldService } from "@bitwarden/vault"; import { BrowserApi } from "../../../../../platform/browser/browser-api"; import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils"; import { PopupRouterCacheService } from "../../../../../platform/popup/view-cache/popup-router-cache.service"; +import { VaultPopupScrollPositionService } from "../../../services/vault-popup-scroll-position.service"; import { VaultPopupAutofillService } from "./../../../services/vault-popup-autofill.service"; import { ViewV2Component } from "./view-v2.component"; @@ -44,6 +47,10 @@ describe("ViewV2Component", () => { const collect = jest.fn().mockResolvedValue(null); const doAutofill = jest.fn().mockResolvedValue(true); const copy = jest.fn().mockResolvedValue(true); + const back = jest.fn().mockResolvedValue(null); + const openSimpleDialog = jest.fn().mockResolvedValue(true); + const stop = jest.fn(); + const showToast = jest.fn(); const mockCipher = { id: "122-333-444", @@ -54,7 +61,7 @@ describe("ViewV2Component", () => { password: "test-password", totp: "123", }, - }; + } as unknown as CipherView; const mockVaultPopupAutofillService = { doAutofill, @@ -68,13 +75,21 @@ describe("ViewV2Component", () => { const mockCipherService = { get: jest.fn().mockResolvedValue({ decrypt: jest.fn().mockResolvedValue(mockCipher) }), getKeyForCipherKeyDecryption: jest.fn().mockResolvedValue({}), + deleteWithServer: jest.fn().mockResolvedValue(undefined), + softDeleteWithServer: jest.fn().mockResolvedValue(undefined), }; beforeEach(async () => { + mockCipherService.deleteWithServer.mockClear(); + mockCipherService.softDeleteWithServer.mockClear(); mockNavigate.mockClear(); collect.mockClear(); doAutofill.mockClear(); copy.mockClear(); + stop.mockClear(); + openSimpleDialog.mockClear(); + back.mockClear(); + showToast.mockClear(); await TestBed.configureTestingModule({ imports: [ViewV2Component], @@ -84,9 +99,12 @@ describe("ViewV2Component", () => { { provide: LogService, useValue: mock() }, { provide: PlatformUtilsService, useValue: mock() }, { provide: ConfigService, useValue: mock() }, - { provide: PopupRouterCacheService, useValue: mock() }, + { provide: PopupRouterCacheService, useValue: mock({ back }) }, { provide: ActivatedRoute, useValue: { queryParams: params$ } }, { provide: EventCollectionService, useValue: { collect } }, + { provide: VaultPopupScrollPositionService, useValue: { stop } }, + { provide: VaultPopupAutofillService, useValue: mockVaultPopupAutofillService }, + { provide: ToastService, useValue: { showToast } }, { provide: I18nService, useValue: { @@ -98,7 +116,6 @@ describe("ViewV2Component", () => { }, }, }, - { provide: VaultPopupAutofillService, useValue: mockVaultPopupAutofillService }, { provide: AccountService, useValue: accountService, @@ -114,7 +131,13 @@ describe("ViewV2Component", () => { useValue: mockCopyCipherFieldService, }, ], - }).compileComponents(); + }) + .overrideProvider(DialogService, { + useValue: { + openSimpleDialog, + }, + }) + .compileComponents(); fixture = TestBed.createComponent(ViewV2Component); component = fixture.componentInstance; @@ -127,7 +150,7 @@ describe("ViewV2Component", () => { flush(); // Resolve all promises - expect(mockCipherService.get).toHaveBeenCalledWith("122-333-444"); + expect(mockCipherService.get).toHaveBeenCalledWith("122-333-444", mockUserId); expect(component.cipher).toEqual(mockCipher); })); @@ -179,7 +202,7 @@ describe("ViewV2Component", () => { flush(); // Resolve all promises - expect(doAutofill).toHaveBeenCalledOnce(); + expect(doAutofill).toHaveBeenCalledTimes(1); })); it('invokes `copy` when action="copy-username"', fakeAsync(() => { @@ -187,7 +210,7 @@ describe("ViewV2Component", () => { flush(); // Resolve all promises - expect(copy).toHaveBeenCalledOnce(); + expect(copy).toHaveBeenCalledTimes(1); })); it('invokes `copy` when action="copy-password"', fakeAsync(() => { @@ -195,7 +218,7 @@ describe("ViewV2Component", () => { flush(); // Resolve all promises - expect(copy).toHaveBeenCalledOnce(); + expect(copy).toHaveBeenCalledTimes(1); })); it('invokes `copy` when action="copy-totp"', fakeAsync(() => { @@ -203,7 +226,7 @@ describe("ViewV2Component", () => { flush(); // Resolve all promises - expect(copy).toHaveBeenCalledOnce(); + expect(copy).toHaveBeenCalledTimes(1); })); it("closes the popout after a load action", fakeAsync(() => { @@ -218,9 +241,135 @@ describe("ViewV2Component", () => { flush(); // Resolve all promises - expect(doAutofill).toHaveBeenCalledOnce(); + expect(doAutofill).toHaveBeenCalledTimes(1); expect(focusSpy).toHaveBeenCalledWith(99); - expect(closeSpy).toHaveBeenCalledOnce(); + expect(closeSpy).toHaveBeenCalledTimes(1); })); }); + + describe("delete", () => { + beforeEach(() => { + component.cipher = mockCipher; + }); + + it("opens confirmation modal", async () => { + await component.delete(); + + expect(openSimpleDialog).toHaveBeenCalledTimes(1); + }); + + it("navigates back", async () => { + await component.delete(); + + expect(back).toHaveBeenCalledTimes(1); + }); + + it("stops scroll position service", async () => { + await component.delete(); + + expect(stop).toHaveBeenCalledTimes(1); + expect(stop).toHaveBeenCalledWith(true); + }); + + describe("deny confirmation", () => { + beforeEach(() => { + openSimpleDialog.mockResolvedValue(false); + }); + + it("does not delete the cipher", async () => { + await component.delete(); + + expect(mockCipherService.deleteWithServer).not.toHaveBeenCalled(); + expect(mockCipherService.softDeleteWithServer).not.toHaveBeenCalled(); + }); + + it("does not interact with side effects", () => { + expect(back).not.toHaveBeenCalled(); + expect(stop).not.toHaveBeenCalled(); + expect(showToast).not.toHaveBeenCalled(); + }); + }); + + describe("accept confirmation", () => { + beforeEach(() => { + openSimpleDialog.mockResolvedValue(true); + }); + + describe("soft delete", () => { + beforeEach(() => { + (mockCipher as any).isDeleted = null; + }); + + it("opens confirmation dialog", async () => { + await component.delete(); + + expect(openSimpleDialog).toHaveBeenCalledTimes(1); + expect(openSimpleDialog).toHaveBeenCalledWith({ + content: { + key: "deleteItemConfirmation", + }, + title: { + key: "deleteItem", + }, + type: "warning", + }); + }); + + it("calls soft delete", async () => { + await component.delete(); + + expect(mockCipherService.softDeleteWithServer).toHaveBeenCalled(); + expect(mockCipherService.deleteWithServer).not.toHaveBeenCalled(); + }); + + it("shows toast", async () => { + await component.delete(); + + expect(showToast).toHaveBeenCalledWith({ + variant: "success", + title: null, + message: "deletedItem", + }); + }); + }); + + describe("hard delete", () => { + beforeEach(() => { + (mockCipher as any).isDeleted = true; + }); + + it("opens confirmation dialog", async () => { + await component.delete(); + + expect(openSimpleDialog).toHaveBeenCalledTimes(1); + expect(openSimpleDialog).toHaveBeenCalledWith({ + content: { + key: "permanentlyDeleteItemConfirmation", + }, + title: { + key: "deleteItem", + }, + type: "warning", + }); + }); + + it("calls soft delete", async () => { + await component.delete(); + + expect(mockCipherService.deleteWithServer).toHaveBeenCalled(); + expect(mockCipherService.softDeleteWithServer).not.toHaveBeenCalled(); + }); + + it("shows toast", async () => { + await component.delete(); + + expect(showToast).toHaveBeenCalledWith({ + variant: "success", + title: null, + message: "permanentlyDeletedItem", + }); + }); + }); + }); + }); }); 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 55b59c087c5..b9eae380ca0 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 @@ -5,13 +5,14 @@ import { Component } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormsModule } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; -import { firstValueFrom, map, Observable, switchMap } from "rxjs"; +import { firstValueFrom, Observable, switchMap } from "rxjs"; import { CollectionView } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { AUTOFILL_ID, COPY_PASSWORD_ID, @@ -22,7 +23,9 @@ import { import { EventType } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -34,17 +37,24 @@ import { IconButtonModule, SearchModule, ToastService, + CalloutModule, } from "@bitwarden/components"; -import { CopyCipherFieldService } from "@bitwarden/vault"; +import { + ChangeLoginPasswordService, + CipherViewComponent, + CopyCipherFieldService, + DefaultChangeLoginPasswordService, + DefaultTaskService, + TaskService, +} from "@bitwarden/vault"; -import { PremiumUpgradePromptService } from "../../../../../../../../libs/common/src/vault/abstractions/premium-upgrade-prompt.service"; -import { CipherViewComponent } from "../../../../../../../../libs/vault/src/cipher-view"; import { BrowserApi } from "../../../../../platform/browser/browser-api"; import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils"; import { PopOutComponent } from "../../../../../platform/popup/components/pop-out.component"; import { PopupRouterCacheService } from "../../../../../platform/popup/view-cache/popup-router-cache.service"; import { BrowserPremiumUpgradePromptService } from "../../../services/browser-premium-upgrade-prompt.service"; import { BrowserViewPasswordHistoryService } from "../../../services/browser-view-password-history.service"; +import { VaultPopupScrollPositionService } from "../../../services/vault-popup-scroll-position.service"; import { closeViewVaultItemPopout, VaultPopoutType } from "../../../utils/vault-popout-window"; import { PopupFooterComponent } from "./../../../../../platform/popup/layout/popup-footer.component"; @@ -80,13 +90,18 @@ type LoadAction = CipherViewComponent, AsyncActionsModule, PopOutComponent, + CalloutModule, ], providers: [ { provide: ViewPasswordHistoryService, useClass: BrowserViewPasswordHistoryService }, { provide: PremiumUpgradePromptService, useClass: BrowserPremiumUpgradePromptService }, + { provide: TaskService, useClass: DefaultTaskService }, + { provide: ChangeLoginPasswordService, useClass: DefaultChangeLoginPasswordService }, ], }) export class ViewV2Component { + private activeUserId: UserId; + headerText: string; cipher: CipherView; organization$: Observable; @@ -109,6 +124,7 @@ export class ViewV2Component { private popupRouterCacheService: PopupRouterCacheService, protected cipherAuthorizationService: CipherAuthorizationService, private copyCipherFieldService: CopyCipherFieldService, + private popupScrollPositionService: VaultPopupScrollPositionService, ) { this.subscribeToParams(); } @@ -116,14 +132,20 @@ export class ViewV2Component { subscribeToParams(): void { this.route.queryParams .pipe( - switchMap(async (params): Promise => { + switchMap(async (params) => { this.loadAction = params.action; this.senderTabId = params.senderTabId ? parseInt(params.senderTabId, 10) : undefined; - return await this.getCipherData(params.cipherId); + + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(getUserId), + ); + const cipher = await this.getCipherData(params.cipherId, activeUserId); + return { activeUserId, cipher }; }), - switchMap(async (cipher) => { + switchMap(async ({ activeUserId, cipher }) => { this.cipher = cipher; this.headerText = this.setHeader(cipher.type); + this.activeUserId = activeUserId; if (this.loadAction) { await this._handleLoadAction(this.loadAction, this.senderTabId); @@ -158,13 +180,10 @@ export class ViewV2Component { } } - async getCipherData(id: string) { - const cipher = await this.cipherService.get(id); - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + async getCipherData(id: string, userId: UserId) { + const cipher = await this.cipherService.get(id, userId); return await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), + await this.cipherService.getKeyForCipherKeyDecryption(cipher, userId), ); } @@ -198,6 +217,7 @@ export class ViewV2Component { return false; } + this.popupScrollPositionService.stop(true); await this.popupRouterCacheService.back(); this.toastService.showToast({ @@ -211,7 +231,7 @@ export class ViewV2Component { restore = async (): Promise => { try { - await this.cipherService.restoreWithServer(this.cipher.id); + await this.cipherService.restoreWithServer(this.cipher.id, this.activeUserId); } catch (e) { this.logService.error(e); } @@ -226,12 +246,16 @@ export class ViewV2Component { protected deleteCipher() { return this.cipher.isDeleted - ? this.cipherService.deleteWithServer(this.cipher.id) - : this.cipherService.softDeleteWithServer(this.cipher.id); + ? this.cipherService.deleteWithServer(this.cipher.id, this.activeUserId) + : this.cipherService.softDeleteWithServer(this.cipher.id, this.activeUserId); } protected showFooter(): boolean { - return this.cipher && (!this.cipher.isDeleted || (this.cipher.isDeleted && this.cipher.edit)); + return ( + this.cipher && + (!this.cipher.isDeleted || + (this.cipher.isDeleted && this.cipher.edit && this.cipher.viewPassword)) + ); } /** diff --git a/apps/browser/src/vault/popup/services/browser-cipher-form-generation.service.ts b/apps/browser/src/vault/popup/services/browser-cipher-form-generation.service.ts index 156f8492275..ecba9aa1413 100644 --- a/apps/browser/src/vault/popup/services/browser-cipher-form-generation.service.ts +++ b/apps/browser/src/vault/popup/services/browser-cipher-form-generation.service.ts @@ -28,9 +28,9 @@ export class BrowserCipherFormGenerationService implements CipherFormGenerationS return result.generatedValue; } - async generateUsername(): Promise { + async generateUsername(uri: string): Promise { const dialogRef = VaultGeneratorDialogComponent.open(this.dialogService, this.overlay, { - data: { type: "username" }, + data: { type: "username", uri: uri }, }); const result = await firstValueFrom(dialogRef.closed); diff --git a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts index 25a7d7594ae..415aeb31081 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts @@ -4,6 +4,7 @@ import { mock } from "jest-mock-extended"; import { BehaviorSubject, of } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -20,12 +21,12 @@ import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; import { ToastService } from "@bitwarden/components"; import { PasswordRepromptService } from "@bitwarden/vault"; -import { InlineMenuFieldQualificationService } from "../../../../../browser/src/autofill/services/inline-menu-field-qualification.service"; import { AutoFillOptions, AutofillService, PageDetail, } from "../../../autofill/services/abstractions/autofill.service"; +import { InlineMenuFieldQualificationService } from "../../../autofill/services/inline-menu-field-qualification.service"; import { BrowserApi } from "../../../platform/browser/browser-api"; import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils"; @@ -42,6 +43,7 @@ describe("VaultPopupAutofillService", () => { // Create mocks for VaultPopupAutofillService const mockAutofillService = mock(); + const mockDomainSettingsService = mock(); const mockI18nService = mock(); const mockToastService = mock(); const mockPlatformUtilsService = mock(); @@ -65,10 +67,12 @@ describe("VaultPopupAutofillService", () => { .mockReturnValue(true); mockAutofillService.collectPageDetailsFromTab$.mockReturnValue(new BehaviorSubject([])); + mockDomainSettingsService.blockedInteractionsUris$ = new BehaviorSubject({}); testBed = TestBed.configureTestingModule({ providers: [ { provide: AutofillService, useValue: mockAutofillService }, + { provide: DomainSettingsService, useValue: mockDomainSettingsService }, { provide: I18nService, useValue: mockI18nService }, { provide: ToastService, useValue: mockToastService }, { provide: PlatformUtilsService, useValue: mockPlatformUtilsService }, diff --git a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts index e4863d5ee43..41d76078fdb 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts @@ -15,10 +15,13 @@ import { } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; +import { isUrlInList } from "@bitwarden/common/autofill/utils"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherRepromptType, CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -26,11 +29,11 @@ import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view import { ToastService } from "@bitwarden/components"; import { PasswordRepromptService } from "@bitwarden/vault"; -import { InlineMenuFieldQualificationService } from "../../../../../browser/src/autofill/services/inline-menu-field-qualification.service"; import { AutofillService, PageDetail, } from "../../../autofill/services/abstractions/autofill.service"; +import { InlineMenuFieldQualificationService } from "../../../autofill/services/inline-menu-field-qualification.service"; import { BrowserApi } from "../../../platform/browser/browser-api"; import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils"; import { closeViewVaultItemPopout, VaultPopoutType } from "../utils/vault-popout-window"; @@ -65,6 +68,76 @@ export class VaultPopupAutofillService { shareReplay({ refCount: false, bufferSize: 1 }), ); + currentTabIsOnBlocklist$: Observable = combineLatest([ + this.domainSettingsService.blockedInteractionsUris$, + this.currentAutofillTab$, + ]).pipe( + map(([blockedInteractionsUrls, currentTab]) => { + if (blockedInteractionsUrls && currentTab) { + return isUrlInList(currentTab?.url, blockedInteractionsUrls); + } + + return false; + }), + shareReplay({ refCount: false, bufferSize: 1 }), + ); + + showCurrentTabIsBlockedBanner$: Observable = combineLatest([ + this.domainSettingsService.blockedInteractionsUris$, + this.currentAutofillTab$, + ]).pipe( + map(([blockedInteractionsUrls, currentTab]) => { + if (blockedInteractionsUrls && currentTab?.url?.length) { + const tabHostname = Utils.getHostname(currentTab.url); + + if (!tabHostname) { + return false; + } + + const tabIsBlocked = isUrlInList(currentTab.url, blockedInteractionsUrls); + + const showScriptInjectionIsBlockedBanner = + tabIsBlocked && !blockedInteractionsUrls[tabHostname]?.bannerIsDismissed; + + return showScriptInjectionIsBlockedBanner; + } + + return false; + }), + shareReplay({ refCount: false, bufferSize: 1 }), + ); + + async dismissCurrentTabIsBlockedBanner() { + try { + const currentTab = await firstValueFrom(this.currentAutofillTab$); + const currentTabHostname = currentTab?.url.length && Utils.getHostname(currentTab.url); + + if (!currentTabHostname) { + return; + } + + const blockedURLs = await firstValueFrom(this.domainSettingsService.blockedInteractionsUris$); + + let tabIsBlocked = false; + if (blockedURLs && currentTab?.url?.length) { + tabIsBlocked = isUrlInList(currentTab.url, blockedURLs); + } + + if (tabIsBlocked) { + void this.domainSettingsService.setBlockedInteractionsUris({ + ...blockedURLs, + [currentTabHostname as string]: { bannerIsDismissed: true }, + }); + } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e) { + throw new Error( + "There was a problem dismissing the blocked interaction URL notification banner", + ); + } + } + /** * Observable that indicates whether autofill is allowed in the current context. * Autofill is allowed when there is a current tab and the popup is not in a popout window. @@ -76,7 +149,20 @@ export class VaultPopupAutofillService { if (!tab) { return of([]); } - return this.autofillService.collectPageDetailsFromTab$(tab); + + return this.domainSettingsService.blockedInteractionsUris$.pipe( + switchMap((blockedURLs) => { + if (blockedURLs && tab?.url?.length) { + const tabIsBlocked = isUrlInList(tab.url, blockedURLs); + + if (tabIsBlocked) { + return of([]); + } + } + + return this.autofillService.collectPageDetailsFromTab$(tab); + }), + ); }), shareReplay({ refCount: false, bufferSize: 1 }), ); @@ -123,6 +209,7 @@ export class VaultPopupAutofillService { constructor( private autofillService: AutofillService, + private domainSettingsService: DomainSettingsService, private i18nService: I18nService, private toastService: ToastService, private platformUtilService: PlatformUtilsService, diff --git a/apps/browser/src/vault/popup/services/vault-popup-copy-buttons.service.ts b/apps/browser/src/vault/popup/services/vault-popup-copy-buttons.service.ts index d6bd12c6200..6ea01f9b109 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-copy-buttons.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-copy-buttons.service.ts @@ -1,5 +1,5 @@ import { inject, Injectable } from "@angular/core"; -import { map, Observable } from "rxjs"; +import { map, Observable, shareReplay } from "rxjs"; import { GlobalStateProvider, @@ -31,6 +31,7 @@ export class VaultPopupCopyButtonsService { showQuickCopyActions$: Observable = this.displayMode$.pipe( map((displayMode) => displayMode === "quick"), + shareReplay({ bufferSize: 1, refCount: true }), ); async setShowQuickCopyActions(value: boolean) { diff --git a/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts b/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts index ffa09aeb554..52cb393c684 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts @@ -1,4 +1,5 @@ -import { TestBed } from "@angular/core/testing"; +import { WritableSignal, signal } from "@angular/core"; +import { TestBed, discardPeriodicTasks, fakeAsync, tick } from "@angular/core/testing"; import { mock } from "jest-mock-extended"; import { BehaviorSubject, firstValueFrom, timeout } from "rxjs"; @@ -6,17 +7,22 @@ import { CollectionService, CollectionView } from "@bitwarden/admin-console/comm import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ProductTierType } from "@bitwarden/common/billing/enums"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SyncService } from "@bitwarden/common/platform/sync"; -import { ObservableTracker } from "@bitwarden/common/spec"; -import { CipherId } from "@bitwarden/common/types/guid"; +import { ObservableTracker, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; import { CipherType } from "@bitwarden/common/vault/enums"; +import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data"; +import { LocalData } from "@bitwarden/common/vault/models/data/local.data"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { InlineMenuFieldQualificationService } from "../../../../../browser/src/autofill/services/inline-menu-field-qualification.service"; +import { InlineMenuFieldQualificationService } from "../../../autofill/services/inline-menu-field-qualification.service"; import { BrowserApi } from "../../../platform/browser/browser-api"; +import { PopupViewCacheService } from "../../../platform/popup/view-cache/popup-view-cache.service"; import { VaultPopupAutofillService } from "./vault-popup-autofill.service"; import { VaultPopupItemsService } from "./vault-popup-items.service"; @@ -31,6 +37,14 @@ describe("VaultPopupItemsService", () => { let mockOrg: Organization; let mockCollections: CollectionView[]; let activeUserLastSync$: BehaviorSubject; + let viewCacheService: { + signal: jest.Mock; + mockSignal: WritableSignal; + }; + + let ciphersSubject: BehaviorSubject>; + let localDataSubject: BehaviorSubject>; + let failedToDecryptCiphersSubject: BehaviorSubject; const cipherServiceMock = mock(); const vaultSettingsServiceMock = mock(); @@ -41,6 +55,8 @@ describe("VaultPopupItemsService", () => { const vaultAutofillServiceMock = mock(); const syncServiceMock = mock(); const inlineMenuFieldQualificationServiceMock = mock(); + const userId = Utils.newGuid() as UserId; + const accountServiceMock = mockAccountServiceWith(userId); beforeEach(() => { allCiphers = cipherFactory(10); @@ -56,10 +72,22 @@ describe("VaultPopupItemsService", () => { cipherList[3].favorite = true; cipherServiceMock.getAllDecrypted.mockResolvedValue(cipherList); - cipherServiceMock.ciphers$ = new BehaviorSubject(null); - cipherServiceMock.localData$ = new BehaviorSubject(null); - cipherServiceMock.failedToDecryptCiphers$ = new BehaviorSubject([]); - searchService.searchCiphers.mockImplementation(async (_, __, ciphers) => ciphers); + + ciphersSubject = new BehaviorSubject>({}); + localDataSubject = new BehaviorSubject>({}); + failedToDecryptCiphersSubject = new BehaviorSubject([]); + + cipherServiceMock.ciphers$.mockImplementation((userId: UserId) => + ciphersSubject.asObservable(), + ); + cipherServiceMock.localData$.mockImplementation((userId: UserId) => + localDataSubject.asObservable(), + ); + cipherServiceMock.failedToDecryptCiphers$.mockImplementation((userId: UserId) => + failedToDecryptCiphersSubject.asObservable(), + ); + + searchService.searchCiphers.mockImplementation(async (userId, _, __, ciphers) => ciphers); cipherServiceMock.filterCiphersForUrl.mockImplementation(async (ciphers) => ciphers.filter((c) => ["0", "1"].includes(c.id)), ); @@ -97,26 +125,35 @@ describe("VaultPopupItemsService", () => { { id: "col2", name: "Collection 2" } as CollectionView, ]; - organizationServiceMock.organizations$ = new BehaviorSubject([mockOrg]); + organizationServiceMock.organizations$.mockReturnValue(new BehaviorSubject([mockOrg])); collectionService.decryptedCollections$ = new BehaviorSubject(mockCollections); activeUserLastSync$ = new BehaviorSubject(new Date()); syncServiceMock.activeUserLastSync$.mockReturnValue(activeUserLastSync$); + const testSearchSignal = createMockSignal(""); + viewCacheService = { + mockSignal: testSearchSignal, + signal: jest.fn((options) => testSearchSignal), + }; + testBed = TestBed.configureTestingModule({ providers: [ { provide: CipherService, useValue: cipherServiceMock }, { provide: VaultSettingsService, useValue: vaultSettingsServiceMock }, { provide: SearchService, useValue: searchService }, { provide: OrganizationService, useValue: organizationServiceMock }, + { provide: AccountService, useValue: accountServiceMock }, { provide: VaultPopupListFiltersService, useValue: vaultPopupListFiltersServiceMock }, { provide: CollectionService, useValue: collectionService }, { provide: VaultPopupAutofillService, useValue: vaultAutofillServiceMock }, { provide: SyncService, useValue: syncServiceMock }, + { provide: AccountService, useValue: mockAccountServiceWith("UserId" as UserId) }, { provide: InlineMenuFieldQualificationService, useValue: inlineMenuFieldQualificationServiceMock, }, + { provide: PopupViewCacheService, useValue: viewCacheService }, ], }); @@ -150,7 +187,7 @@ describe("VaultPopupItemsService", () => { await tracker.expectEmission(); - (cipherServiceMock.ciphers$ as BehaviorSubject).next(null); + ciphersSubject.next({}); await tracker.expectEmission(); @@ -164,7 +201,7 @@ describe("VaultPopupItemsService", () => { await tracker.expectEmission(); - (cipherServiceMock.localData$ as BehaviorSubject).next(null); + localDataSubject.next({}); await tracker.expectEmission(); @@ -239,7 +276,7 @@ describe("VaultPopupItemsService", () => { it("should filter autoFillCiphers$ down to search term", (done) => { const searchText = "Login"; - searchService.searchCiphers.mockImplementation(async (q, _, ciphers) => { + searchService.searchCiphers.mockImplementation(async (userId, q, _, ciphers) => { return ciphers.filter((cipher) => { return cipher.name.includes(searchText); }); @@ -368,7 +405,7 @@ describe("VaultPopupItemsService", () => { cipherServiceMock.getAllDecrypted.mockResolvedValue(ciphers); - (cipherServiceMock.ciphers$ as BehaviorSubject).next(null); + ciphersSubject.next({}); const deletedCiphers = await firstValueFrom(service.deletedCiphers$); expect(deletedCiphers.length).toBe(1); @@ -417,7 +454,7 @@ describe("VaultPopupItemsService", () => { it("should cycle when cipherService.ciphers$ emits", async () => { // Restart tracking tracked = new ObservableTracker(service.loading$); - (cipherServiceMock.ciphers$ as BehaviorSubject).next(null); + ciphersSubject.next({}); await trackedCiphers.pauseUntilReceived(2); @@ -431,15 +468,37 @@ describe("VaultPopupItemsService", () => { describe("applyFilter", () => { it("should call search Service with the new search term", (done) => { const searchText = "Hello"; - service.applyFilter(searchText); const searchServiceSpy = jest.spyOn(searchService, "searchCiphers"); + service.applyFilter(searchText); service.favoriteCiphers$.subscribe(() => { - expect(searchServiceSpy).toHaveBeenCalledWith(searchText, null, expect.anything()); + expect(searchServiceSpy).toHaveBeenCalledWith( + "UserId", + searchText, + undefined, + expect.anything(), + ); done(); }); }); }); + + it("should update searchText$ when applyFilter is called", fakeAsync(() => { + let latestValue: string | null; + service.searchText$.subscribe((val) => { + latestValue = val; + }); + tick(); + expect(latestValue!).toEqual(""); + + service.applyFilter("test search"); + tick(); + expect(latestValue!).toEqual("test search"); + + expect(viewCacheService.mockSignal()).toEqual("test search"); + + discardPeriodicTasks(); + })); }); // A function to generate a list of ciphers of different types @@ -494,3 +553,9 @@ function cipherFactory(count: number): Record { } return Object.fromEntries(ciphers.map((c) => [c.id, c])); } + +function createMockSignal(initialValue: T): WritableSignal { + const s = signal(initialValue); + s.set = (value: T) => s.update(() => value); + return s; +} diff --git a/apps/browser/src/vault/popup/services/vault-popup-items.service.ts b/apps/browser/src/vault/popup/services/vault-popup-items.service.ts index fb230df7953..dac6a141d41 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-items.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-items.service.ts @@ -1,8 +1,6 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { inject, Injectable, NgZone } from "@angular/core"; +import { toObservable } from "@angular/core/rxjs-interop"; import { - BehaviorSubject, combineLatest, concatMap, distinctUntilChanged, @@ -24,15 +22,18 @@ import { import { CollectionService } from "@bitwarden/admin-console/common"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SyncService } from "@bitwarden/common/platform/sync"; -import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; +import { CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { runInsideAngular } from "../../../platform/browser/run-inside-angular.operator"; +import { PopupViewCacheService } from "../../../platform/popup/view-cache/popup-view-cache.service"; import { waitUntil } from "../../util"; import { PopupCipherView } from "../views/popup-cipher.view"; @@ -46,7 +47,12 @@ import { MY_VAULT_ID, VaultPopupListFiltersService } from "./vault-popup-list-fi providedIn: "root", }) export class VaultPopupItemsService { - private _searchText$ = new BehaviorSubject(""); + private cachedSearchText = inject(PopupViewCacheService).signal({ + key: "vault-search-text", + initialValue: "", + }); + + readonly searchText$ = toObservable(this.cachedSearchText); /** * Subject that emits whenever new ciphers are being processed/filtered. @@ -54,8 +60,14 @@ export class VaultPopupItemsService { */ private _ciphersLoading$ = new Subject(); - latestSearchText$: Observable = this._searchText$.asObservable(); + private activeUserId$ = this.accountService.activeAccount$.pipe( + map((a) => a?.id), + filter((userId): userId is UserId => userId !== null), + ); + private organizations$ = this.activeUserId$.pipe( + switchMap((userId) => this.organizationService.organizations$(userId)), + ); /** * Observable that contains the list of other cipher types that should be shown * in the autofill section of the Vault tab. Depends on vault settings. @@ -67,9 +79,9 @@ export class VaultPopupItemsService { this.vaultPopupAutofillService.nonLoginCipherTypesOnPage$, ]).pipe( map(([showCardsSettingEnabled, showIdentitiesSettingEnabled, nonLoginCipherTypesOnPage]) => { - const showCards = showCardsSettingEnabled && nonLoginCipherTypesOnPage[CipherType.Card]; + const showCards = showCardsSettingEnabled || nonLoginCipherTypesOnPage[CipherType.Card]; const showIdentities = - showIdentitiesSettingEnabled && nonLoginCipherTypesOnPage[CipherType.Identity]; + showIdentitiesSettingEnabled || nonLoginCipherTypesOnPage[CipherType.Identity]; return [ ...(showCards ? [CipherType.Card] : []), @@ -82,25 +94,29 @@ export class VaultPopupItemsService { * Observable that contains the list of all decrypted ciphers. * @private */ - private _allDecryptedCiphers$: Observable = merge( - this.cipherService.ciphers$, - this.cipherService.localData$, - ).pipe( - runInsideAngular(inject(NgZone)), // Workaround to ensure cipher$ state provider emissions are run inside Angular - tap(() => this._ciphersLoading$.next()), - waitUntilSync(this.syncService), - switchMap(() => Utils.asyncToObservable(() => this.cipherService.getAllDecrypted())), - withLatestFrom(this.cipherService.failedToDecryptCiphers$), - map(([ciphers, failedToDecryptCiphers]) => [...failedToDecryptCiphers, ...ciphers]), + private _allDecryptedCiphers$: Observable = this.accountService.activeAccount$.pipe( + map((a) => a?.id), + filter((userId): userId is UserId => userId != null), + switchMap((userId) => + merge(this.cipherService.ciphers$(userId), this.cipherService.localData$(userId)).pipe( + runInsideAngular(this.ngZone), + tap(() => this._ciphersLoading$.next()), + waitUntilSync(this.syncService), + switchMap(() => + combineLatest([ + Utils.asyncToObservable(() => this.cipherService.getAllDecrypted(userId)), + this.cipherService.failedToDecryptCiphers$(userId), + ]), + ), + map(([ciphers, failedToDecryptCiphers]) => [...failedToDecryptCiphers, ...ciphers]), + ), + ), shareReplay({ refCount: true, bufferSize: 1 }), ); private _activeCipherList$: Observable = this._allDecryptedCiphers$.pipe( switchMap((ciphers) => - combineLatest([ - this.organizationService.organizations$, - this.collectionService.decryptedCollections$, - ]).pipe( + combineLatest([this.organizations$, this.collectionService.decryptedCollections$]).pipe( map(([organizations, collections]) => { const orgMap = Object.fromEntries(organizations.map((org) => [org.id, org])); const collectionMap = Object.fromEntries(collections.map((col) => [col.id, col])); @@ -123,22 +139,31 @@ export class VaultPopupItemsService { * Observable that indicates whether there is search text present that is searchable. * @private */ - private _hasSearchText$ = this._searchText$.pipe( - switchMap((searchText) => this.searchService.isSearchable(searchText)), + private _hasSearchText = combineLatest([ + this.searchText$, + getUserId(this.accountService.activeAccount$), + ]).pipe( + switchMap(([searchText, userId]) => { + return this.searchService.isSearchable(userId, searchText); + }), ); private _filteredCipherList$: Observable = combineLatest([ this._activeCipherList$, - this._searchText$, + this.searchText$, this.vaultPopupListFiltersService.filterFunction$, + getUserId(this.accountService.activeAccount$), ]).pipe( - map(([ciphers, searchText, filterFunction]): [CipherView[], string] => [ + map(([ciphers, searchText, filterFunction, userId]): [CipherView[], string, UserId] => [ filterFunction(ciphers), searchText, + userId, ]), switchMap( - ([ciphers, searchText]) => - this.searchService.searchCiphers(searchText, null, ciphers) as Promise, + ([ciphers, searchText, userId]) => + this.searchService.searchCiphers(userId, searchText, undefined, ciphers) as Promise< + PopupCipherView[] + >, ), shareReplay({ refCount: true, bufferSize: 1 }), ); @@ -155,7 +180,7 @@ export class VaultPopupItemsService { this.vaultPopupAutofillService.currentAutofillTab$, ]).pipe( switchMap(([ciphers, otherTypes, tab]) => { - if (!tab) { + if (!tab || !tab.url) { return of([]); } return this.cipherService.filterCiphersForUrl(ciphers, tab.url, otherTypes); @@ -207,12 +232,13 @@ export class VaultPopupItemsService { * Observable that indicates whether a filter or search text is currently applied to the ciphers. */ hasFilterApplied$ = combineLatest([ - this._hasSearchText$, + this._hasSearchText, this.vaultPopupListFiltersService.filters$, ]).pipe( map(([hasSearchText, filters]) => { return hasSearchText || Object.values(filters).some((filter) => filter !== null); }), + shareReplay({ bufferSize: 1, refCount: true }), ); /** @@ -232,14 +258,14 @@ export class VaultPopupItemsService { /** Observable that indicates when the user should see the deactivated org state */ showDeactivatedOrg$: Observable = combineLatest([ this.vaultPopupListFiltersService.filters$.pipe(distinctUntilKeyChanged("organization")), - this.organizationService.organizations$, + this.organizations$, ]).pipe( map(([filters, orgs]) => { if (!filters.organization || filters.organization.id === MY_VAULT_ID) { return false; } - const org = orgs.find((o) => o.id === filters.organization.id); + const org = orgs.find((o) => o.id === filters?.organization?.id); return org ? !org.enabled : false; }), ); @@ -249,10 +275,7 @@ export class VaultPopupItemsService { */ deletedCiphers$: Observable = this._allDecryptedCiphers$.pipe( switchMap((ciphers) => - combineLatest([ - this.organizationService.organizations$, - this.collectionService.decryptedCollections$, - ]).pipe( + combineLatest([this.organizations$, this.collectionService.decryptedCollections$]).pipe( map(([organizations, collections]) => { const orgMap = Object.fromEntries(organizations.map((org) => [org.id, org])); const collectionMap = Object.fromEntries(collections.map((col) => [col.id, col])); @@ -281,10 +304,12 @@ export class VaultPopupItemsService { private collectionService: CollectionService, private vaultPopupAutofillService: VaultPopupAutofillService, private syncService: SyncService, + private accountService: AccountService, + private ngZone: NgZone, ) {} applyFilter(newSearchText: string) { - this._searchText$.next(newSearchText); + this.cachedSearchText.set(newSearchText); } /** diff --git a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts index 0eb91c6cbe2..99a27c54bcc 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts @@ -1,8 +1,9 @@ -import { TestBed } from "@angular/core/testing"; +import { Injector, WritableSignal, runInInjectionContext, signal } from "@angular/core"; +import { TestBed, discardPeriodicTasks, fakeAsync, tick } from "@angular/core/testing"; import { FormBuilder } from "@angular/forms"; import { BehaviorSubject, skipWhile } from "rxjs"; -import { CollectionService, Collection, CollectionView } from "@bitwarden/admin-console/common"; +import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; @@ -19,15 +20,27 @@ import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; -import { MY_VAULT_ID, VaultPopupListFiltersService } from "./vault-popup-list-filters.service"; +import { PopupViewCacheService } from "../../../platform/popup/view-cache/popup-view-cache.service"; + +import { + CachedFilterState, + MY_VAULT_ID, + VaultPopupListFiltersService, +} from "./vault-popup-list-filters.service"; describe("VaultPopupListFiltersService", () => { let service: VaultPopupListFiltersService; - const memberOrganizations$ = new BehaviorSubject([]); - const folderViews$ = new BehaviorSubject([]); + let _memberOrganizations$ = new BehaviorSubject([]); + const memberOrganizations$ = (userId: UserId) => _memberOrganizations$; + const organizations$ = new BehaviorSubject([]); + let folderViews$ = new BehaviorSubject([]); const cipherViews$ = new BehaviorSubject({}); - const decryptedCollections$ = new BehaviorSubject([]); + let decryptedCollections$ = new BehaviorSubject([]); const policyAppliesToActiveUser$ = new BehaviorSubject(false); + let viewCacheService: { + signal: jest.Mock; + mockSignal: WritableSignal; + }; const collectionService = { decryptedCollections$, @@ -39,11 +52,12 @@ describe("VaultPopupListFiltersService", () => { } as unknown as FolderService; const cipherService = { - cipherViews$, + cipherViews$: () => cipherViews$, } as unknown as CipherService; const organizationService = { memberOrganizations$, + organizations$, } as unknown as OrganizationService; const i18nService = { @@ -58,12 +72,19 @@ describe("VaultPopupListFiltersService", () => { const update = jest.fn().mockResolvedValue(undefined); beforeEach(() => { - memberOrganizations$.next([]); - decryptedCollections$.next([]); + _memberOrganizations$ = new BehaviorSubject([]); // Fresh instance per test + folderViews$ = new BehaviorSubject([]); // Fresh instance per test + decryptedCollections$ = new BehaviorSubject([]); // Fresh instance per test policyAppliesToActiveUser$.next(false); policyService.policyAppliesToActiveUser$.mockClear(); const accountService = mockAccountServiceWith("userId" as UserId); + const mockCachedSignal = createMockSignal({}); + + viewCacheService = { + mockSignal: mockCachedSignal, + signal: jest.fn(() => mockCachedSignal), + }; collectionService.getAllNested = () => Promise.resolve([]); TestBed.configureTestingModule({ @@ -101,6 +122,10 @@ describe("VaultPopupListFiltersService", () => { provide: AccountService, useValue: accountService, }, + { + provide: PopupViewCacheService, + useValue: viewCacheService, + }, ], }); @@ -135,7 +160,7 @@ describe("VaultPopupListFiltersService", () => { describe("organizations$", () => { it('does not add "myVault" to the list of organizations when there are no organizations', (done) => { - memberOrganizations$.next([]); + _memberOrganizations$.next([]); service.organizations$.subscribe((organizations) => { expect(organizations.map((o) => o.label)).toEqual([]); @@ -145,7 +170,7 @@ describe("VaultPopupListFiltersService", () => { it('adds "myVault" to the list of organizations when there are other organizations', (done) => { const orgs = [{ name: "bobby's org", id: "1234-3323-23223" }] as Organization[]; - memberOrganizations$.next(orgs); + _memberOrganizations$.next(orgs); service.organizations$.subscribe((organizations) => { expect(organizations.map((o) => o.label)).toEqual(["myVault", "bobby's org"]); @@ -158,7 +183,7 @@ describe("VaultPopupListFiltersService", () => { { name: "bobby's org", id: "1234-3323-23223" }, { name: "alice's org", id: "2223-4343-99888" }, ] as Organization[]; - memberOrganizations$.next(orgs); + _memberOrganizations$.next(orgs); service.organizations$.subscribe((organizations) => { expect(organizations.map((o) => o.label)).toEqual([ @@ -179,7 +204,7 @@ describe("VaultPopupListFiltersService", () => { it("returns an empty array when the policy applies and there is a single organization", (done) => { policyAppliesToActiveUser$.next(true); - memberOrganizations$.next([ + _memberOrganizations$.next([ { name: "bobby's org", id: "1234-3323-23223" }, ] as Organization[]); @@ -196,7 +221,7 @@ describe("VaultPopupListFiltersService", () => { { name: "alice's org", id: "2223-4343-99888" }, ] as Organization[]; - memberOrganizations$.next(orgs); + _memberOrganizations$.next(orgs); service.organizations$.subscribe((organizations) => { expect(organizations.map((o) => o.label)).toEqual([ @@ -216,7 +241,7 @@ describe("VaultPopupListFiltersService", () => { { name: "catherine's org", id: "77733-4343-99888" }, ] as Organization[]; - memberOrganizations$.next(orgs); + _memberOrganizations$.next(orgs); service.organizations$.subscribe((organizations) => { expect(organizations.map((o) => o.label)).toEqual([ @@ -240,7 +265,7 @@ describe("VaultPopupListFiltersService", () => { }, ] as Organization[]; - memberOrganizations$.next(orgs); + _memberOrganizations$.next(orgs); service.organizations$.subscribe((organizations) => { expect(organizations.map((o) => o.icon)).toEqual(["bwi-user", "bwi-family"]); @@ -258,7 +283,7 @@ describe("VaultPopupListFiltersService", () => { }, ] as Organization[]; - memberOrganizations$.next(orgs); + _memberOrganizations$.next(orgs); service.organizations$.subscribe((organizations) => { expect(organizations.map((o) => o.icon)).toEqual(["bwi-user", "bwi-family"]); @@ -276,7 +301,7 @@ describe("VaultPopupListFiltersService", () => { }, ] as Organization[]; - memberOrganizations$.next(orgs); + _memberOrganizations$.next(orgs); service.organizations$.subscribe((organizations) => { expect(organizations.map((o) => o.icon)).toEqual([ @@ -437,7 +462,7 @@ describe("VaultPopupListFiltersService", () => { }); it("filters by collection", (done) => { - const collection = { id: "1234" } as Collection; + const collection = { id: "1234" } as CollectionView; service.filterFunction$.subscribe((filterFunction) => { expect(filterFunction(ciphers)).toEqual([ciphers[1]]); @@ -488,7 +513,7 @@ describe("VaultPopupListFiltersService", () => { state$.next(true); service.filterVisibilityState$.subscribe((filterVisibility) => { - expect(filterVisibility).toBeTrue(); + expect(filterVisibility).toBe(true); done(); }); }); @@ -496,10 +521,200 @@ describe("VaultPopupListFiltersService", () => { it("updates stored filter state", async () => { await service.updateFilterVisibility(false); - expect(update).toHaveBeenCalledOnce(); + expect(update).toHaveBeenCalledTimes(1); // Get callback passed to `update` const updateCallback = update.mock.calls[0][0]; expect(updateCallback()).toBe(false); }); }); + + describe("caching", () => { + it("initializes form from cached state", fakeAsync(() => { + const cachedState: CachedFilterState = { + organizationId: MY_VAULT_ID, + collectionId: "test-collection-id", + folderId: "test-folder-id", + cipherType: CipherType.Login, + }; + + const seededOrganizations: Organization[] = [ + { id: MY_VAULT_ID, name: "Test Org" } as Organization, + ]; + const seededCollections: CollectionView[] = [ + { + id: "test-collection-id", + organizationId: MY_VAULT_ID, + name: "Test collection", + } as CollectionView, + ]; + const seededFolderViews: FolderView[] = [ + { id: "test-folder-id", name: "Test Folder" } as FolderView, + ]; + + const { service } = createSeededVaultPopupListFiltersService( + seededOrganizations, + seededCollections, + seededFolderViews, + cachedState, + ); + + tick(); + + expect(service.filterForm.value).toEqual({ + organization: { id: MY_VAULT_ID }, + collection: { + id: "test-collection-id", + organizationId: MY_VAULT_ID, + name: "Test collection", + }, + folder: { id: "test-folder-id", name: "Test Folder" }, + cipherType: CipherType.Login, + }); + discardPeriodicTasks(); + })); + + it("serializes filters to cache on changes", fakeAsync(() => { + const seededOrganizations: Organization[] = [ + { id: "test-org-id", name: "Org" } as Organization, + ]; + const seededCollections: CollectionView[] = [ + { + id: "test-collection-id", + organizationId: "test-org-id", + name: "Test collection", + } as CollectionView, + ]; + const seededFolderViews: FolderView[] = [ + { id: "test-folder-id", name: "Test Folder" } as FolderView, + ]; + + const { service, cachedSignal } = createSeededVaultPopupListFiltersService( + seededOrganizations, + seededCollections, + seededFolderViews, + {}, + ); + const testOrg = { id: "test-org-id", name: "Org" } as Organization; + const testCollection = { + id: "test-collection-id", + organizationId: "test-org-id", + name: "Test collection", + } as CollectionView; + const testFolder = { id: "test-folder-id", name: "Test Folder" } as FolderView; + + service.filterForm.patchValue({ + organization: testOrg, + collection: testCollection, + folder: testFolder, + cipherType: CipherType.Card, + }); + + tick(300); + + // force another emission by patching with the same value again. workaround for debounce times + service.filterForm.patchValue({ + organization: testOrg, + collection: testCollection, + folder: testFolder, + cipherType: CipherType.Card, + }); + + tick(300); + + expect(cachedSignal()).toEqual({ + organizationId: "test-org-id", + collectionId: "test-collection-id", + folderId: "test-folder-id", + cipherType: CipherType.Card, + }); + discardPeriodicTasks(); + })); + }); }); + +function createMockSignal(initialValue: T): WritableSignal { + const s = signal(initialValue); + s.set = (value: T) => s.update(() => value); + return s; +} + +// Helper function to create a seeded VaultPopupListFiltersService +function createSeededVaultPopupListFiltersService( + organizations: Organization[], + collections: CollectionView[], + folderViews: FolderView[], + cachedState: CachedFilterState = {}, +): { service: VaultPopupListFiltersService; cachedSignal: WritableSignal } { + const seededMemberOrganizations$ = new BehaviorSubject(organizations); + const seededCollections$ = new BehaviorSubject(collections); + const seededFolderViews$ = new BehaviorSubject(folderViews); + + const organizationServiceMock = { + memberOrganizations$: (userId: string) => seededMemberOrganizations$, + organizations$: seededMemberOrganizations$, + } as any; + + const collectionServiceMock = { + decryptedCollections$: seededCollections$, + getAllNested: () => + Promise.resolve( + seededCollections$.value.map((c) => ({ + children: [], + node: c, + parent: null, + })), + ), + } as any; + + const folderServiceMock = { + folderViews$: () => seededFolderViews$, + } as any; + + const cipherServiceMock = { + cipherViews$: () => new BehaviorSubject({}), + } as any; + + const i18nServiceMock = { + t: (key: string) => key, + } as any; + + const policyServiceMock = { + policyAppliesToActiveUser$: jest.fn(() => new BehaviorSubject(false)), + } as any; + + const stateProviderMock = { + getGlobal: () => ({ + state$: new BehaviorSubject(false), + update: jest.fn().mockResolvedValue(undefined), + }), + } as any; + + const accountServiceMock = mockAccountServiceWith("userId" as UserId); + const formBuilderInstance = new FormBuilder(); + + const seededCachedSignal = createMockSignal(cachedState); + const viewCacheServiceMock = { + signal: jest.fn(() => seededCachedSignal), + mockSignal: seededCachedSignal, + } as any; + + // Get an injector from TestBed so that we can run in an injection context. + const injector = TestBed.inject(Injector); + let service: VaultPopupListFiltersService; + runInInjectionContext(injector, () => { + service = new VaultPopupListFiltersService( + folderServiceMock, + cipherServiceMock, + organizationServiceMock, + i18nServiceMock, + collectionServiceMock, + formBuilderInstance, + policyServiceMock, + stateProviderMock, + accountServiceMock, + viewCacheServiceMock, + ); + }); + + return { service: service!, cachedSignal: seededCachedSignal }; +} diff --git a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts index 8455fd587d0..8d9f6664e45 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts @@ -1,20 +1,22 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Injectable } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormBuilder } from "@angular/forms"; import { combineLatest, + debounceTime, distinctUntilChanged, + filter, map, Observable, + of, shareReplay, startWith, switchMap, + take, tap, } from "rxjs"; -import { CollectionService, Collection, CollectionView } from "@bitwarden/admin-console/common"; +import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { DynamicTreeNode } from "@bitwarden/angular/vault/vault-filter/models/dynamic-tree-node.model"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; @@ -29,6 +31,7 @@ import { StateProvider, VAULT_SETTINGS_DISK, } from "@bitwarden/common/platform/state"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -38,14 +41,26 @@ import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { ServiceUtils } from "@bitwarden/common/vault/service-utils"; import { ChipSelectOption } from "@bitwarden/components"; +import { PopupViewCacheService } from "../../../platform/popup/view-cache/popup-view-cache.service"; + const FILTER_VISIBILITY_KEY = new KeyDefinition(VAULT_SETTINGS_DISK, "filterVisibility", { deserializer: (obj) => obj, }); +/** + * Serialized state of the PopupListFilter for interfacing with the PopupViewCacheService + */ +export interface CachedFilterState { + organizationId?: string; + collectionId?: string; + folderId?: string; + cipherType?: CipherType | null; +} + /** All available cipher filters */ export type PopupListFilter = { organization: Organization | null; - collection: Collection | null; + collection: CollectionView | null; folder: FolderView | null; cipherType: CipherType | null; }; @@ -76,8 +91,9 @@ export class VaultPopupListFiltersService { * Observable for `filterForm` value */ filters$ = this.filterForm.valueChanges.pipe( - startWith(INITIAL_FILTERS), - ) as Observable; + startWith(this.filterForm.value), + shareReplay({ bufferSize: 1, refCount: true }), + ); /** Emits the number of applied filters. */ numberOfAppliedFilters$ = this.filters$.pipe( @@ -93,17 +109,65 @@ export class VaultPopupListFiltersService { */ private cipherViews: CipherView[] = []; - /** - * Observable of cipher views - */ - private cipherViews$: Observable = this.cipherService.cipherViews$.pipe( - tap((cipherViews) => { - this.cipherViews = Object.values(cipherViews); - }), - map((ciphers) => Object.values(ciphers)), + private activeUserId$ = this.accountService.activeAccount$.pipe( + map((a) => a?.id), + filter((userId): userId is UserId => userId !== null), ); - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + private serializeFilters(): CachedFilterState { + return { + organizationId: this.filterForm.value.organization?.id, + collectionId: this.filterForm.value.collection?.id, + folderId: this.filterForm.value.folder?.id, + cipherType: this.filterForm.value.cipherType, + }; + } + + private deserializeFilters(state: CachedFilterState): void { + combineLatest([this.organizations$, this.collections$, this.folders$]) + .pipe(take(1)) + .subscribe(([orgOptions, collectionOptions, folderOptions]) => { + const patchValue: PopupListFilter = { + organization: null, + collection: null, + folder: null, + cipherType: null, + }; + + if (state.organizationId) { + if (state.organizationId === MY_VAULT_ID) { + patchValue.organization = { id: MY_VAULT_ID } as Organization; + } else { + const orgOption = orgOptions.find((o) => o.value?.id === state.organizationId); + patchValue.organization = orgOption?.value || null; + } + } + + if (state.collectionId) { + const collection = collectionOptions + .flatMap((c) => this.flattenOptions(c)) + .find((c) => c.value?.id === state.collectionId)?.value; + patchValue.collection = collection || null; + } + + if (state.folderId) { + const folder = folderOptions + .flatMap((f) => this.flattenOptions(f)) + .find((f) => f.value?.id === state.folderId)?.value; + patchValue.folder = folder || null; + } + + if (state.cipherType) { + patchValue.cipherType = state.cipherType; + } + + this.filterForm.patchValue(patchValue); + }); + } + + private flattenOptions(option: ChipSelectOption): ChipSelectOption[] { + return [option, ...(option.children?.flatMap((c) => this.flattenOptions(c)) || [])]; + } constructor( private folderService: FolderService, @@ -115,10 +179,30 @@ export class VaultPopupListFiltersService { private policyService: PolicyService, private stateProvider: StateProvider, private accountService: AccountService, + private viewCacheService: PopupViewCacheService, ) { this.filterForm.controls.organization.valueChanges .pipe(takeUntilDestroyed()) .subscribe(this.validateOrganizationChange.bind(this)); + + const cachedFilters = this.viewCacheService.signal({ + key: "vault-filters", + initialValue: {}, + deserializer: (v) => v, + }); + + this.deserializeFilters(cachedFilters()); + + // Save changes to cache + this.filterForm.valueChanges + .pipe( + debounceTime(300), + map(() => this.serializeFilters()), + distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)), + ) + .subscribe((state) => { + cachedFilters.set(state); + }); } /** Stored state for the visibility of the filters. */ @@ -140,14 +224,11 @@ export class VaultPopupListFiltersService { return false; } - if ( - filters.collection !== null && - !cipher.collectionIds.includes(filters.collection.id) - ) { + if (filters.collection && !cipher.collectionIds?.includes(filters.collection.id)) { return false; } - if (filters.folder !== null && cipher.folderId !== filters.folder.id) { + if (filters.folder && cipher.folderId !== filters.folder.id) { return false; } @@ -157,7 +238,7 @@ export class VaultPopupListFiltersService { if (cipher.organizationId !== null) { return false; } - } else if (filters.organization !== null) { + } else if (filters.organization) { if (cipher.organizationId !== filters.organization.id) { return false; } @@ -208,7 +289,11 @@ export class VaultPopupListFiltersService { * Organization array structured to be directly passed to `ChipSelectComponent` */ organizations$: Observable[]> = combineLatest([ - this.organizationService.memberOrganizations$, + this.accountService.activeAccount$.pipe( + switchMap((account) => + account === null ? of([]) : this.organizationService.memberOrganizations$(account.id), + ), + ), this.policyService.policyAppliesToActiveUser$(PolicyType.PersonalOwnership), ]).pipe( map(([orgs, personalOwnershipApplies]): [Organization[], boolean] => [ @@ -263,14 +348,23 @@ export class VaultPopupListFiltersService { }), ]; }), + shareReplay({ refCount: true, bufferSize: 1 }), ); /** * Folder array structured to be directly passed to `ChipSelectComponent` */ folders$: Observable[]> = this.activeUserId$.pipe( - switchMap((userId) => - combineLatest([ + switchMap((userId) => { + // Observable of cipher views + const cipherViews$ = this.cipherService.cipherViews$(userId).pipe( + tap((cipherViews) => { + this.cipherViews = Object.values(cipherViews); + }), + map((ciphers) => Object.values(ciphers)), + ); + + return combineLatest([ this.filters$.pipe( distinctUntilChanged( (previousFilter, currentFilter) => @@ -279,12 +373,12 @@ export class VaultPopupListFiltersService { ), ), this.folderService.folderViews$(userId), - this.cipherViews$, + cipherViews$, ]).pipe( map(([filters, folders, cipherViews]): [PopupListFilter, FolderView[], CipherView[]] => { if (folders.length === 1 && folders[0].id === null) { // Do not display folder selections when only the "no folder" option is available. - return [filters, [], cipherViews]; + return [filters as PopupListFilter, [], cipherViews]; } // Sort folders by alphabetic name @@ -303,7 +397,7 @@ export class VaultPopupListFiltersService { // Move the "no folder" option to the end of the list arrangedFolders = [...folders.filter((f) => f.id !== null), updatedNoFolder]; } - return [filters, arrangedFolders, cipherViews]; + return [filters as PopupListFilter, arrangedFolders, cipherViews]; }), map(([filters, folders, cipherViews]) => { const organizationId = filters.organization?.id ?? null; @@ -328,8 +422,9 @@ export class VaultPopupListFiltersService { map((folders) => folders.nestedList.map((f) => this.convertToChipSelectOption(f, "bwi-folder")), ), - ), - ), + ); + }), + shareReplay({ refCount: true, bufferSize: 1 }), ); /** @@ -366,8 +461,12 @@ export class VaultPopupListFiltersService { map((collections) => collections.nestedList.map((c) => this.convertToChipSelectOption(c, "bwi-collection")), ), + shareReplay({ refCount: true, bufferSize: 1 }), ); + /** Organizations, collection, folders filters. */ + allFilters$ = combineLatest([this.organizations$, this.collections$, this.folders$]); + /** Updates the stored state for filter visibility. */ async updateFilterVisibility(isVisible: boolean): Promise { await this.filterVisibilityState.update(() => isVisible); @@ -404,7 +503,7 @@ export class VaultPopupListFiltersService { // Remove "/" from beginning and end of the folder name // then split the folder name by the delimiter const parts = f.name != null ? f.name.replace(/^\/+|\/+$/g, "").split(NESTING_DELIMITER) : []; - ServiceUtils.nestedTraverse(nodes, 0, parts, folderCopy, null, NESTING_DELIMITER); + ServiceUtils.nestedTraverse(nodes, 0, parts, folderCopy, undefined, NESTING_DELIMITER); }); return nodes; @@ -423,7 +522,7 @@ export class VaultPopupListFiltersService { // When the organization filter changes and a collection is already selected, // reset the collection filter if the collection does not belong to the new organization filter if (currentFilters.collection && currentFilters.collection.organizationId !== organization.id) { - this.filterForm.get("collection").setValue(null); + this.filterForm.get("collection")?.setValue(null); } // When the organization filter changes and a folder is already selected, @@ -438,12 +537,12 @@ export class VaultPopupListFiltersService { // Find any ciphers within the organization that belong to the current folder const newOrgContainsFolder = orgCiphers.some( - (oc) => oc.folderId === currentFilters.folder.id, + (oc) => oc.folderId === currentFilters?.folder?.id, ); // If the new organization does not contain the current folder, reset the folder filter if (!newOrgContainsFolder) { - this.filterForm.get("folder").setValue(null); + this.filterForm.get("folder")?.setValue(null); } } } diff --git a/apps/browser/src/vault/popup/services/vault-popup-scroll-position.service.spec.ts b/apps/browser/src/vault/popup/services/vault-popup-scroll-position.service.spec.ts new file mode 100644 index 00000000000..562375f8f85 --- /dev/null +++ b/apps/browser/src/vault/popup/services/vault-popup-scroll-position.service.spec.ts @@ -0,0 +1,137 @@ +import { CdkVirtualScrollableElement } from "@angular/cdk/scrolling"; +import { fakeAsync, TestBed, tick } from "@angular/core/testing"; +import { NavigationEnd, Router } from "@angular/router"; +import { Subject, Subscription } from "rxjs"; + +import { VaultPopupScrollPositionService } from "./vault-popup-scroll-position.service"; + +describe("VaultPopupScrollPositionService", () => { + let service: VaultPopupScrollPositionService; + const events$ = new Subject(); + const unsubscribe = jest.fn(); + + beforeEach(async () => { + unsubscribe.mockClear(); + + await TestBed.configureTestingModule({ + providers: [ + VaultPopupScrollPositionService, + { provide: Router, useValue: { events: events$ } }, + ], + }); + + service = TestBed.inject(VaultPopupScrollPositionService); + + // set up dummy values + service["scrollPosition"] = 234; + service["scrollSubscription"] = { unsubscribe } as unknown as Subscription; + }); + + describe("router events", () => { + it("does not reset service when navigating to `/tabs/vault`", fakeAsync(() => { + const event = new NavigationEnd(22, "/tabs/vault", ""); + events$.next(event); + + tick(); + + expect(service["scrollPosition"]).toBe(234); + expect(service["scrollSubscription"]).not.toBeNull(); + })); + + it("resets values when navigating to other tab pages", fakeAsync(() => { + const event = new NavigationEnd(23, "/tabs/generator", ""); + events$.next(event); + + tick(); + + expect(service["scrollPosition"]).toBeNull(); + expect(unsubscribe).toHaveBeenCalled(); + expect(service["scrollSubscription"]).toBeNull(); + })); + }); + + describe("stop", () => { + it("removes scroll listener", () => { + service.stop(); + + expect(unsubscribe).toHaveBeenCalledTimes(1); + expect(service["scrollSubscription"]).toBeNull(); + }); + + it("resets stored values", () => { + service.stop(true); + + expect(service["scrollPosition"]).toBeNull(); + }); + }); + + describe("start", () => { + const elementScrolled$ = new Subject(); + const focus = jest.fn(); + const nativeElement = { + scrollTop: 0, + querySelector: jest.fn(() => ({ focus })), + addEventListener: jest.fn(), + style: { + visibility: "", + }, + }; + const virtualElement = { + elementScrolled: () => elementScrolled$, + getElementRef: () => ({ nativeElement }), + scrollTo: jest.fn(), + } as unknown as CdkVirtualScrollableElement; + + afterEach(() => { + // remove the actual subscription created by `.subscribe` + service["scrollSubscription"]?.unsubscribe(); + }); + + describe("initial scroll position", () => { + beforeEach(() => { + (virtualElement.scrollTo as jest.Mock).mockClear(); + nativeElement.querySelector.mockClear(); + }); + + it("does not scroll when `scrollPosition` is null", () => { + service["scrollPosition"] = null; + + service.start(virtualElement); + + expect(virtualElement.scrollTo).not.toHaveBeenCalled(); + }); + + it("scrolls the virtual element to `scrollPosition`", fakeAsync(() => { + service["scrollPosition"] = 500; + nativeElement.scrollTop = 500; + + service.start(virtualElement); + tick(); + + expect(virtualElement.scrollTo).toHaveBeenCalledWith({ behavior: "instant", top: 500 }); + })); + }); + + describe("scroll listener", () => { + it("unsubscribes from any existing subscription", () => { + service.start(virtualElement); + + expect(unsubscribe).toHaveBeenCalled(); + }); + + it("subscribes to `elementScrolled`", fakeAsync(() => { + virtualElement.measureScrollOffset = jest.fn(() => 455); + + service.start(virtualElement); + + elementScrolled$.next(null); // first subscription is skipped by `skip(1)` + elementScrolled$.next(null); + tick(); + + expect(virtualElement.measureScrollOffset).toHaveBeenCalledTimes(1); + expect(virtualElement.measureScrollOffset).toHaveBeenCalledWith("top"); + expect(service["scrollPosition"]).toBe(455); + })); + }); + }); +}); diff --git a/apps/browser/src/vault/popup/services/vault-popup-scroll-position.service.ts b/apps/browser/src/vault/popup/services/vault-popup-scroll-position.service.ts new file mode 100644 index 00000000000..5bfe0ec9331 --- /dev/null +++ b/apps/browser/src/vault/popup/services/vault-popup-scroll-position.service.ts @@ -0,0 +1,81 @@ +import { CdkVirtualScrollableElement } from "@angular/cdk/scrolling"; +import { inject, Injectable } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { NavigationEnd, Router } from "@angular/router"; +import { filter, skip, Subscription } from "rxjs"; + +@Injectable({ + providedIn: "root", +}) +export class VaultPopupScrollPositionService { + private router = inject(Router); + + /** Path of the vault screen */ + private readonly vaultPath = "/tabs/vault"; + + /** Current scroll position relative to the top of the viewport. */ + private scrollPosition: number | null = null; + + /** Subscription associated with the virtual scroll element. */ + private scrollSubscription: Subscription | null = null; + + constructor() { + this.router.events + .pipe( + takeUntilDestroyed(), + filter((event): event is NavigationEnd => event instanceof NavigationEnd), + ) + .subscribe((event) => { + this.resetListenerForNavigation(event); + }); + } + + /** Scrolls the user to the stored scroll position and starts tracking scroll of the page. */ + start(virtualScrollElement: CdkVirtualScrollableElement) { + if (this.hasScrollPosition()) { + // Use `setTimeout` to scroll after rendering is complete + setTimeout(() => { + virtualScrollElement.scrollTo({ top: this.scrollPosition!, behavior: "instant" }); + }); + } + + this.scrollSubscription?.unsubscribe(); + + // Skip the first scroll event to avoid settings the scroll from the above `scrollTo` call + this.scrollSubscription = virtualScrollElement + ?.elementScrolled() + .pipe(skip(1)) + .subscribe(() => { + const offset = virtualScrollElement.measureScrollOffset("top"); + this.scrollPosition = offset; + }); + } + + /** Stops the scroll listener from updating the stored location. */ + stop(reset?: true) { + this.scrollSubscription?.unsubscribe(); + this.scrollSubscription = null; + + if (reset) { + this.scrollPosition = null; + } + } + + /** Returns true when a scroll position has been stored. */ + hasScrollPosition() { + return this.scrollPosition !== null; + } + + /** Conditionally resets the scroll listeners based on the ending path of the navigation */ + private resetListenerForNavigation(event: NavigationEnd): void { + // The vault page is the target of the scroll listener, return early + if (event.url === this.vaultPath) { + return; + } + + // For all other tab pages reset the scroll position + if (event.url.startsWith("/tabs/")) { + this.stop(true); + } + } +} diff --git a/apps/browser/src/vault/popup/services/vault-popup-section.service.ts b/apps/browser/src/vault/popup/services/vault-popup-section.service.ts new file mode 100644 index 00000000000..ed641e0cdf7 --- /dev/null +++ b/apps/browser/src/vault/popup/services/vault-popup-section.service.ts @@ -0,0 +1,129 @@ +import { computed, effect, inject, Injectable, signal, Signal } from "@angular/core"; +import { toSignal } from "@angular/core/rxjs-interop"; +import { map } from "rxjs"; + +import { + KeyDefinition, + StateProvider, + VAULT_SETTINGS_DISK, +} from "@bitwarden/common/platform/state"; + +import { VaultPopupItemsService } from "./vault-popup-items.service"; + +export type PopupSectionOpen = { + favorites: boolean; + allItems: boolean; +}; + +const SECTION_OPEN_KEY = new KeyDefinition(VAULT_SETTINGS_DISK, "sectionOpen", { + deserializer: (obj) => obj, +}); + +const INITIAL_OPEN: PopupSectionOpen = { + favorites: true, + allItems: true, +}; + +@Injectable({ + providedIn: "root", +}) +export class VaultPopupSectionService { + private vaultPopupItemsService = inject(VaultPopupItemsService); + private stateProvider = inject(StateProvider); + + private hasFilterOrSearchApplied = toSignal( + this.vaultPopupItemsService.hasFilterApplied$.pipe(map((hasFilter) => hasFilter)), + ); + + /** + * Used to change the open/close state without persisting it to the local disk. Reflects + * application-applied overrides. + * `null` means there is no current override + */ + private temporaryStateOverride = signal | null>(null); + + constructor() { + effect( + () => { + /** + * auto-open all sections when search or filter is applied, and remove + * override when search or filter is removed + */ + if (this.hasFilterOrSearchApplied()) { + this.temporaryStateOverride.set(INITIAL_OPEN); + } else { + this.temporaryStateOverride.set(null); + } + }, + { + allowSignalWrites: true, + }, + ); + } + + /** + * Stored disk state for the open/close state of the sections. Will be `null` if user has never + * opened/closed a section + */ + private sectionOpenStateProvider = this.stateProvider.getGlobal(SECTION_OPEN_KEY); + + /** + * Stored disk state for the open/close state of the sections, with an initial value provided + * if the stored disk state does not yet exist. + */ + private sectionOpenStoredState = toSignal( + this.sectionOpenStateProvider.state$.pipe(map((sectionOpen) => sectionOpen ?? INITIAL_OPEN)), + // Indicates that the state value is loading + { initialValue: null }, + ); + + /** + * Indicates the current open/close display state of each section, accounting for temporary + * non-persisted overrides. + */ + sectionOpenDisplayState: Signal> = computed(() => ({ + ...this.sectionOpenStoredState(), + ...this.temporaryStateOverride(), + })); + + /** + * Retrieve the open/close display state for a given section. + * + * @param sectionKey section key + */ + getOpenDisplayStateForSection(sectionKey: keyof PopupSectionOpen): Signal { + return computed(() => this.sectionOpenDisplayState()?.[sectionKey]); + } + + /** + * Updates the stored open/close state of a given section. Should be called only when a user action + * is taken directly to change the open/close state. + * + * Removes any current temporary override for the given section, as direct user action should + * supersede any application-applied overrides. + * + * @param sectionKey section key + */ + async updateSectionOpenStoredState( + sectionKey: keyof PopupSectionOpen, + open: boolean, + ): Promise { + await this.sectionOpenStateProvider.update((currentState) => { + return { + ...(currentState ?? INITIAL_OPEN), + [sectionKey]: open, + }; + }); + + this.temporaryStateOverride.update((prev) => { + if (prev !== null) { + return { + ...prev, + [sectionKey]: open, + }; + } + + return prev; + }); + } +} diff --git a/apps/browser/src/vault/popup/services/vault-ui-onboarding.service.ts b/apps/browser/src/vault/popup/services/vault-ui-onboarding.service.ts deleted file mode 100644 index f50d6ebc236..00000000000 --- a/apps/browser/src/vault/popup/services/vault-ui-onboarding.service.ts +++ /dev/null @@ -1,89 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Injectable } from "@angular/core"; -import { firstValueFrom, map } from "rxjs"; - -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { - GlobalState, - KeyDefinition, - StateProvider, - VAULT_BROWSER_UI_ONBOARDING, -} from "@bitwarden/common/platform/state"; -import { DialogService } from "@bitwarden/components"; - -import { VaultUiOnboardingComponent } from "../components/vault-v2/vault-ui-onboarding/vault-ui-onboarding.component"; - -// Key definition for the Vault UI onboarding state. -// This key is used to store the state of the new UI information dialog. -export const GLOBAL_VAULT_UI_ONBOARDING = new KeyDefinition( - VAULT_BROWSER_UI_ONBOARDING, - "dialogState", - { - deserializer: (obj) => obj, - }, -); - -@Injectable() -export class VaultUiOnboardingService { - private onboardingUiReleaseDate = new Date("2024-12-10"); - - private vaultUiOnboardingState: GlobalState = this.stateProvider.getGlobal( - GLOBAL_VAULT_UI_ONBOARDING, - ); - - private readonly vaultUiOnboardingState$ = this.vaultUiOnboardingState.state$.pipe( - map((x) => x ?? false), - ); - - constructor( - private stateProvider: StateProvider, - private dialogService: DialogService, - private apiService: ApiService, - ) {} - - /** - * Checks whether the onboarding dialog should be shown and opens it if necessary. - * The dialog is shown if the user has not previously viewed it and is not a new account. - */ - async showOnboardingDialog(): Promise { - const hasViewedDialog = await this.getVaultUiOnboardingState(); - - if (!hasViewedDialog && !(await this.isNewAccount())) { - await this.openVaultUiOnboardingDialog(); - } - } - - private async openVaultUiOnboardingDialog(): Promise { - const dialogRef = VaultUiOnboardingComponent.open(this.dialogService); - - const result = firstValueFrom(dialogRef.closed); - - // Update the onboarding state when the dialog is closed - await this.setVaultUiOnboardingState(true); - - return result; - } - - private async isNewAccount(): Promise { - const userProfile = await this.apiService.getProfile(); - const profileCreationDate = new Date(userProfile.creationDate); - return profileCreationDate > this.onboardingUiReleaseDate; - } - - /** - * Updates and saves the state indicating whether the user has viewed - * the new UI onboarding information dialog. - */ - private async setVaultUiOnboardingState(value: boolean): Promise { - await this.vaultUiOnboardingState.update(() => value); - } - - /** - * Retrieves the current state indicating whether the user has viewed - * the new UI onboarding information dialog.s - */ - private async getVaultUiOnboardingState(): Promise { - return await firstValueFrom(this.vaultUiOnboardingState$); - } -} 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 3a05d239592..4f7f2757e0e 100644 --- a/apps/browser/src/vault/popup/settings/appearance-v2.component.html +++ b/apps/browser/src/vault/popup/settings/appearance-v2.component.html @@ -31,25 +31,32 @@ > - - - {{ "showQuickCopyActions" | i18n }} - - {{ "showNumberOfAutofillSuggestions" | i18n }} - - - {{ "enableFavicon" | i18n }} - - {{ "showAnimations" | i18n }} +

{{ "vaultCustomization" | i18n }}

+ + + + {{ "enableFavicon" | i18n }} + + + + {{ "showQuickCopyActions" | i18n }} + + + + + {{ "clickToAutofill" | i18n }} + + + diff --git a/apps/browser/src/vault/popup/settings/appearance-v2.component.spec.ts b/apps/browser/src/vault/popup/settings/appearance-v2.component.spec.ts index bca83a2fba0..30715ebaedf 100644 --- a/apps/browser/src/vault/popup/settings/appearance-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/settings/appearance-v2.component.spec.ts @@ -12,6 +12,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ThemeType } from "@bitwarden/common/platform/enums"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; +import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; import { PopupCompactModeService } from "../../../platform/popup/layout/popup-compact-mode.service"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; @@ -36,7 +37,9 @@ class MockPopupHeaderComponent { selector: "popup-page", template: ``, }) -class MockPopupPageComponent {} +class MockPopupPageComponent { + @Input() loading: boolean; +} describe("AppearanceV2Component", () => { let component: AppearanceV2Component; @@ -44,16 +47,18 @@ describe("AppearanceV2Component", () => { const showFavicons$ = new BehaviorSubject(true); const enableBadgeCounter$ = new BehaviorSubject(true); - const selectedTheme$ = new BehaviorSubject(ThemeType.Nord); + const selectedTheme$ = new BehaviorSubject(ThemeType.Light); const enableRoutingAnimation$ = new BehaviorSubject(true); const enableCompactMode$ = new BehaviorSubject(false); const showQuickCopyActions$ = new BehaviorSubject(false); + const clickItemsToAutofillVaultView$ = new BehaviorSubject(false); const setSelectedTheme = jest.fn().mockResolvedValue(undefined); const setShowFavicons = jest.fn().mockResolvedValue(undefined); const setEnableBadgeCounter = jest.fn().mockResolvedValue(undefined); const setEnableRoutingAnimation = jest.fn().mockResolvedValue(undefined); const setEnableCompactMode = jest.fn().mockResolvedValue(undefined); const setShowQuickCopyActions = jest.fn().mockResolvedValue(undefined); + const setClickItemsToAutofillVaultView = jest.fn().mockResolvedValue(undefined); const mockWidthService: Partial = { width$: new BehaviorSubject("default"), @@ -98,6 +103,13 @@ describe("AppearanceV2Component", () => { provide: PopupSizeService, useValue: mockWidthService, }, + { + provide: VaultSettingsService, + useValue: { + clickItemsToAutofillVaultView$, + setClickItemsToAutofillVaultView, + }, + }, ], }) .overrideComponent(AppearanceV2Component, { @@ -115,15 +127,19 @@ describe("AppearanceV2Component", () => { fixture.detectChanges(); }); - it("populates the form with the user's current settings", () => { + it("populates the form with the user's current settings", async () => { + fixture.detectChanges(); + await fixture.whenStable(); + fixture.detectChanges(); expect(component.appearanceForm.value).toEqual({ enableAnimations: true, enableFavicon: true, enableBadgeCounter: true, - theme: ThemeType.Nord, + theme: ThemeType.Light, enableCompactMode: false, showQuickCopyActions: false, width: "default", + clickItemsToAutofillVaultView: false, }); }); @@ -151,5 +167,29 @@ describe("AppearanceV2Component", () => { expect(setEnableRoutingAnimation).toHaveBeenCalledWith(false); }); + + it("updates the compact mode setting", () => { + component.appearanceForm.controls.enableCompactMode.setValue(true); + + expect(setEnableCompactMode).toHaveBeenCalledWith(true); + }); + + it("updates the quick copy actions setting", () => { + component.appearanceForm.controls.showQuickCopyActions.setValue(true); + + expect(setShowQuickCopyActions).toHaveBeenCalledWith(true); + }); + + it("updates the width setting", () => { + component.appearanceForm.controls.width.setValue("wide"); + + expect(mockWidthService.setWidth).toHaveBeenCalledWith("wide"); + }); + + it("updates the click items to autofill vault view setting", () => { + component.appearanceForm.controls.clickItemsToAutofillVaultView.setValue(true); + + expect(setClickItemsToAutofillVaultView).toHaveBeenCalledWith(true); + }); }); }); diff --git a/apps/browser/src/vault/popup/settings/appearance-v2.component.ts b/apps/browser/src/vault/popup/settings/appearance-v2.component.ts index 7f300a508a6..1462a2d7ab4 100644 --- a/apps/browser/src/vault/popup/settings/appearance-v2.component.ts +++ b/apps/browser/src/vault/popup/settings/appearance-v2.component.ts @@ -12,13 +12,18 @@ import { DomainSettingsService } from "@bitwarden/common/autofill/services/domai import { AnimationControlService } from "@bitwarden/common/platform/abstractions/animation-control.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { ThemeType } from "@bitwarden/common/platform/enums"; +import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; -import { BadgeModule, CheckboxModule, Option } from "@bitwarden/components"; +import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; +import { + BadgeModule, + CardComponent, + CheckboxModule, + FormFieldModule, + Option, + SelectModule, +} from "@bitwarden/components"; -import { CardComponent } from "../../../../../../libs/components/src/card/card.component"; -import { FormFieldModule } from "../../../../../../libs/components/src/form-field/form-field.module"; -import { SelectModule } from "../../../../../../libs/components/src/select/select.module"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; import { PopupCompactModeService } from "../../../platform/popup/layout/popup-compact-mode.service"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; @@ -55,18 +60,19 @@ export class AppearanceV2Component implements OnInit { appearanceForm = this.formBuilder.group({ enableFavicon: false, enableBadgeCounter: true, - theme: ThemeType.System, + theme: ThemeTypes.System as Theme, enableAnimations: true, enableCompactMode: false, showQuickCopyActions: false, width: "default" as PopupWidthOption, + clickItemsToAutofillVaultView: false, }); /** To avoid flashes of inaccurate values, only show the form after the entire form is populated. */ formLoading = true; /** Available theme options */ - themeOptions: { name: string; value: ThemeType }[]; + themeOptions: { name: string; value: Theme }[]; /** Available width options */ protected readonly widthOptions: Option[] = [ @@ -84,11 +90,12 @@ export class AppearanceV2Component implements OnInit { private destroyRef: DestroyRef, private animationControlService: AnimationControlService, i18nService: I18nService, + private vaultSettingsService: VaultSettingsService, ) { this.themeOptions = [ - { name: i18nService.t("systemDefault"), value: ThemeType.System }, - { name: i18nService.t("light"), value: ThemeType.Light }, - { name: i18nService.t("dark"), value: ThemeType.Dark }, + { name: i18nService.t("systemDefault"), value: ThemeTypes.System }, + { name: i18nService.t("light"), value: ThemeTypes.Light }, + { name: i18nService.t("dark"), value: ThemeTypes.Dark }, ]; } @@ -104,6 +111,9 @@ export class AppearanceV2Component implements OnInit { this.copyButtonsService.showQuickCopyActions$, ); const width = await firstValueFrom(this.popupSizeService.width$); + const clickItemsToAutofillVaultView = await firstValueFrom( + this.vaultSettingsService.clickItemsToAutofillVaultView$, + ); // Set initial values for the form this.appearanceForm.setValue({ @@ -114,6 +124,7 @@ export class AppearanceV2Component implements OnInit { enableCompactMode, showQuickCopyActions, width, + clickItemsToAutofillVaultView, }); this.formLoading = false; @@ -159,6 +170,16 @@ export class AppearanceV2Component implements OnInit { .subscribe((width) => { void this.updateWidth(width); }); + + this.appearanceForm.controls.clickItemsToAutofillVaultView.valueChanges + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((clickItemsToAutofillVaultView) => { + void this.updateClickItemsToAutofillVaultView(clickItemsToAutofillVaultView); + }); + } + + async updateClickItemsToAutofillVaultView(clickItemsToAutofillVaultView: boolean) { + await this.vaultSettingsService.setClickItemsToAutofillVaultView(clickItemsToAutofillVaultView); } async updateFavicon(enableFavicon: boolean) { @@ -170,7 +191,7 @@ export class AppearanceV2Component implements OnInit { this.messagingService.send("bgUpdateContextMenu"); } - async saveTheme(newTheme: ThemeType) { + async saveTheme(newTheme: Theme) { await this.themeStateService.setSelectedTheme(newTheme); } diff --git a/apps/browser/src/vault/popup/settings/folders-v2.component.html b/apps/browser/src/vault/popup/settings/folders-v2.component.html index 21e00757a29..35a0fbec0a9 100644 --- a/apps/browser/src/vault/popup/settings/folders-v2.component.html +++ b/apps/browser/src/vault/popup/settings/folders-v2.component.html @@ -19,7 +19,7 @@ slot="end" type="button" (click)="openAddEditFolderDialog(folder)" - [appA11yTitle]="'editFolder' | i18n" + [appA11yTitle]="'editFolderWithName' | i18n: folder.name" bitIconButton="bwi-pencil-square" class="tw-self-end" data-testid="edit-folder-button" diff --git a/apps/browser/src/vault/popup/settings/folders-v2.component.spec.ts b/apps/browser/src/vault/popup/settings/folders-v2.component.spec.ts index 9c202e26fef..6689f5a6c6d 100644 --- a/apps/browser/src/vault/popup/settings/folders-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/settings/folders-v2.component.spec.ts @@ -14,10 +14,10 @@ import { UserId } from "@bitwarden/common/types/guid"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { DialogService } from "@bitwarden/components"; +import { AddEditFolderDialogComponent } from "@bitwarden/vault"; import { PopupFooterComponent } from "../../../platform/popup/layout/popup-footer.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; -import { AddEditFolderDialogComponent } from "../components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component"; import { FoldersV2Component } from "./folders-v2.component"; @@ -27,8 +27,8 @@ import { FoldersV2Component } from "./folders-v2.component"; template: ``, }) class MockPopupHeaderComponent { - @Input() pageTitle: string; - @Input() backAction: () => void; + @Input() pageTitle: string = ""; + @Input() backAction: () => void = () => {}; } @Component({ @@ -37,14 +37,15 @@ class MockPopupHeaderComponent { template: ``, }) class MockPopupFooterComponent { - @Input() pageTitle: string; + @Input() pageTitle: string = ""; } describe("FoldersV2Component", () => { let component: FoldersV2Component; let fixture: ComponentFixture; const folderViews$ = new BehaviorSubject([]); - const open = jest.fn(); + const open = jest.spyOn(AddEditFolderDialogComponent, "open"); + const mockDialogService = { open: jest.fn() }; beforeEach(async () => { open.mockClear(); @@ -68,7 +69,7 @@ describe("FoldersV2Component", () => { imports: [MockPopupHeaderComponent, MockPopupFooterComponent], }, }) - .overrideProvider(DialogService, { useValue: { open } }) + .overrideProvider(DialogService, { useValue: mockDialogService }) .compileComponents(); fixture = TestBed.createComponent(FoldersV2Component); @@ -101,9 +102,7 @@ describe("FoldersV2Component", () => { editButton.triggerEventHandler("click"); - expect(open).toHaveBeenCalledWith(AddEditFolderDialogComponent, { - data: { editFolderConfig: { folder } }, - }); + expect(open).toHaveBeenCalledWith(mockDialogService, { editFolderConfig: { folder } }); }); it("opens add dialog for new folder when there are no folders", () => { @@ -114,6 +113,6 @@ describe("FoldersV2Component", () => { addButton.triggerEventHandler("click"); - expect(open).toHaveBeenCalledWith(AddEditFolderDialogComponent, { data: {} }); + expect(open).toHaveBeenCalledWith(mockDialogService, {}); }); }); diff --git a/apps/browser/src/vault/popup/settings/folders-v2.component.ts b/apps/browser/src/vault/popup/settings/folders-v2.component.ts index b1db949f2ee..f71374e5305 100644 --- a/apps/browser/src/vault/popup/settings/folders-v2.component.ts +++ b/apps/browser/src/vault/popup/settings/folders-v2.component.ts @@ -12,19 +12,14 @@ import { ButtonModule, DialogService, IconButtonModule, + ItemModule, + NoItemsModule, } from "@bitwarden/components"; -import { VaultIcons } from "@bitwarden/vault"; +import { AddEditFolderDialogComponent, VaultIcons } from "@bitwarden/vault"; -import { ItemGroupComponent } from "../../../../../../libs/components/src/item/item-group.component"; -import { ItemModule } from "../../../../../../libs/components/src/item/item.module"; -import { NoItemsModule } from "../../../../../../libs/components/src/no-items/no-items.module"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; -import { - AddEditFolderDialogComponent, - AddEditFolderDialogData, -} from "../components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component"; @Component({ standalone: true, @@ -36,7 +31,6 @@ import { PopupPageComponent, PopupHeaderComponent, ItemModule, - ItemGroupComponent, NoItemsModule, IconButtonModule, ButtonModule, @@ -72,8 +66,6 @@ export class FoldersV2Component { // If a folder is provided, the edit variant should be shown const editFolderConfig = folder ? { folder } : undefined; - this.dialogService.open(AddEditFolderDialogComponent, { - data: { editFolderConfig }, - }); + AddEditFolderDialogComponent.open(this.dialogService, { editFolderConfig }); } } diff --git a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.html b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.html index dcbda9fd96a..c77ac4f9755 100644 --- a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.html +++ b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.html @@ -31,7 +31,7 @@ > {{ cipher.subTitle }} - + - - + + diff --git a/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift b/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift index dbaa8517086..5568b2e75db 100644 --- a/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift +++ b/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift @@ -17,17 +17,56 @@ class CredentialProviderViewController: ASCredentialProviderViewController { // // If instead I make this a static, the deinit gets called correctly after each request. // I think we still might want a static regardless, to be able to reuse the connection if possible. - static let client: MacOsProviderClient = { - let instance = MacOsProviderClient.connect() - // setup code - return instance - }() + let client: MacOsProviderClient = { + let logger = Logger(subsystem: "com.bitwarden.desktop.autofill-extension", category: "credential-provider") + + // Check if the Electron app is running + let workspace = NSWorkspace.shared + let isRunning = workspace.runningApplications.contains { app in + app.bundleIdentifier == "com.bitwarden.desktop" + } + + if !isRunning { + logger.log("[autofill-extension] Bitwarden Desktop not running, attempting to launch") + + // Try to launch the app + if let appURL = workspace.urlForApplication(withBundleIdentifier: "com.bitwarden.desktop") { + let semaphore = DispatchSemaphore(value: 0) + + workspace.openApplication(at: appURL, + configuration: NSWorkspace.OpenConfiguration()) { app, error in + if let error = error { + logger.error("[autofill-extension] Failed to launch Bitwarden Desktop: \(error.localizedDescription)") + } else if let app = app { + logger.log("[autofill-extension] Successfully launched Bitwarden Desktop") + } else { + logger.error("[autofill-extension] Failed to launch Bitwarden Desktop: unknown error") + } + semaphore.signal() + } + + // Wait for launch completion with timeout + _ = semaphore.wait(timeout: .now() + 5.0) + + // Add a small delay to allow for initialization + Thread.sleep(forTimeInterval: 1.0) + } else { + logger.error("[autofill-extension] Could not find Bitwarden Desktop app") + } + } else { + logger.log("[autofill-extension] Bitwarden Desktop is running") + } + + logger.log("[autofill-extension] Connecting to Bitwarden over IPC") + + return MacOsProviderClient.connect() + }() init() { logger = Logger(subsystem: "com.bitwarden.desktop.autofill-extension", category: "credential-provider") logger.log("[autofill-extension] initializing extension") - + super.init(nibName: nil, bundle: nil) } @@ -43,40 +82,35 @@ class CredentialProviderViewController: ASCredentialProviderViewController { @IBAction func cancel(_ sender: AnyObject?) { self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code: ASExtensionError.userCanceled.rawValue)) } - + @IBAction func passwordSelected(_ sender: AnyObject?) { let passwordCredential = ASPasswordCredential(user: "j_appleseed", password: "apple1234") self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil) } - /* - Implement this method if your extension supports showing credentials in the QuickType bar. - When the user selects a credential from your app, this method will be called with the - ASPasswordCredentialIdentity your app has previously saved to the ASCredentialIdentityStore. - Provide the password by completing the extension request with the associated ASPasswordCredential. - If using the credential would require showing custom UI for authenticating the user, cancel - the request with error code ASExtensionError.userInteractionRequired. - - */ - - // Deprecated - override func provideCredentialWithoutUserInteraction(for credentialIdentity: ASPasswordCredentialIdentity) { - logger.log("[autofill-extension] provideCredentialWithoutUserInteraction called \(credentialIdentity)") - logger.log("[autofill-extension] user \(credentialIdentity.user)") - logger.log("[autofill-extension] id \(credentialIdentity.recordIdentifier ?? "")") - logger.log("[autofill-extension] sid \(credentialIdentity.serviceIdentifier.identifier)") - logger.log("[autofill-extension] sidt \(credentialIdentity.serviceIdentifier.type.rawValue)") + private func getWindowPosition() -> Position { + let frame = self.view.window?.frame ?? .zero + let screenHeight = NSScreen.main?.frame.height ?? 0 -// let databaseIsUnlocked = true -// if (databaseIsUnlocked) { - let passwordCredential = ASPasswordCredential(user: credentialIdentity.user, password: "example1234") - self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil) -// } else { -// self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code:ASExtensionError.userInteractionRequired.rawValue)) -// } + // frame.width and frame.height is always 0. Estimating works OK for now. + let estimatedWidth:CGFloat = 400; + let estimatedHeight:CGFloat = 200; + let centerX = Int32(round(frame.origin.x + estimatedWidth/2)) + let centerY = Int32(round(screenHeight - (frame.origin.y + estimatedHeight/2))) + + return Position(x: centerX, y:centerY) } + override func loadView() { + let view = NSView() + // Hide the native window since we only need the IPC connection + view.isHidden = true + self.view = view + } + override func provideCredentialWithoutUserInteraction(for credentialRequest: any ASCredentialRequest) { + let timeoutTimer = createTimer() + if let request = credentialRequest as? ASPasskeyCredentialRequest { if let passkeyIdentity = request.credentialIdentity as? ASPasskeyCredentialIdentity { @@ -84,11 +118,16 @@ class CredentialProviderViewController: ASCredentialProviderViewController { class CallbackImpl: PreparePasskeyAssertionCallback { let ctx: ASCredentialProviderExtensionContext - required init(_ ctx: ASCredentialProviderExtensionContext) { + let logger: Logger + let timeoutTimer: DispatchWorkItem + required init(_ ctx: ASCredentialProviderExtensionContext,_ logger: Logger, _ timeoutTimer: DispatchWorkItem) { self.ctx = ctx + self.logger = logger + self.timeoutTimer = timeoutTimer } func onComplete(credential: PasskeyAssertionResponse) { + self.timeoutTimer.cancel() ctx.completeAssertionRequest(using: ASPasskeyAssertionCredential( userHandle: credential.userHandle, relyingParty: credential.rpId, @@ -100,6 +139,8 @@ class CredentialProviderViewController: ASCredentialProviderViewController { } func onError(error: BitwardenError) { + logger.error("[autofill-extension] OnError called, cancelling the request \(error)") + self.timeoutTimer.cancel() ctx.cancelRequest(withError: error) } } @@ -113,55 +154,74 @@ class CredentialProviderViewController: ASCredentialProviderViewController { UserVerification.discouraged } - let req = PasskeyAssertionRequest( + let req = PasskeyAssertionWithoutUserInterfaceRequest( rpId: passkeyIdentity.relyingPartyIdentifier, credentialId: passkeyIdentity.credentialID, userName: passkeyIdentity.userName, userHandle: passkeyIdentity.userHandle, recordIdentifier: passkeyIdentity.recordIdentifier, clientDataHash: request.clientDataHash, - userVerification: userVerification + userVerification: userVerification, + windowXy: self.getWindowPosition() ) - CredentialProviderViewController.client.preparePasskeyAssertion(request: req, callback: CallbackImpl(self.extensionContext)) + self.client.preparePasskeyAssertionWithoutUserInterface(request: req, callback: CallbackImpl(self.extensionContext, self.logger, timeoutTimer)) return } } - if let request = credentialRequest as? ASPasswordCredentialRequest { - logger.log("[autofill-extension] provideCredentialWithoutUserInteraction2(password) called \(request)") - return; - } - + timeoutTimer.cancel() + logger.log("[autofill-extension] provideCredentialWithoutUserInteraction2 called wrong") self.extensionContext.cancelRequest(withError: BitwardenError.Internal("Invalid authentication request")) } - + /* Implement this method if provideCredentialWithoutUserInteraction(for:) can fail with ASExtensionError.userInteractionRequired. In this case, the system may present your extension's UI and call this method. Show appropriate UI for authenticating the user then provide the password by completing the extension request with the associated ASPasswordCredential. + + override func prepareInterfaceToProvideCredential(for credentialIdentity: ASPasswordCredentialIdentity) { + } + */ - override func prepareInterfaceToProvideCredential(for credentialIdentity: ASPasswordCredentialIdentity) { - } - */ + private func createTimer() -> DispatchWorkItem { + // Create a timer for 600 second timeout + let timeoutTimer = DispatchWorkItem { [weak self] in + guard let self = self else { return } + logger.log("[autofill-extension] The operation timed out after 600 seconds") + self.extensionContext.cancelRequest(withError: BitwardenError.Internal("The operation timed out")) + } + + // Schedule the timeout + DispatchQueue.main.asyncAfter(deadline: .now() + 600, execute: timeoutTimer) - - override func prepareInterfaceForExtensionConfiguration() { - logger.log("[autofill-extension] prepareInterfaceForExtensionConfiguration called") + return timeoutTimer } override func prepareInterface(forPasskeyRegistration registrationRequest: ASCredentialRequest) { + logger.log("[autofill-extension] prepareInterface") + let timeoutTimer = createTimer() + + if let request = registrationRequest as? ASPasskeyCredentialRequest { if let passkeyIdentity = registrationRequest.credentialIdentity as? ASPasskeyCredentialIdentity { + logger.log("[autofill-extension] prepareInterface(passkey) called \(request)") + class CallbackImpl: PreparePasskeyRegistrationCallback { let ctx: ASCredentialProviderExtensionContext - required init(_ ctx: ASCredentialProviderExtensionContext) { + let timeoutTimer: DispatchWorkItem + let logger: Logger + + required init(_ ctx: ASCredentialProviderExtensionContext, _ logger: Logger,_ timeoutTimer: DispatchWorkItem) { self.ctx = ctx + self.logger = logger + self.timeoutTimer = timeoutTimer } - + func onComplete(credential: PasskeyRegistrationResponse) { + self.timeoutTimer.cancel() ctx.completeRegistrationRequest(using: ASPasskeyRegistrationCredential( relyingParty: credential.rpId, clientDataHash: credential.clientDataHash, @@ -169,58 +229,99 @@ class CredentialProviderViewController: ASCredentialProviderViewController { attestationObject: credential.attestationObject )) } - + func onError(error: BitwardenError) { + logger.error("[autofill-extension] OnError called, cancelling the request \(error)") + self.timeoutTimer.cancel() ctx.cancelRequest(withError: error) } } let userVerification = switch request.userVerificationPreference { - case .preferred: - UserVerification.preferred - case .required: - UserVerification.required - default: - UserVerification.discouraged + case .preferred: + UserVerification.preferred + case .required: + UserVerification.required + default: + UserVerification.discouraged } - + let req = PasskeyRegistrationRequest( rpId: passkeyIdentity.relyingPartyIdentifier, userName: passkeyIdentity.userName, userHandle: passkeyIdentity.userHandle, clientDataHash: request.clientDataHash, userVerification: userVerification, - supportedAlgorithms: request.supportedAlgorithms.map{ Int32($0.rawValue) } + supportedAlgorithms: request.supportedAlgorithms.map{ Int32($0.rawValue) }, + windowXy: self.getWindowPosition() ) - CredentialProviderViewController.client.preparePasskeyRegistration(request: req, callback: CallbackImpl(self.extensionContext)) + logger.log("[autofill-extension] prepareInterface(passkey) calling preparePasskeyRegistration") + + self.client.preparePasskeyRegistration(request: req, callback: CallbackImpl(self.extensionContext, self.logger, timeoutTimer)) return } } - + + logger.log("[autofill-extension] We didn't get a passkey") + + timeoutTimer.cancel() // If we didn't get a passkey, return an error self.extensionContext.cancelRequest(withError: BitwardenError.Internal("Invalid registration request")) } - - /* - Prepare your UI to list available credentials for the user to choose from. The items in - 'serviceIdentifiers' describe the service the user is logging in to, so your extension can - prioritize the most relevant credentials in the list. - */ - override func prepareCredentialList(for serviceIdentifiers: [ASCredentialServiceIdentifier]) { - logger.log("[autofill-extension] prepareCredentialList for serviceIdentifiers: \(serviceIdentifiers.count)") - - for serviceIdentifier in serviceIdentifiers { - logger.log(" service: \(serviceIdentifier.identifier)") - } - } - + override func prepareCredentialList(for serviceIdentifiers: [ASCredentialServiceIdentifier], requestParameters: ASPasskeyCredentialRequestParameters) { logger.log("[autofill-extension] prepareCredentialList(passkey) for serviceIdentifiers: \(serviceIdentifiers.count)") - logger.log("request parameters: \(requestParameters.relyingPartyIdentifier)") - - for serviceIdentifier in serviceIdentifiers { - logger.log(" service: \(serviceIdentifier.identifier)") + + class CallbackImpl: PreparePasskeyAssertionCallback { + let ctx: ASCredentialProviderExtensionContext + let timeoutTimer: DispatchWorkItem + let logger: Logger + required init(_ ctx: ASCredentialProviderExtensionContext,_ logger: Logger, _ timeoutTimer: DispatchWorkItem) { + self.ctx = ctx + self.logger = logger + self.timeoutTimer = timeoutTimer + } + + func onComplete(credential: PasskeyAssertionResponse) { + self.timeoutTimer.cancel() + ctx.completeAssertionRequest(using: ASPasskeyAssertionCredential( + userHandle: credential.userHandle, + relyingParty: credential.rpId, + signature: credential.signature, + clientDataHash: credential.clientDataHash, + authenticatorData: credential.authenticatorData, + credentialID: credential.credentialId + )) + } + + func onError(error: BitwardenError) { + logger.error("[autofill-extension] OnError called, cancelling the request \(error)") + self.timeoutTimer.cancel() + ctx.cancelRequest(withError: error) + } } - } - + + let userVerification = switch requestParameters.userVerificationPreference { + case .preferred: + UserVerification.preferred + case .required: + UserVerification.required + default: + UserVerification.discouraged + } + + let req = PasskeyAssertionRequest( + rpId: requestParameters.relyingPartyIdentifier, + clientDataHash: requestParameters.clientDataHash, + userVerification: userVerification, + allowedCredentials: requestParameters.allowedCredentials, + windowXy: self.getWindowPosition() + //extensionInput: requestParameters.extensionInput, + ) + + let timeoutTimer = createTimer() + + self.client.preparePasskeyAssertion(request: req, callback: CallbackImpl(self.extensionContext, self.logger, timeoutTimer)) + return + } } diff --git a/apps/desktop/native-messaging-test-runner/.eslintrc.json b/apps/desktop/native-messaging-test-runner/.eslintrc.json deleted file mode 100644 index d5ba8f9d9ca..00000000000 --- a/apps/desktop/native-messaging-test-runner/.eslintrc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "rules": { - "no-console": "off" - } -} diff --git a/apps/desktop/native-messaging-test-runner/package-lock.json b/apps/desktop/native-messaging-test-runner/package-lock.json index 4bf939c8b41..02a5e850401 100644 --- a/apps/desktop/native-messaging-test-runner/package-lock.json +++ b/apps/desktop/native-messaging-test-runner/package-lock.json @@ -12,15 +12,13 @@ "@bitwarden/common": "file:../../../libs/common", "@bitwarden/node": "file:../../../libs/node", "module-alias": "2.2.3", - "node-ipc": "9.2.1", "ts-node": "10.9.2", - "uuid": "11.0.3", + "uuid": "11.0.5", "yargs": "17.7.2" }, "devDependencies": { - "@types/node": "22.10.2", - "@types/node-ipc": "9.2.3", - "typescript": "4.7.4" + "@types/node": "22.10.7", + "typescript": "5.4.2" } }, "../../../libs/common": { @@ -31,10 +29,7 @@ "../../../libs/node": { "name": "@bitwarden/node", "version": "0.0.0", - "license": "GPL-3.0", - "dependencies": { - "@bitwarden/common": "file:../common" - } + "license": "GPL-3.0" }, "node_modules/@bitwarden/common": { "resolved": "../../../libs/common", @@ -106,24 +101,14 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.10.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", - "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", + "version": "22.10.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz", + "integrity": "sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==", "license": "MIT", "dependencies": { "undici-types": "~6.20.0" } }, - "node_modules/@types/node-ipc": { - "version": "9.2.3", - "resolved": "https://registry.npmjs.org/@types/node-ipc/-/node-ipc-9.2.3.tgz", - "integrity": "sha512-/MvSiF71fYf3+zwqkh/zkVkZj1hl1Uobre9EMFy08mqfJNAmpR0vmPgOUdEIDVgifxHj6G1vYMPLSBLLxoDACQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/acorn": { "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", @@ -225,15 +210,6 @@ "node": ">=0.3.1" } }, - "node_modules/easy-stack": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/easy-stack/-/easy-stack-1.0.1.tgz", - "integrity": "sha512-wK2sCs4feiiJeFXn3zvY0p41mdU5VUgbgs1rNsc/y5ngFUijdWd+iIN8eoyuZHKB8xN6BL4PdWmzqFmxNg6V2w==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -249,15 +225,6 @@ "node": ">=6" } }, - "node_modules/event-pubsub": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/event-pubsub/-/event-pubsub-4.3.0.tgz", - "integrity": "sha512-z7IyloorXvKbFx9Bpie2+vMJKKx1fH1EN5yiTfp8CiLOTptSYy1g8H4yDpGlEdshL1PBiFtBHepF2cNsqeEeFQ==", - "license": "Unlicense", - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -276,27 +243,6 @@ "node": ">=8" } }, - "node_modules/js-message": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/js-message/-/js-message-1.0.7.tgz", - "integrity": "sha512-efJLHhLjIyKRewNS9EGZ4UpI8NguuL6fKkhRxVuMmrGV2xN/0APGdQYwLFky5w9naebSZ0OwAGp0G6/2Cg90rA==", - "license": "MIT", - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/js-queue": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/js-queue/-/js-queue-2.0.2.tgz", - "integrity": "sha512-pbKLsbCfi7kriM3s1J4DDCo7jQkI58zPLHi0heXPzPlj0hjUsm+FesPUbE0DSbIVIK503A36aUBoCN7eMFedkA==", - "license": "MIT", - "dependencies": { - "easy-stack": "^1.0.1" - }, - "engines": { - "node": ">=1.0.0" - } - }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -309,20 +255,6 @@ "integrity": "sha512-23g5BFj4zdQL/b6tor7Ji+QY4pEfNH784BMslY9Qb0UnJWRAt+lQGLYmRaM0KDBwIG23ffEBELhZDP2rhi9f/Q==", "license": "MIT" }, - "node_modules/node-ipc": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/node-ipc/-/node-ipc-9.2.1.tgz", - "integrity": "sha512-mJzaM6O3xHf9VT8BULvJSbdVbmHUKRNOH7zDDkCrA1/T+CVjq2WVIDfLt0azZRXpgArJtl3rtmEozrbXPZ9GaQ==", - "license": "MIT", - "dependencies": { - "event-pubsub": "4.3.0", - "js-message": "1.0.7", - "js-queue": "2.0.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -402,16 +334,16 @@ } }, "node_modules/typescript": { - "version": "4.7.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", - "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", + "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, "node_modules/undici-types": { @@ -421,9 +353,9 @@ "license": "MIT" }, "node_modules/uuid": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz", - "integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==", + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.5.tgz", + "integrity": "sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" diff --git a/apps/desktop/native-messaging-test-runner/package.json b/apps/desktop/native-messaging-test-runner/package.json index 6ab267be4a8..0df272c142f 100644 --- a/apps/desktop/native-messaging-test-runner/package.json +++ b/apps/desktop/native-messaging-test-runner/package.json @@ -17,15 +17,13 @@ "@bitwarden/common": "file:../../../libs/common", "@bitwarden/node": "file:../../../libs/node", "module-alias": "2.2.3", - "node-ipc": "9.2.1", "ts-node": "10.9.2", - "uuid": "11.0.3", + "uuid": "11.0.5", "yargs": "17.7.2" }, "devDependencies": { - "@types/node": "22.10.2", - "@types/node-ipc": "9.2.3", - "typescript": "4.7.4" + "@types/node": "22.10.7", + "typescript": "5.4.2" }, "_moduleAliases": { "@bitwarden/common": "dist/libs/common/src", diff --git a/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-create.ts b/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-create.ts index 2f8e61c68a4..8d2d734677a 100644 --- a/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-create.ts +++ b/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-create.ts @@ -7,6 +7,7 @@ import { hideBin } from "yargs/helpers"; import { NativeMessagingVersion } from "@bitwarden/common/enums"; +// eslint-disable-next-line no-restricted-imports import { CredentialCreatePayload } from "../../../src/models/native-messaging/encrypted-message-payloads/credential-create-payload"; import { LogUtils } from "../log-utils"; import NativeMessageService from "../native-message.service"; diff --git a/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-update.ts b/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-update.ts index 4f17bb5658a..93598bf9eef 100644 --- a/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-update.ts +++ b/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-update.ts @@ -7,6 +7,7 @@ import { hideBin } from "yargs/helpers"; import { NativeMessagingVersion } from "@bitwarden/common/enums"; +// eslint-disable-next-line no-restricted-imports import { CredentialUpdatePayload } from "../../../src/models/native-messaging/encrypted-message-payloads/credential-update-payload"; import { LogUtils } from "../log-utils"; import NativeMessageService from "../native-message.service"; diff --git a/apps/desktop/native-messaging-test-runner/src/ipc.service.ts b/apps/desktop/native-messaging-test-runner/src/ipc.service.ts index ee17e672a79..b02ff1a4225 100644 --- a/apps/desktop/native-messaging-test-runner/src/ipc.service.ts +++ b/apps/desktop/native-messaging-test-runner/src/ipc.service.ts @@ -1,20 +1,16 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { homedir } from "os"; - -import * as NodeIPC from "node-ipc"; +/* eslint-disable no-console */ +import { ChildProcess, spawn } from "child_process"; +import * as fs from "fs"; +import * as path from "path"; +// eslint-disable-next-line no-restricted-imports import { MessageCommon } from "../../src/models/native-messaging/message-common"; +// eslint-disable-next-line no-restricted-imports import { UnencryptedMessageResponse } from "../../src/models/native-messaging/unencrypted-message-response"; import Deferred from "./deferred"; import { race } from "./race"; -NodeIPC.config.id = "native-messaging-test-runner"; -NodeIPC.config.maxRetries = 0; -NodeIPC.config.silent = true; - -const DESKTOP_APP_PATH = `${homedir}/tmp/app.bitwarden`; const DEFAULT_MESSAGE_TIMEOUT = 10 * 1000; // 10 seconds export type MessageHandler = (MessageCommon) => void; @@ -39,6 +35,10 @@ export default class IPCService { // A set of deferred promises that are awaiting socket connection private awaitingConnection = new Set>(); + // The IPC desktop_proxy process + private process?: ChildProcess; + private processOutputBuffer = Buffer.alloc(0); + constructor( private socketName: string, private messageHandler: MessageHandler, @@ -69,47 +69,47 @@ export default class IPCService { private _connect() { this.connectionState = IPCConnectionState.Connecting; - NodeIPC.connectTo(this.socketName, DESKTOP_APP_PATH, () => { - // Process incoming message - this.getSocket().on("message", (message: any) => { - this.processMessage(message); - }); + const proxyPath = selectProxyPath(); + console.log(`[IPCService] connecting to proxy at ${proxyPath}`); - this.getSocket().on("error", (error: Error) => { - // Only makes sense as long as config.maxRetries stays set to 0. Otherwise this will be - // invoked multiple times each time a connection error happens - console.log("[IPCService] errored"); - console.log( - "\x1b[33m Please make sure the desktop app is running locally and 'Allow DuckDuckGo browser integration' setting is enabled \x1b[0m", - ); - this.awaitingConnection.forEach((deferred) => { - console.log(`rejecting: ${deferred}`); - deferred.reject(error); - }); - this.awaitingConnection.clear(); - }); + this.process = spawn(proxyPath, process.argv.slice(1), { + cwd: process.cwd(), + stdio: "pipe", + shell: false, + }); - this.getSocket().on("connect", () => { - console.log("[IPCService] connected"); - this.connectionState = IPCConnectionState.Connected; + this.process.stdout.on("data", (data: Buffer) => { + this.processIpcMessage(data); + }); - this.awaitingConnection.forEach((deferred) => { - deferred.resolve(null); - }); - this.awaitingConnection.clear(); - }); + this.process.stderr.on("data", (data: Buffer) => { + console.error(`proxy log: ${data}`); + }); - this.getSocket().on("disconnect", () => { - console.log("[IPCService] disconnected"); - this.connectionState = IPCConnectionState.Disconnected; + this.process.on("error", (error) => { + // Only makes sense as long as config.maxRetries stays set to 0. Otherwise this will be + // invoked multiple times each time a connection error happens + console.log("[IPCService] errored"); + console.log( + "\x1b[33m Please make sure the desktop app is running locally and 'Allow DuckDuckGo browser integration' setting is enabled \x1b[0m", + ); + this.awaitingConnection.forEach((deferred) => { + console.log(`rejecting: ${deferred}`); + deferred.reject(error); }); + this.awaitingConnection.clear(); + }); + + this.process.on("exit", () => { + console.log("[IPCService] disconnected"); + this.connectionState = IPCConnectionState.Disconnected; }); } disconnect() { console.log("[IPCService] disconnecting..."); if (this.connectionState !== IPCConnectionState.Disconnected) { - NodeIPC.disconnect(this.socketName); + this.process?.kill(); } } @@ -130,7 +130,7 @@ export default class IPCService { this.pendingMessages.set(message.messageId, deferred); - this.getSocket().emit("message", message); + this.sendIpcMessage(message); try { // Since we can not guarantee that a response message will ever be sent, we put a timeout @@ -148,8 +148,56 @@ export default class IPCService { } } - private getSocket() { - return NodeIPC.of[this.socketName]; + // As we're using the desktop_proxy to communicate with the native messaging directly, + // the messages need to follow Native Messaging Host protocol (uint32 size followed by message). + // https://developer.chrome.com/docs/extensions/develop/concepts/native-messaging#native-messaging-host-protocol + private sendIpcMessage(message: MessageCommon) { + const messageStr = JSON.stringify(message); + const buffer = Buffer.alloc(4 + messageStr.length); + buffer.writeUInt32LE(messageStr.length, 0); + buffer.write(messageStr, 4); + + this.process?.stdin.write(buffer); + } + + private processIpcMessage(data: Buffer) { + this.processOutputBuffer = Buffer.concat([this.processOutputBuffer, data]); + + // We might receive more than one IPC message per data event, so we need to process them all + // We continue as long as we have at least 4 + 1 bytes in the buffer, where the first 4 bytes + // represent the message length and the 5th byte is the message + while (this.processOutputBuffer.length > 4) { + // Read the message length and ensure we have the full message + const msgLength = this.processOutputBuffer.readUInt32LE(0); + if (msgLength + 4 < this.processOutputBuffer.length) { + return; + } + + // Parse the message from the buffer + const messageStr = this.processOutputBuffer.subarray(4, msgLength + 4).toString(); + const message = JSON.parse(messageStr); + + // Store the remaining buffer, which is part of the next message + this.processOutputBuffer = this.processOutputBuffer.subarray(msgLength + 4); + + // Process the connect/disconnect messages separately + if (message?.command === "connected") { + console.log("[IPCService] connected"); + this.connectionState = IPCConnectionState.Connected; + + this.awaitingConnection.forEach((deferred) => { + deferred.resolve(null); + }); + this.awaitingConnection.clear(); + continue; + } else if (message?.command === "disconnected") { + console.log("[IPCService] disconnected"); + this.connectionState = IPCConnectionState.Disconnected; + continue; + } + + this.processMessage(message); + } } private processMessage(message: any) { @@ -169,3 +217,41 @@ export default class IPCService { } } } + +function selectProxyPath(): string { + const proxyExtension = process.platform === "win32" ? ".exe" : ""; + + // If the PROXY_PATH environment variable is set, use that + if (process.env.PROXY_PATH) { + if (!fs.existsSync(process.env.PROXY_PATH)) { + throw new Error(`PROXY_PATH is set to ${process.env.PROXY_PATH} but the file does not exist`); + } + return process.env.PROXY_PATH; + } + + // Otherwise try the debug build if present + const debugProxyPath = path.join( + __dirname, + "..", + "..", + "..", + "..", + "..", + "..", + "desktop_native", + "target", + "debug", + `desktop_proxy${proxyExtension}`, + ); + if (fs.existsSync(debugProxyPath)) { + return debugProxyPath; + } + + // On MacOS, try the release build (sandboxed) + const macReleaseProxyPath = `/Applications/Bitwarden.app/Contents/MacOS/desktop_proxy${proxyExtension}`; + if (process.platform === "darwin" && fs.existsSync(macReleaseProxyPath)) { + return macReleaseProxyPath; + } + + throw new Error("Could not find the desktop_proxy executable"); +} diff --git a/apps/desktop/native-messaging-test-runner/src/native-message.service.ts b/apps/desktop/native-messaging-test-runner/src/native-message.service.ts index cd84504c630..c01d581afe8 100644 --- a/apps/desktop/native-messaging-test-runner/src/native-message.service.ts +++ b/apps/desktop/native-messaging-test-runner/src/native-message.service.ts @@ -1,21 +1,30 @@ +/* eslint-disable no-console */ import "module-alias/register"; import { v4 as uuidv4 } from "uuid"; +import { EncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/encrypt.service.implementation"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; -import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation"; import { NodeCryptoFunctionService } from "@bitwarden/node/services/node-crypto-function.service"; +// eslint-disable-next-line no-restricted-imports import { DecryptedCommandData } from "../../src/models/native-messaging/decrypted-command-data"; +// eslint-disable-next-line no-restricted-imports import { EncryptedMessage } from "../../src/models/native-messaging/encrypted-message"; +// eslint-disable-next-line no-restricted-imports import { CredentialCreatePayload } from "../../src/models/native-messaging/encrypted-message-payloads/credential-create-payload"; +// eslint-disable-next-line no-restricted-imports import { CredentialUpdatePayload } from "../../src/models/native-messaging/encrypted-message-payloads/credential-update-payload"; +// eslint-disable-next-line no-restricted-imports import { EncryptedMessageResponse } from "../../src/models/native-messaging/encrypted-message-response"; +// eslint-disable-next-line no-restricted-imports import { MessageCommon } from "../../src/models/native-messaging/message-common"; +// eslint-disable-next-line no-restricted-imports import { UnencryptedMessage } from "../../src/models/native-messaging/unencrypted-message"; +// eslint-disable-next-line no-restricted-imports import { UnencryptedMessageResponse } from "../../src/models/native-messaging/unencrypted-message-response"; import IPCService, { IPCOptions } from "./ipc.service"; diff --git a/apps/desktop/native-messaging-test-runner/tsconfig.json b/apps/desktop/native-messaging-test-runner/tsconfig.json index 72a28de3f7a..59c7040e509 100644 --- a/apps/desktop/native-messaging-test-runner/tsconfig.json +++ b/apps/desktop/native-messaging-test-runner/tsconfig.json @@ -5,12 +5,17 @@ "target": "es6", "module": "CommonJS", "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, "sourceMap": false, "declaration": false, "paths": { "@src/*": ["src/*"], - "@bitwarden/node/*": ["../../../libs/node/src/*"], - "@bitwarden/common/*": ["../../../libs/common/src/*"] + "@bitwarden/admin-console/*": ["../../../libs/admin-console/src/*"], + "@bitwarden/auth/*": ["../../../libs/auth/src/*"], + "@bitwarden/common/*": ["../../../libs/common/src/*"], + "@bitwarden/key-management": ["../../../libs/key-management/src/"], + "@bitwarden/node/*": ["../../../libs/node/src/*"] }, "plugins": [ { diff --git a/apps/desktop/package.json b/apps/desktop/package.json index c55f39d1b2a..d4fe93d05b9 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.1.0", + "version": "2025.3.0", "keywords": [ "bitwarden", "password", @@ -35,7 +35,7 @@ "clean:dist": "rimraf ./dist", "pack:dir": "npm run clean:dist && electron-builder --dir -p never", "pack:lin:flatpak": "npm run clean:dist && electron-builder --dir -p never && flatpak-builder --repo=build/.repo build/.flatpak ./resources/com.bitwarden.desktop.devel.yaml --install-deps-from=flathub --force-clean && flatpak build-bundle ./build/.repo/ ./dist/com.bitwarden.desktop.flatpak com.bitwarden.desktop", - "pack:lin": "npm run clean:dist && electron-builder --linux --x64 -p never && export SNAP_FILE=$(realpath ./dist/bitwarden_*.snap) && unsquashfs -d ./dist/tmp-snap/ $SNAP_FILE && mkdir -p ./dist/tmp-snap/meta/polkit/ && cp ./resources/com.bitwarden.desktop.policy ./dist/tmp-snap/meta/polkit/polkit.com.bitwarden.desktop.policy && rm $SNAP_FILE && mksquashfs ./dist/tmp-snap/ $SNAP_FILE -noappend -comp lzo -no-fragments && rm -rf ./dist/tmp-snap/", + "pack:lin": "npm run clean:dist && electron-builder --linux --x64 -p never && export SNAP_FILE=$(realpath ./dist/bitwarden_*.snap) && unsquashfs -d ./dist/tmp-snap/ $SNAP_FILE && mkdir -p ./dist/tmp-snap/meta/polkit/ && cp ./resources/com.bitwarden.desktop.policy ./dist/tmp-snap/meta/polkit/polkit.com.bitwarden.desktop.policy && rm $SNAP_FILE && snapcraft pack ./dist/tmp-snap/ && mv ./*.snap ./dist/ && rm -rf ./dist/tmp-snap/", "pack:mac": "npm run clean:dist && electron-builder --mac --universal -p never", "pack:mac:arm64": "npm run clean:dist && electron-builder --mac --arm64 -p never", "pack:mac:mas": "npm run clean:dist && electron-builder --mac mas --universal -p never", diff --git a/apps/desktop/postcss.config.js b/apps/desktop/postcss.config.js index c4513687e89..5657df3afcf 100644 --- a/apps/desktop/postcss.config.js +++ b/apps/desktop/postcss.config.js @@ -1,4 +1,9 @@ -/* eslint-disable no-undef */ +/* eslint-disable @typescript-eslint/no-require-imports */ module.exports = { - plugins: [require("tailwindcss"), require("autoprefixer"), require("postcss-nested")], + plugins: [ + require("postcss-import"), + require("postcss-nested"), + require("tailwindcss"), + require("autoprefixer"), + ], }; diff --git a/apps/desktop/resources/com.bitwarden.desktop.devel.yaml b/apps/desktop/resources/com.bitwarden.desktop.devel.yaml index 02f2474927e..3aeebfd809d 100644 --- a/apps/desktop/resources/com.bitwarden.desktop.devel.yaml +++ b/apps/desktop/resources/com.bitwarden.desktop.devel.yaml @@ -8,7 +8,6 @@ command: bitwarden.sh finish-args: - --share=ipc - --share=network - - --socket=wayland - --socket=x11 - --device=dri - --env=XDG_CURRENT_DESKTOP=Unity @@ -23,6 +22,7 @@ finish-args: - --talk-name=org.freedesktop.ScreenSaver - --system-talk-name=org.freedesktop.login1 - --filesystem=xdg-download + - --device=all modules: - name: bitwarden-desktop buildsystem: simple @@ -43,5 +43,4 @@ modules: commands: - ulimit -c 0 - export TMPDIR="$XDG_RUNTIME_DIR/app/$FLATPAK_ID" - - exec zypak-wrapper /app/bin/bitwarden-app --ozone-platform-hint=auto - --enable-features=WaylandWindowDecorations "$@" + - exec zypak-wrapper /app/bin/bitwarden-app "$@" diff --git a/apps/desktop/resources/entitlements.mas.plist b/apps/desktop/resources/entitlements.mas.plist index 0450111bebd..bb06ae10431 100644 --- a/apps/desktop/resources/entitlements.mas.plist +++ b/apps/desktop/resources/entitlements.mas.plist @@ -16,6 +16,8 @@ com.apple.security.files.user-selected.read-write + com.apple.security.device.usb + - -
-
-
- - -
-
- - -
-
- -
-
-
- -
-
-
-

{{ "newAroundHere" | i18n }}

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

{{ "loggingInAs" | i18n }} {{ loggedEmail }}

- {{ "notYou" | i18n }} -
-
-
-
- - - diff --git a/apps/desktop/src/auth/login/login-v1.component.ts b/apps/desktop/src/auth/login/login-v1.component.ts deleted file mode 100644 index 5b1a1c68d29..00000000000 --- a/apps/desktop/src/auth/login/login-v1.component.ts +++ /dev/null @@ -1,267 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, NgZone, OnDestroy, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; -import { FormBuilder } from "@angular/forms"; -import { ActivatedRoute, Router } from "@angular/router"; -import { Subject, takeUntil, tap } from "rxjs"; - -import { LoginComponentV1 as BaseLoginComponent } from "@bitwarden/angular/auth/components/login-v1.component"; -import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service"; -import { ModalService } from "@bitwarden/angular/services/modal.service"; -import { - LoginStrategyServiceAbstraction, - LoginEmailServiceAbstraction, - RegisterRouteService, -} from "@bitwarden/auth/common"; -import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; -import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; -import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { ToastService } from "@bitwarden/components"; -import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; - -import { EnvironmentComponent } from "../environment.component"; - -const BroadcasterSubscriptionId = "LoginComponent"; - -@Component({ - selector: "app-login", - templateUrl: "login-v1.component.html", -}) -export class LoginComponentV1 extends BaseLoginComponent implements OnInit, OnDestroy { - @ViewChild("environment", { read: ViewContainerRef, static: true }) - environmentModal: ViewContainerRef; - - protected componentDestroyed$: Subject = new Subject(); - webVaultHostname = ""; - - showingModal = false; - - private deferFocus: boolean = null; - - get loggedEmail() { - return this.formGroup.value.email; - } - - constructor( - devicesApiService: DevicesApiServiceAbstraction, - appIdService: AppIdService, - loginStrategyService: LoginStrategyServiceAbstraction, - router: Router, - i18nService: I18nService, - syncService: SyncService, - private modalService: ModalService, - platformUtilsService: PlatformUtilsService, - stateService: StateService, - environmentService: EnvironmentService, - passwordGenerationService: PasswordGenerationServiceAbstraction, - cryptoFunctionService: CryptoFunctionService, - private broadcasterService: BroadcasterService, - ngZone: NgZone, - private messagingService: MessagingService, - logService: LogService, - formBuilder: FormBuilder, - formValidationErrorService: FormValidationErrorsService, - route: ActivatedRoute, - loginEmailService: LoginEmailServiceAbstraction, - ssoLoginService: SsoLoginServiceAbstraction, - webAuthnLoginService: WebAuthnLoginServiceAbstraction, - registerRouteService: RegisterRouteService, - toastService: ToastService, - private configService: ConfigService, - ) { - super( - devicesApiService, - appIdService, - loginStrategyService, - router, - platformUtilsService, - i18nService, - stateService, - environmentService, - passwordGenerationService, - cryptoFunctionService, - logService, - ngZone, - formBuilder, - formValidationErrorService, - route, - loginEmailService, - ssoLoginService, - webAuthnLoginService, - registerRouteService, - toastService, - ); - this.onSuccessfulLogin = () => { - return syncService.fullSync(true); - }; - } - - async ngOnInit() { - this.listenForUnauthUiRefreshFlagChanges(); - - await super.ngOnInit(); - await this.getLoginWithDevice(this.loggedEmail); - this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => { - this.ngZone.run(() => { - switch (message.command) { - case "windowHidden": - this.onWindowHidden(); - break; - case "windowIsFocused": - if (this.deferFocus === null) { - this.deferFocus = !message.windowIsFocused; - if (!this.deferFocus) { - this.focusInput(); - } - } else if (this.deferFocus && message.windowIsFocused) { - this.focusInput(); - this.deferFocus = false; - } - break; - default: - } - }); - }); - this.messagingService.send("getWindowIsFocused"); - } - - ngOnDestroy() { - this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); - this.componentDestroyed$.next(); - this.componentDestroyed$.complete(); - } - - private listenForUnauthUiRefreshFlagChanges() { - this.configService - .getFeatureFlag$(FeatureFlag.UnauthenticatedExtensionUIRefresh) - .pipe( - tap(async (flag) => { - // If the flag is turned ON, we must force a reload to ensure the correct UI is shown - if (flag) { - const uniqueQueryParams = { - ...this.route.queryParams, - // adding a unique timestamp to the query params to force a reload - t: new Date().getTime().toString(), - }; - - await this.router.navigate(["/"], { - queryParams: uniqueQueryParams, - }); - } - }), - takeUntil(this.destroy$), - ) - .subscribe(); - } - - async settings() { - const [modal, childComponent] = await this.modalService.openViewRef( - EnvironmentComponent, - this.environmentModal, - ); - - modal.onShown.pipe(takeUntil(this.componentDestroyed$)).subscribe(() => { - this.showingModal = true; - }); - - modal.onClosed.pipe(takeUntil(this.componentDestroyed$)).subscribe(() => { - this.showingModal = false; - }); - - // eslint-disable-next-line rxjs/no-async-subscribe - childComponent.onSaved.pipe(takeUntil(this.componentDestroyed$)).subscribe(async () => { - modal.close(); - await this.getLoginWithDevice(this.loggedEmail); - }); - } - - onWindowHidden() { - this.showPassword = false; - } - - async continue() { - await super.validateEmail(); - if (!this.formGroup.controls.email.valid) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccured"), - message: this.i18nService.t("invalidEmail"), - }); - return; - } - this.focusInput(); - } - - async submit() { - if (!this.validatedEmail) { - return; - } - - await super.submit(); - if (this.captchaSiteKey) { - const content = document.getElementById("content") as HTMLDivElement; - content.setAttribute("style", "width:335px"); - } - } - - private focusInput() { - const email = this.loggedEmail; - document.getElementById(email == null || email === "" ? "email" : "masterPassword")?.focus(); - } - - async launchSsoBrowser(clientId: string, ssoRedirectUri: string) { - if (!ipc.platform.isAppImage && !ipc.platform.isSnapStore && !ipc.platform.isDev) { - return super.launchSsoBrowser(clientId, ssoRedirectUri); - } - - // Save off email for SSO - await this.ssoLoginService.setSsoEmail(this.formGroup.value.email); - - // Generate necessary sso params - const passwordOptions: any = { - type: "password", - length: 64, - uppercase: true, - lowercase: true, - numbers: true, - special: false, - }; - const state = await this.passwordGenerationService.generatePassword(passwordOptions); - const ssoCodeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); - const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, "sha256"); - const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); - - // Save sso params - await this.ssoLoginService.setSsoState(state); - await this.ssoLoginService.setCodeVerifier(ssoCodeVerifier); - try { - await ipc.platform.localhostCallbackService.openSsoPrompt(codeChallenge, state); - } catch (err) { - this.platformUtilsService.showToast( - "error", - this.i18nService.t("errorOccured"), - this.i18nService.t("ssoError"), - ); - } - } - - /** - * Force the validatedEmail flag to false, which will show the login page. - */ - invalidateEmail() { - this.validatedEmail = false; - } -} diff --git a/apps/desktop/src/auth/login/login-via-auth-request-v1.component.html b/apps/desktop/src/auth/login/login-via-auth-request-v1.component.html deleted file mode 100644 index 06c1f279c00..00000000000 --- a/apps/desktop/src/auth/login/login-via-auth-request-v1.component.html +++ /dev/null @@ -1,72 +0,0 @@ -
-
- Bitwarden - - -

{{ "loginInitiated" | i18n }}

- -
-
-
-
-

{{ "notificationSentDevice" | i18n }}

-

- {{ "fingerprintMatchInfo" | i18n }} -

-
- -
-

{{ "fingerprintPhraseHeader" | i18n }}

- {{ fingerprintPhrase }} -
- - - -
-

- {{ "needAnotherOption" | i18n }} - - {{ "viewAllLoginOptions" | i18n }} - -

-
-
-
-
-
- - -

{{ "adminApprovalRequested" | i18n }}

- -
-
-
-
-

{{ "adminApprovalRequestSentToAdmins" | i18n }}

-

{{ "youWillBeNotifiedOnceApproved" | i18n }}

-
- -
-

{{ "fingerprintPhraseHeader" | i18n }}

- {{ fingerprintPhrase }} -
- -
-

- {{ "troubleLoggingIn" | i18n }} - - {{ "viewAllLoginOptions" | i18n }} - -

-
-
-
-
-
-
-
- diff --git a/apps/desktop/src/auth/login/login-via-auth-request-v1.component.ts b/apps/desktop/src/auth/login/login-via-auth-request-v1.component.ts deleted file mode 100644 index 30e693b9ac6..00000000000 --- a/apps/desktop/src/auth/login/login-via-auth-request-v1.component.ts +++ /dev/null @@ -1,117 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Location } from "@angular/common"; -import { Component, ViewChild, ViewContainerRef } from "@angular/core"; -import { Router } from "@angular/router"; - -import { LoginViaAuthRequestComponentV1 as BaseLoginViaAuthRequestComponentV1 } from "@bitwarden/angular/auth/components/login-via-auth-request-v1.component"; -import { ModalService } from "@bitwarden/angular/services/modal.service"; -import { - AuthRequestServiceAbstraction, - LoginStrategyServiceAbstraction, - LoginEmailServiceAbstraction, -} from "@bitwarden/auth/common"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { AnonymousHubService } from "@bitwarden/common/auth/abstractions/anonymous-hub.service"; -import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; -import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; -import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { ToastService } from "@bitwarden/components"; -import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; -import { KeyService } from "@bitwarden/key-management"; - -import { EnvironmentComponent } from "../environment.component"; - -@Component({ - selector: "app-login-via-auth-request", - templateUrl: "login-via-auth-request-v1.component.html", -}) -export class LoginViaAuthRequestComponentV1 extends BaseLoginViaAuthRequestComponentV1 { - @ViewChild("environment", { read: ViewContainerRef, static: true }) - environmentModal: ViewContainerRef; - showingModal = false; - - constructor( - protected router: Router, - keyService: KeyService, - cryptoFunctionService: CryptoFunctionService, - appIdService: AppIdService, - passwordGenerationService: PasswordGenerationServiceAbstraction, - apiService: ApiService, - authService: AuthService, - logService: LogService, - environmentService: EnvironmentService, - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - anonymousHubService: AnonymousHubService, - validationService: ValidationService, - private modalService: ModalService, - syncService: SyncService, - loginEmailService: LoginEmailServiceAbstraction, - deviceTrustService: DeviceTrustServiceAbstraction, - authRequestService: AuthRequestServiceAbstraction, - loginStrategyService: LoginStrategyServiceAbstraction, - accountService: AccountService, - private location: Location, - toastService: ToastService, - ) { - super( - router, - keyService, - cryptoFunctionService, - appIdService, - passwordGenerationService, - apiService, - authService, - logService, - environmentService, - i18nService, - platformUtilsService, - anonymousHubService, - validationService, - accountService, - loginEmailService, - deviceTrustService, - authRequestService, - loginStrategyService, - toastService, - ); - - this.onSuccessfulLogin = () => { - return syncService.fullSync(true); - }; - } - - async settings() { - const [modal, childComponent] = await this.modalService.openViewRef( - EnvironmentComponent, - this.environmentModal, - ); - - // eslint-disable-next-line rxjs-angular/prefer-takeuntil - modal.onShown.subscribe(() => { - this.showingModal = true; - }); - // eslint-disable-next-line rxjs-angular/prefer-takeuntil - modal.onClosed.subscribe(() => { - this.showingModal = false; - }); - - // eslint-disable-next-line rxjs-angular/prefer-takeuntil - childComponent.onSaved.subscribe(() => { - modal.close(); - }); - } - - back() { - this.location.back(); - } -} diff --git a/apps/desktop/src/auth/login/login.module.ts b/apps/desktop/src/auth/login/login.module.ts index 427cbcb2069..601c71e00b1 100644 --- a/apps/desktop/src/auth/login/login.module.ts +++ b/apps/desktop/src/auth/login/login.module.ts @@ -5,18 +5,9 @@ import { EnvironmentSelectorComponent } from "@bitwarden/angular/auth/components import { SharedModule } from "../../app/shared/shared.module"; -import { LoginDecryptionOptionsComponentV1 } from "./login-decryption-options/login-decryption-options-v1.component"; -import { LoginComponentV1 } from "./login-v1.component"; -import { LoginViaAuthRequestComponentV1 } from "./login-via-auth-request-v1.component"; - @NgModule({ imports: [SharedModule, RouterModule], - declarations: [ - LoginComponentV1, - LoginViaAuthRequestComponentV1, - EnvironmentSelectorComponent, - LoginDecryptionOptionsComponentV1, - ], - exports: [LoginComponentV1, LoginViaAuthRequestComponentV1], + declarations: [EnvironmentSelectorComponent], + exports: [], }) export class LoginModule {} diff --git a/apps/desktop/src/auth/register.component.html b/apps/desktop/src/auth/register.component.html deleted file mode 100644 index 9f76b9b2a4d..00000000000 --- a/apps/desktop/src/auth/register.component.html +++ /dev/null @@ -1,150 +0,0 @@ -
-
-

{{ "createAccount" | i18n }}

-
-
-
- - -
-
-
-
- - -
-
- -
-
- - -
-
- -
-
-
-
-
- - -
-
- -
-
-
- - -
-
-
-
- - -
-
-
-
- -
- -
- - -
-
- - -
-
-
diff --git a/apps/desktop/src/auth/register.component.ts b/apps/desktop/src/auth/register.component.ts deleted file mode 100644 index b6cc9be3581..00000000000 --- a/apps/desktop/src/auth/register.component.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { Component, NgZone, OnDestroy, OnInit } from "@angular/core"; -import { UntypedFormBuilder } from "@angular/forms"; -import { Router } from "@angular/router"; - -import { RegisterComponent as BaseRegisterComponent } from "@bitwarden/angular/auth/components/register.component"; -import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service"; -import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { AuditService } from "@bitwarden/common/abstractions/audit.service"; -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; -import { KeyService } from "@bitwarden/key-management"; - -const BroadcasterSubscriptionId = "RegisterComponent"; - -@Component({ - selector: "app-register", - templateUrl: "register.component.html", -}) -export class RegisterComponent extends BaseRegisterComponent implements OnInit, OnDestroy { - constructor( - formValidationErrorService: FormValidationErrorsService, - formBuilder: UntypedFormBuilder, - loginStrategyService: LoginStrategyServiceAbstraction, - router: Router, - i18nService: I18nService, - keyService: KeyService, - apiService: ApiService, - stateService: StateService, - platformUtilsService: PlatformUtilsService, - passwordGenerationService: PasswordGenerationServiceAbstraction, - environmentService: EnvironmentService, - private broadcasterService: BroadcasterService, - private ngZone: NgZone, - logService: LogService, - auditService: AuditService, - dialogService: DialogService, - toastService: ToastService, - configService: ConfigService, - ) { - super( - formValidationErrorService, - formBuilder, - loginStrategyService, - router, - i18nService, - keyService, - apiService, - stateService, - platformUtilsService, - passwordGenerationService, - environmentService, - logService, - auditService, - dialogService, - toastService, - configService, - ); - } - - async ngOnInit() { - this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => { - this.ngZone.run(() => { - switch (message.command) { - case "windowHidden": - this.onWindowHidden(); - break; - default: - } - }); - }); - - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - super.ngOnInit(); - } - - ngOnDestroy() { - this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); - } - - onWindowHidden() { - this.showPassword = false; - } -} diff --git a/apps/desktop/src/auth/services/desktop-two-factor-auth-duo-component.service.spec.ts b/apps/desktop/src/auth/services/desktop-two-factor-auth-duo-component.service.spec.ts new file mode 100644 index 00000000000..12ca8013b1d --- /dev/null +++ b/apps/desktop/src/auth/services/desktop-two-factor-auth-duo-component.service.spec.ts @@ -0,0 +1,93 @@ +import { MockProxy, mock } from "jest-mock-extended"; +import { BehaviorSubject, firstValueFrom } from "rxjs"; + +import { + Environment, + 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 { MessageListener } from "@bitwarden/common/platform/messaging"; + +import { DesktopTwoFactorAuthDuoComponentService } from "./desktop-two-factor-auth-duo-component.service"; + +describe("DesktopTwoFactorAuthDuoComponentService", () => { + let desktopTwoFactorAuthDuoComponentService: DesktopTwoFactorAuthDuoComponentService; + let messageListener: MockProxy; + let environmentService: MockProxy; + let i18nService: MockProxy; + let platformUtilsService: MockProxy; + + beforeEach(() => { + jest.clearAllMocks(); + + messageListener = mock(); + environmentService = mock(); + i18nService = mock(); + platformUtilsService = mock(); + + desktopTwoFactorAuthDuoComponentService = new DesktopTwoFactorAuthDuoComponentService( + messageListener, + environmentService, + i18nService, + platformUtilsService, + ); + }); + + describe("listenForDuo2faResult$", () => { + it("should return an observable that emits a duo 2FA result when a duo result message is received", async () => { + const message: { code: string; state: string } = { + code: "123456", + state: "abcdef", + }; + const expectedDuo2faResult = { + code: message.code, + state: message.state, + token: `${message.code}|${message.state}`, + }; + + const messages = new BehaviorSubject(message); + messageListener.messages$.mockReturnValue(messages); + + const duo2faResult = await firstValueFrom( + desktopTwoFactorAuthDuoComponentService.listenForDuo2faResult$(), + ); + expect(duo2faResult).toEqual(expectedDuo2faResult); + }); + }); + + describe("launchDuoFrameless", () => { + it("should build and launch the duo frameless URL", async () => { + // Arrange + const duoFramelessUrl = "https://duoFramelessUrl"; + const webVaultUrl = "https://webVaultUrl"; + + i18nService.t.mockImplementation((key) => key); + + const handOffMessage = { + title: "youSuccessfullyLoggedIn", + message: "youMayCloseThisWindow", + isCountdown: false, + }; + + const mockEnvironment = { + getWebVaultUrl: () => webVaultUrl, + } as unknown as Environment; + const environmentBSubject = new BehaviorSubject(mockEnvironment); + environmentService.environment$ = environmentBSubject.asObservable(); + + // Act + await desktopTwoFactorAuthDuoComponentService.launchDuoFrameless(duoFramelessUrl); + + // Assert + const launchUrl = + webVaultUrl + + "/duo-redirect-connector.html" + + "?duoFramelessUrl=" + + encodeURIComponent(duoFramelessUrl) + + "&handOffMessage=" + + encodeURIComponent(JSON.stringify(handOffMessage)); + expect(platformUtilsService.launchUri).toHaveBeenCalledWith(launchUrl); + }); + }); +}); diff --git a/apps/desktop/src/auth/services/desktop-two-factor-auth-duo-component.service.ts b/apps/desktop/src/auth/services/desktop-two-factor-auth-duo-component.service.ts new file mode 100644 index 00000000000..eef03ca5b53 --- /dev/null +++ b/apps/desktop/src/auth/services/desktop-two-factor-auth-duo-component.service.ts @@ -0,0 +1,56 @@ +import { firstValueFrom, map, Observable } from "rxjs"; + +import { TwoFactorAuthDuoComponentService, Duo2faResult } from "@bitwarden/auth/angular"; +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 { CommandDefinition, MessageListener } from "@bitwarden/common/platform/messaging"; + +// TODO: PM-16209 We should create a Duo2faMessageListenerService that listens for messages from duo +// and this command definition should move to that file. +// We should explore consolidating the messaging approach across clients - i.e., we +// should use the same command definition across all clients. We use duoResult on extension for no real +// benefit. +export const DUO_2FA_RESULT_COMMAND = new CommandDefinition<{ code: string; state: string }>( + "duoCallback", +); + +export class DesktopTwoFactorAuthDuoComponentService implements TwoFactorAuthDuoComponentService { + constructor( + private messageListener: MessageListener, + private environmentService: EnvironmentService, + private i18nService: I18nService, + private platformUtilsService: PlatformUtilsService, + ) {} + listenForDuo2faResult$(): Observable { + return this.messageListener.messages$(DUO_2FA_RESULT_COMMAND).pipe( + map((msg) => { + return { + code: msg.code, + state: msg.state, + token: `${msg.code}|${msg.state}`, + } as Duo2faResult; + }), + ); + } + + async launchDuoFrameless(duoFramelessUrl: string): Promise { + const duoHandOffMessage = { + title: this.i18nService.t("youSuccessfullyLoggedIn"), + message: this.i18nService.t("youMayCloseThisWindow"), + isCountdown: false, + }; + + // we're using the connector here as a way to set a cookie with translations + // before continuing to the duo frameless url + const env = await firstValueFrom(this.environmentService.environment$); + const launchUrl = + env.getWebVaultUrl() + + "/duo-redirect-connector.html" + + "?duoFramelessUrl=" + + encodeURIComponent(duoFramelessUrl) + + "&handOffMessage=" + + encodeURIComponent(JSON.stringify(duoHandOffMessage)); + this.platformUtilsService.launchUri(launchUrl); + } +} diff --git a/apps/desktop/src/auth/set-password.component.ts b/apps/desktop/src/auth/set-password.component.ts index 902fa59791c..5a78fb08c47 100644 --- a/apps/desktop/src/auth/set-password.component.ts +++ b/apps/desktop/src/auth/set-password.component.ts @@ -9,19 +9,18 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-conso import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; +import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { MasterKey, UserKey } from "@bitwarden/common/types/key"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { DialogService, ToastService } from "@bitwarden/components"; -import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { KdfConfigService, KeyService } from "@bitwarden/key-management"; const BroadcasterSubscriptionId = "SetPasswordComponent"; @@ -38,16 +37,15 @@ export class SetPasswordComponent extends BaseSetPasswordComponent implements On i18nService: I18nService, keyService: KeyService, messagingService: MessagingService, - passwordGenerationService: PasswordGenerationServiceAbstraction, platformUtilsService: PlatformUtilsService, policyApiService: PolicyApiServiceAbstraction, policyService: PolicyService, router: Router, + masterPasswordApiService: MasterPasswordApiService, syncService: SyncService, route: ActivatedRoute, private broadcasterService: BroadcasterService, private ngZone: NgZone, - stateService: StateService, organizationApiService: OrganizationApiServiceAbstraction, organizationUserApiService: OrganizationUserApiService, userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction, @@ -63,15 +61,14 @@ export class SetPasswordComponent extends BaseSetPasswordComponent implements On i18nService, keyService, messagingService, - passwordGenerationService, platformUtilsService, policyApiService, policyService, router, + masterPasswordApiService, apiService, syncService, route, - stateService, organizationApiService, organizationUserApiService, userDecryptionOptionsService, diff --git a/apps/desktop/src/auth/sso-v1.component.ts b/apps/desktop/src/auth/sso-v1.component.ts index da3139e31f7..1bb6d8362a1 100644 --- a/apps/desktop/src/auth/sso-v1.component.ts +++ b/apps/desktop/src/auth/sso-v1.component.ts @@ -8,8 +8,8 @@ import { } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; diff --git a/apps/desktop/src/auth/two-factor-auth-duo.component.ts b/apps/desktop/src/auth/two-factor-auth-duo.component.ts deleted file mode 100644 index 72137dc5364..00000000000 --- a/apps/desktop/src/auth/two-factor-auth-duo.component.ts +++ /dev/null @@ -1,115 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { DialogModule } from "@angular/cdk/dialog"; -import { CommonModule } from "@angular/common"; -import { Component, NgZone, OnDestroy, OnInit } from "@angular/core"; -import { ReactiveFormsModule, FormsModule } from "@angular/forms"; -import { firstValueFrom } from "rxjs"; - -import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { - AsyncActionsModule, - ButtonModule, - FormFieldModule, - LinkModule, - ToastService, - TypographyModule, -} from "@bitwarden/components"; - -import { TwoFactorAuthDuoComponent as TwoFactorAuthDuoBaseComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-duo.component"; - -const BroadcasterSubscriptionId = "TwoFactorComponent"; - -@Component({ - standalone: true, - selector: "app-two-factor-auth-duo", - templateUrl: - "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-duo.component.html", - imports: [ - CommonModule, - JslibModule, - DialogModule, - ButtonModule, - LinkModule, - TypographyModule, - ReactiveFormsModule, - FormFieldModule, - AsyncActionsModule, - FormsModule, - ], - providers: [I18nPipe], -}) -export class TwoFactorAuthDuoComponent - extends TwoFactorAuthDuoBaseComponent - implements OnInit, OnDestroy -{ - constructor( - protected i18nService: I18nService, - protected platformUtilsService: PlatformUtilsService, - private broadcasterService: BroadcasterService, - private ngZone: NgZone, - private environmentService: EnvironmentService, - toastService: ToastService, - ) { - super(i18nService, platformUtilsService, toastService); - } - - async ngOnInit(): Promise { - await super.ngOnInit(); - } - - duoCallbackSubscriptionEnabled: boolean = false; - - protected override setupDuoResultListener() { - if (!this.duoCallbackSubscriptionEnabled) { - this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => { - await this.ngZone.run(async () => { - if (message.command === "duoCallback") { - this.token.emit(message.code + "|" + message.state); - } - }); - }); - this.duoCallbackSubscriptionEnabled = true; - } - } - - override async launchDuoFrameless() { - if (this.duoFramelessUrl === null) { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("duoHealthCheckResultsInNullAuthUrlError"), - }); - return; - } - const duoHandOffMessage = { - title: this.i18nService.t("youSuccessfullyLoggedIn"), - message: this.i18nService.t("youMayCloseThisWindow"), - isCountdown: false, - }; - - // we're using the connector here as a way to set a cookie with translations - // before continuing to the duo frameless url - const env = await firstValueFrom(this.environmentService.environment$); - const launchUrl = - env.getWebVaultUrl() + - "/duo-redirect-connector.html" + - "?duoFramelessUrl=" + - encodeURIComponent(this.duoFramelessUrl) + - "&handOffMessage=" + - encodeURIComponent(JSON.stringify(duoHandOffMessage)); - this.platformUtilsService.launchUri(launchUrl); - } - - async ngOnDestroy() { - if (this.duoCallbackSubscriptionEnabled) { - this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); - this.duoCallbackSubscriptionEnabled = false; - } - } -} diff --git a/apps/desktop/src/auth/two-factor-auth.component.ts b/apps/desktop/src/auth/two-factor-auth.component.ts deleted file mode 100644 index 29271b565c1..00000000000 --- a/apps/desktop/src/auth/two-factor-auth.component.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { DialogModule } from "@angular/cdk/dialog"; -import { CommonModule } from "@angular/common"; -import { Component } from "@angular/core"; -import { ReactiveFormsModule } from "@angular/forms"; -import { RouterLink } from "@angular/router"; - -import { TwoFactorAuthAuthenticatorComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-authenticator.component"; -import { TwoFactorAuthEmailComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-email.component"; -import { TwoFactorAuthWebAuthnComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-webauthn.component"; -import { TwoFactorAuthYubikeyComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-yubikey.component"; -import { TwoFactorAuthComponent as BaseTwoFactorAuthComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component"; -import { TwoFactorOptionsComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-options.component"; -import { JslibModule } from "../../../../libs/angular/src/jslib.module"; -import { AsyncActionsModule } from "../../../../libs/components/src/async-actions"; -import { ButtonModule } from "../../../../libs/components/src/button"; -import { CheckboxModule } from "../../../../libs/components/src/checkbox"; -import { FormFieldModule } from "../../../../libs/components/src/form-field"; -import { LinkModule } from "../../../../libs/components/src/link"; -import { I18nPipe } from "../../../../libs/components/src/shared/i18n.pipe"; -import { TypographyModule } from "../../../../libs/components/src/typography"; - -import { TwoFactorAuthDuoComponent } from "./two-factor-auth-duo.component"; - -@Component({ - standalone: true, - templateUrl: - "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.html", - selector: "app-two-factor-auth", - imports: [ - CommonModule, - JslibModule, - DialogModule, - ButtonModule, - LinkModule, - TypographyModule, - ReactiveFormsModule, - FormFieldModule, - AsyncActionsModule, - RouterLink, - CheckboxModule, - TwoFactorOptionsComponent, - TwoFactorAuthEmailComponent, - TwoFactorAuthAuthenticatorComponent, - TwoFactorAuthYubikeyComponent, - TwoFactorAuthDuoComponent, - TwoFactorAuthWebAuthnComponent, - ], - providers: [I18nPipe], -}) -export class TwoFactorAuthComponent extends BaseTwoFactorAuthComponent {} diff --git a/apps/desktop/src/auth/two-factor-options.component.html b/apps/desktop/src/auth/two-factor-options-v1.component.html similarity index 100% rename from apps/desktop/src/auth/two-factor-options.component.html rename to apps/desktop/src/auth/two-factor-options-v1.component.html diff --git a/apps/desktop/src/auth/two-factor-options.component.ts b/apps/desktop/src/auth/two-factor-options-v1.component.ts similarity index 74% rename from apps/desktop/src/auth/two-factor-options.component.ts rename to apps/desktop/src/auth/two-factor-options-v1.component.ts index 624d003c91f..1cb440a5f5f 100644 --- a/apps/desktop/src/auth/two-factor-options.component.ts +++ b/apps/desktop/src/auth/two-factor-options-v1.component.ts @@ -1,7 +1,7 @@ import { Component } from "@angular/core"; import { Router } from "@angular/router"; -import { TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent } from "@bitwarden/angular/auth/components/two-factor-options.component"; +import { TwoFactorOptionsComponentV1 as BaseTwoFactorOptionsComponentV1 } from "@bitwarden/angular/auth/components/two-factor-options-v1.component"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -9,9 +9,9 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl @Component({ selector: "app-two-factor-options", - templateUrl: "two-factor-options.component.html", + templateUrl: "two-factor-options-v1.component.html", }) -export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent { +export class TwoFactorOptionsComponentV1 extends BaseTwoFactorOptionsComponentV1 { constructor( twoFactorService: TwoFactorService, router: Router, diff --git a/apps/desktop/src/auth/two-factor.component.html b/apps/desktop/src/auth/two-factor-v1.component.html similarity index 100% rename from apps/desktop/src/auth/two-factor.component.html rename to apps/desktop/src/auth/two-factor-v1.component.html diff --git a/apps/desktop/src/auth/two-factor.component.ts b/apps/desktop/src/auth/two-factor-v1.component.ts similarity index 93% rename from apps/desktop/src/auth/two-factor.component.ts rename to apps/desktop/src/auth/two-factor-v1.component.ts index 7f4525c5f14..13c7d0a452b 100644 --- a/apps/desktop/src/auth/two-factor.component.ts +++ b/apps/desktop/src/auth/two-factor-v1.component.ts @@ -4,7 +4,7 @@ import { Component, Inject, NgZone, OnDestroy, ViewChild, ViewContainerRef } fro import { ActivatedRoute, Router } from "@angular/router"; import { firstValueFrom } from "rxjs"; -import { TwoFactorComponent as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor.component"; +import { TwoFactorComponentV1 as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor-v1.component"; import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { @@ -14,10 +14,10 @@ import { } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -29,16 +29,15 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { ToastService } from "@bitwarden/components"; -import { TwoFactorOptionsComponent } from "./two-factor-options.component"; +import { TwoFactorOptionsComponentV1 } from "./two-factor-options-v1.component"; const BroadcasterSubscriptionId = "TwoFactorComponent"; @Component({ selector: "app-two-factor", - templateUrl: "two-factor.component.html", + templateUrl: "two-factor-v1.component.html", }) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class TwoFactorComponent extends BaseTwoFactorComponent implements OnDestroy { +export class TwoFactorComponentV1 extends BaseTwoFactorComponent implements OnDestroy { @ViewChild("twoFactorOptions", { read: ViewContainerRef, static: true }) twoFactorOptionsModal: ViewContainerRef; @@ -106,7 +105,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent implements OnDest async anotherMethod() { const [modal, childComponent] = await this.modalService.openViewRef( - TwoFactorOptionsComponent, + TwoFactorOptionsComponentV1, this.twoFactorOptionsModal, ); diff --git a/apps/desktop/src/platform/main/main-ssh-agent.service.ts b/apps/desktop/src/autofill/main/main-ssh-agent.service.ts similarity index 88% rename from apps/desktop/src/platform/main/main-ssh-agent.service.ts rename to apps/desktop/src/autofill/main/main-ssh-agent.service.ts index cc4565f27f4..595ef778bcf 100644 --- a/apps/desktop/src/platform/main/main-ssh-agent.service.ts +++ b/apps/desktop/src/autofill/main/main-ssh-agent.service.ts @@ -25,16 +25,6 @@ export class MainSshAgentService { private logService: LogService, private messagingService: MessagingService, ) { - ipcMain.handle( - "sshagent.importkey", - async ( - event: any, - { privateKey, password }: { privateKey: string; password?: string }, - ): Promise => { - return sshagent.importKey(privateKey, password); - }, - ); - ipcMain.handle("sshagent.init", async (event: any, message: any) => { this.init(); }); @@ -47,7 +37,7 @@ export class MainSshAgentService { init() { // handle sign request passing to UI sshagent - .serve(async (err: Error, cipherId: string, isListRequest: boolean, processName: string) => { + .serve(async (err: Error, sshUiRequest: sshagent.SshUiRequest) => { // clear all old (> SIGN_TIMEOUT) requests this.requestResponses = this.requestResponses.filter( (response) => response.timestamp > new Date(Date.now() - this.SIGN_TIMEOUT), @@ -56,10 +46,12 @@ export class MainSshAgentService { this.request_id += 1; const id_for_this_request = this.request_id; this.messagingService.send("sshagent.signrequest", { - cipherId, - isListRequest, + cipherId: sshUiRequest.cipherId, + isListRequest: sshUiRequest.isList, requestId: id_for_this_request, - processName, + processName: sshUiRequest.processName, + isAgentForwarding: sshUiRequest.isForwarding, + namespace: sshUiRequest.namespace, }); const result = await firstValueFrom( diff --git a/apps/desktop/src/autofill/preload.ts b/apps/desktop/src/autofill/preload.ts index 494544f5858..2c006b5c928 100644 --- a/apps/desktop/src/autofill/preload.ts +++ b/apps/desktop/src/autofill/preload.ts @@ -80,6 +80,44 @@ export default { return; } + ipcRenderer.send("autofill.completePasskeyAssertion", { + clientId, + sequenceNumber, + response, + }); + }); + }, + ); + }, + listenPasskeyAssertionWithoutUserInterface: ( + fn: ( + clientId: number, + sequenceNumber: number, + request: autofill.PasskeyAssertionWithoutUserInterfaceRequest, + completeCallback: (error: Error | null, response: autofill.PasskeyAssertionResponse) => void, + ) => void, + ) => { + ipcRenderer.on( + "autofill.passkeyAssertionWithoutUserInterface", + ( + event, + data: { + clientId: number; + sequenceNumber: number; + request: autofill.PasskeyAssertionWithoutUserInterfaceRequest; + }, + ) => { + const { clientId, sequenceNumber, request } = data; + fn(clientId, sequenceNumber, request, (error, response) => { + if (error) { + ipcRenderer.send("autofill.completeError", { + clientId, + sequenceNumber, + error: error.message, + }); + return; + } + ipcRenderer.send("autofill.completePasskeyAssertion", { clientId, sequenceNumber, diff --git a/apps/desktop/src/autofill/services/desktop-autofill-settings.service.ts b/apps/desktop/src/autofill/services/desktop-autofill-settings.service.ts index d60a08a9fe1..e7a7d24cdf2 100644 --- a/apps/desktop/src/autofill/services/desktop-autofill-settings.service.ts +++ b/apps/desktop/src/autofill/services/desktop-autofill-settings.service.ts @@ -18,8 +18,9 @@ export class DesktopAutofillSettingsService { private enableDuckDuckGoBrowserIntegrationState = this.stateProvider.getGlobal( ENABLE_DUCK_DUCK_GO_BROWSER_INTEGRATION, ); - readonly enableDuckDuckGoBrowserIntegration$ = - this.enableDuckDuckGoBrowserIntegrationState.state$.pipe(map((x) => x ?? false)); + enableDuckDuckGoBrowserIntegration$ = this.enableDuckDuckGoBrowserIntegrationState.state$.pipe( + map((x) => x ?? false), + ); constructor(private stateProvider: StateProvider) {} diff --git a/apps/desktop/src/autofill/services/desktop-autofill.service.ts b/apps/desktop/src/autofill/services/desktop-autofill.service.ts index 1ce58596b34..e88e16c2ffc 100644 --- a/apps/desktop/src/autofill/services/desktop-autofill.service.ts +++ b/apps/desktop/src/autofill/services/desktop-autofill.service.ts @@ -1,17 +1,19 @@ import { Injectable, OnDestroy } from "@angular/core"; import { autofill } from "desktop_native/napi"; import { - EMPTY, Subject, distinctUntilChanged, + filter, firstValueFrom, map, mergeMap, switchMap, takeUntil, + EMPTY, } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getOptionalUserId } from "@bitwarden/common/auth/services/account.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -24,9 +26,10 @@ import { } from "@bitwarden/common/platform/abstractions/fido2/fido2-authenticator.service.abstraction"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { parseCredentialId } from "@bitwarden/common/platform/services/fido2/credential-id-utils"; import { getCredentialsForAutofill } from "@bitwarden/common/platform/services/fido2/fido2-autofill-utils"; import { Fido2Utils } from "@bitwarden/common/platform/services/fido2/fido2-utils"; -import { guidToRawFormat } from "@bitwarden/common/platform/services/fido2/guid-utils"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -38,6 +41,8 @@ import { NativeAutofillSyncCommand, } from "../../platform/main/autofill/sync.command"; +import type { NativeWindowObject } from "./desktop-fido2-user-interface.service"; + @Injectable() export class DesktopAutofillService implements OnDestroy { private destroy$ = new Subject(); @@ -46,7 +51,7 @@ export class DesktopAutofillService implements OnDestroy { private logService: LogService, private cipherService: CipherService, private configService: ConfigService, - private fido2AuthenticatorService: Fido2AuthenticatorServiceAbstraction, + private fido2AuthenticatorService: Fido2AuthenticatorServiceAbstraction, private accountService: AccountService, ) {} @@ -60,7 +65,11 @@ export class DesktopAutofillService implements OnDestroy { return EMPTY; } - return this.cipherService.cipherViews$; + return this.accountService.activeAccount$.pipe( + map((account) => account?.id), + filter((userId): userId is UserId => userId != null), + switchMap((userId) => this.cipherService.cipherViews$(userId)), + ); }), // TODO: This will unset all the autofill credentials on the OS // when the account locks. We should instead explicilty clear the credentials @@ -148,7 +157,11 @@ export class DesktopAutofillService implements OnDestroy { const controller = new AbortController(); void this.fido2AuthenticatorService - .makeCredential(this.convertRegistrationRequest(request), null, controller) + .makeCredential( + this.convertRegistrationRequest(request), + { windowXy: request.windowXy }, + controller, + ) .then((response) => { callback(null, this.convertRegistrationResponse(request, response)); }) @@ -158,42 +171,77 @@ export class DesktopAutofillService implements OnDestroy { }); }); + ipc.autofill.listenPasskeyAssertionWithoutUserInterface( + async (clientId, sequenceNumber, request, callback) => { + this.logService.warning( + "listenPasskeyAssertion without user interface", + clientId, + sequenceNumber, + request, + ); + + // For some reason the credentialId is passed as an empty array in the request, so we need to + // get it from the cipher. For that we use the recordIdentifier, which is the cipherId. + if (request.recordIdentifier && request.credentialId.length === 0) { + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(getOptionalUserId), + ); + if (!activeUserId) { + this.logService.error("listenPasskeyAssertion error", "Active user not found"); + callback(new Error("Active user not found"), null); + return; + } + + const cipher = await this.cipherService.get(request.recordIdentifier, activeUserId); + if (!cipher) { + this.logService.error("listenPasskeyAssertion error", "Cipher not found"); + callback(new Error("Cipher not found"), null); + return; + } + + const decrypted = await cipher.decrypt( + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), + ); + + const fido2Credential = decrypted.login.fido2Credentials?.[0]; + if (!fido2Credential) { + this.logService.error("listenPasskeyAssertion error", "Fido2Credential not found"); + callback(new Error("Fido2Credential not found"), null); + return; + } + + request.credentialId = Array.from( + parseCredentialId(decrypted.login.fido2Credentials?.[0].credentialId), + ); + } + + const controller = new AbortController(); + void this.fido2AuthenticatorService + .getAssertion( + this.convertAssertionRequest(request), + { windowXy: request.windowXy }, + controller, + ) + .then((response) => { + callback(null, this.convertAssertionResponse(request, response)); + }) + .catch((error) => { + this.logService.error("listenPasskeyAssertion error", error); + callback(error, null); + }); + }, + ); + ipc.autofill.listenPasskeyAssertion(async (clientId, sequenceNumber, request, callback) => { this.logService.warning("listenPasskeyAssertion", clientId, sequenceNumber, request); - // TODO: For some reason the credentialId is passed as an empty array in the request, so we need to - // get it from the cipher. For that we use the recordIdentifier, which is the cipherId. - if (request.recordIdentifier && request.credentialId.length === 0) { - const cipher = await this.cipherService.get(request.recordIdentifier); - if (!cipher) { - this.logService.error("listenPasskeyAssertion error", "Cipher not found"); - callback(new Error("Cipher not found"), null); - return; - } - - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - - const decrypted = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); - - const fido2Credential = decrypted.login.fido2Credentials?.[0]; - if (!fido2Credential) { - this.logService.error("listenPasskeyAssertion error", "Fido2Credential not found"); - callback(new Error("Fido2Credential not found"), null); - return; - } - - request.credentialId = Array.from( - guidToRawFormat(decrypted.login.fido2Credentials?.[0].credentialId), - ); - } - const controller = new AbortController(); void this.fido2AuthenticatorService - .getAssertion(this.convertAssertionRequest(request), null, controller) + .getAssertion( + this.convertAssertionRequest(request), + { windowXy: request.windowXy }, + controller, + ) .then((response) => { callback(null, this.convertAssertionResponse(request, response)); }) @@ -245,27 +293,48 @@ export class DesktopAutofillService implements OnDestroy { }; } + /** + * + * @param request + * @param assumeUserPresence For WithoutUserInterface requests, we assume the user is present + * @returns + */ private convertAssertionRequest( - request: autofill.PasskeyAssertionRequest, + request: + | autofill.PasskeyAssertionRequest + | autofill.PasskeyAssertionWithoutUserInterfaceRequest, ): Fido2AuthenticatorGetAssertionParams { + let allowedCredentials; + if ("credentialId" in request) { + allowedCredentials = [ + { + id: new Uint8Array(request.credentialId), + type: "public-key" as const, + }, + ]; + } else { + allowedCredentials = request.allowedCredentials.map((credentialId) => ({ + id: new Uint8Array(credentialId), + type: "public-key" as const, + })); + } + return { rpId: request.rpId, hash: new Uint8Array(request.clientDataHash), - allowCredentialDescriptorList: [ - { - id: new Uint8Array(request.credentialId), - type: "public-key", - }, - ], + allowCredentialDescriptorList: allowedCredentials, extensions: {}, requireUserVerification: request.userVerification === "required" || request.userVerification === "preferred", fallbackSupported: false, + assumeUserPresence: true, // For desktop assertions, it's safe to assume UP has been checked by OS dialogues }; } private convertAssertionResponse( - request: autofill.PasskeyAssertionRequest, + request: + | autofill.PasskeyAssertionRequest + | autofill.PasskeyAssertionWithoutUserInterfaceRequest, response: Fido2AuthenticatorGetAssertionResult, ): autofill.PasskeyAssertionResponse { return { diff --git a/apps/desktop/src/autofill/services/desktop-fido2-user-interface.service.ts b/apps/desktop/src/autofill/services/desktop-fido2-user-interface.service.ts index 684b19e7394..3caf13fa5b7 100644 --- a/apps/desktop/src/autofill/services/desktop-fido2-user-interface.service.ts +++ b/apps/desktop/src/autofill/services/desktop-fido2-user-interface.service.ts @@ -1,4 +1,14 @@ -import { firstValueFrom, map } from "rxjs"; +import { Router } from "@angular/router"; +import { + lastValueFrom, + firstValueFrom, + map, + Subject, + filter, + take, + BehaviorSubject, + timeout, +} from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; @@ -10,8 +20,10 @@ import { PickCredentialParams, } from "@bitwarden/common/platform/abstractions/fido2/fido2-user-interface.service.abstraction"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherRepromptType, CipherType, SecureNoteType } from "@bitwarden/common/vault/enums"; +import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; import { CardView } from "@bitwarden/common/vault/models/view/card.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { IdentityView } from "@bitwarden/common/vault/models/view/identity.view"; @@ -19,28 +31,54 @@ import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; import { SecureNoteView } from "@bitwarden/common/vault/models/view/secure-note.view"; +import { DesktopSettingsService } from "../../platform/services/desktop-settings.service"; + +/** + * This type is used to pass the window position from the native UI + */ +export type NativeWindowObject = { + /** + * The position of the window, first entry is the x position, second is the y position + */ + windowXy?: { x: number; y: number }; +}; + export class DesktopFido2UserInterfaceService - implements Fido2UserInterfaceServiceAbstraction + implements Fido2UserInterfaceServiceAbstraction { constructor( private authService: AuthService, private cipherService: CipherService, private accountService: AccountService, private logService: LogService, + private messagingService: MessagingService, + private router: Router, + private desktopSettingsService: DesktopSettingsService, ) {} + private currentSession: any; + + getCurrentSession(): DesktopFido2UserInterfaceSession | undefined { + return this.currentSession; + } async newSession( fallbackSupported: boolean, - _tab: void, + nativeWindowObject: NativeWindowObject, abortController?: AbortController, ): Promise { - this.logService.warning("newSession", fallbackSupported, abortController); - return new DesktopFido2UserInterfaceSession( + this.logService.warning("newSession", fallbackSupported, abortController, nativeWindowObject); + const session = new DesktopFido2UserInterfaceSession( this.authService, this.cipherService, this.accountService, this.logService, + this.router, + this.desktopSettingsService, + nativeWindowObject, ); + + this.currentSession = session; + return session; } } @@ -50,17 +88,110 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi private cipherService: CipherService, private accountService: AccountService, private logService: LogService, + private router: Router, + private desktopSettingsService: DesktopSettingsService, + private windowObject: NativeWindowObject, ) {} + private confirmCredentialSubject = new Subject(); + private createdCipher: Cipher; + + private availableCipherIdsSubject = new BehaviorSubject(null); + /** + * Observable that emits available cipher IDs once they're confirmed by the UI + */ + availableCipherIds$ = this.availableCipherIdsSubject.pipe( + filter((ids) => ids != null), + take(1), + ); + + private chosenCipherSubject = new Subject<{ cipherId: string; userVerified: boolean }>(); + + // Method implementation async pickCredential({ cipherIds, userVerification, + assumeUserPresence, + masterPasswordRepromptRequired, }: PickCredentialParams): Promise<{ cipherId: string; userVerified: boolean }> { - this.logService.warning("pickCredential", cipherIds, userVerification); + this.logService.warning("pickCredential desktop function", { + cipherIds, + userVerification, + assumeUserPresence, + masterPasswordRepromptRequired, + }); - return { cipherId: cipherIds[0], userVerified: userVerification }; + try { + // Check if we can return the credential without user interaction + if (assumeUserPresence && cipherIds.length === 1 && !masterPasswordRepromptRequired) { + this.logService.debug( + "shortcut - Assuming user presence and returning cipherId", + cipherIds[0], + ); + return { cipherId: cipherIds[0], userVerified: userVerification }; + } + + this.logService.debug("Could not shortcut, showing UI"); + + // make the cipherIds available to the UI. + this.availableCipherIdsSubject.next(cipherIds); + + await this.showUi("/passkeys", this.windowObject.windowXy); + + const chosenCipherResponse = await this.waitForUiChosenCipher(); + + this.logService.debug("Received chosen cipher", chosenCipherResponse); + + return { + cipherId: chosenCipherResponse.cipherId, + userVerified: chosenCipherResponse.userVerified, + }; + } finally { + // Make sure to clean up so the app is never stuck in modal mode? + await this.desktopSettingsService.setModalMode(false); + } } + confirmChosenCipher(cipherId: string, userVerified: boolean = false): void { + this.chosenCipherSubject.next({ cipherId, userVerified }); + this.chosenCipherSubject.complete(); + } + + private async waitForUiChosenCipher( + timeoutMs: number = 60000, + ): Promise<{ cipherId: string; userVerified: boolean } | undefined> { + try { + return await lastValueFrom(this.chosenCipherSubject.pipe(timeout(timeoutMs))); + } catch { + // If we hit a timeout, return undefined instead of throwing + this.logService.warning("Timeout: User did not select a cipher within the allowed time", { + timeoutMs, + }); + return { cipherId: undefined, userVerified: false }; + } + } + + /** + * Notifies the Fido2UserInterfaceSession that the UI operations has completed and it can return to the OS. + */ + notifyConfirmNewCredential(confirmed: boolean): void { + this.confirmCredentialSubject.next(confirmed); + this.confirmCredentialSubject.complete(); + } + + /** + * Returns once the UI has confirmed and completed the operation + * @returns + */ + private async waitForUiNewCredentialConfirmation(): Promise { + return lastValueFrom(this.confirmCredentialSubject); + } + + /** + * This is called by the OS. It loads the UI and waits for the user to confirm the new credential. Once the UI has confirmed, it returns to the the OS. + * @param param0 + * @returns + */ async confirmNewCredential({ credentialName, userName, @@ -75,6 +206,48 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi rpId, ); + try { + await this.showUi("/passkeys", this.windowObject.windowXy); + + // Wait for the UI to wrap up + const confirmation = await this.waitForUiNewCredentialConfirmation(); + if (!confirmation) { + return { cipherId: undefined, userVerified: false }; + } + // Create the credential + await this.createCredential({ + credentialName, + userName, + rpId, + userHandle: "", + userVerification, + }); + + // wait for 10ms to help RXJS catch up(?) + // We sometimes get a race condition from this.createCredential not updating cipherService in time + //console.log("waiting 10ms.."); + //await new Promise((resolve) => setTimeout(resolve, 10)); + //console.log("Just waited 10ms"); + + // Return the new cipher (this.createdCipher) + return { cipherId: this.createdCipher.id, userVerified: userVerification }; + } finally { + // Make sure to clean up so the app is never stuck in modal mode? + await this.desktopSettingsService.setModalMode(false); + } + } + + private async showUi(route: string, position?: { x: number; y: number }): Promise { + // Load the UI: + await this.desktopSettingsService.setModalMode(true, position); + await this.router.navigate(["/passkeys"]); + } + + /** + * Can be called by the UI to create a new credential with user input etc. + * @param param0 + */ + async createCredential({ credentialName, userName, rpId }: NewCredentialParams): Promise { // Store the passkey on a new cipher to avoid replacing something important const cipher = new CipherView(); cipher.name = credentialName; @@ -97,7 +270,9 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi const encCipher = await this.cipherService.encrypt(cipher, activeUserId); const createdCipher = await this.cipherService.createWithServer(encCipher); - return { cipherId: createdCipher.id, userVerified: userVerification }; + this.createdCipher = createdCipher; + + return createdCipher; } async informExcludedCredential(existingCipherIds: string[]): Promise { diff --git a/apps/desktop/src/platform/services/ssh-agent.service.ts b/apps/desktop/src/autofill/services/ssh-agent.service.ts similarity index 86% rename from apps/desktop/src/platform/services/ssh-agent.service.ts rename to apps/desktop/src/autofill/services/ssh-agent.service.ts index 726d28022e5..bf7167c0240 100644 --- a/apps/desktop/src/platform/services/ssh-agent.service.ts +++ b/apps/desktop/src/autofill/services/ssh-agent.service.ts @@ -24,18 +24,16 @@ import { import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { 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 { CommandDefinition, MessageListener } from "@bitwarden/common/platform/messaging"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { DialogService, ToastService } from "@bitwarden/components"; -import { ApproveSshRequestComponent } from "../components/approve-ssh-request"; - -import { DesktopSettingsService } from "./desktop-settings.service"; +import { ApproveSshRequestComponent } from "../../platform/components/approve-ssh-request"; +import { DesktopSettingsService } from "../../platform/services/desktop-settings.service"; @Injectable({ providedIn: "root", @@ -58,23 +56,13 @@ export class SshAgentService implements OnDestroy { private toastService: ToastService, private i18nService: I18nService, private desktopSettingsService: DesktopSettingsService, - private configService: ConfigService, private accountService: AccountService, ) {} async init() { - this.configService - .getFeatureFlag$(FeatureFlag.SSHAgent) - .pipe( - concatMap(async (enabled) => { - this.isFeatureFlagEnabled = enabled; - if (!(await ipc.platform.sshAgent.isLoaded()) && enabled) { - await ipc.platform.sshAgent.init(); - } - }), - takeUntil(this.destroy$), - ) - .subscribe(); + if (!(await ipc.platform.sshAgent.isLoaded())) { + await ipc.platform.sshAgent.init(); + } await this.initListeners(); } @@ -83,14 +71,23 @@ export class SshAgentService implements OnDestroy { this.messageListener .messages$(new CommandDefinition("sshagent.signrequest")) .pipe( - withLatestFrom(this.authService.activeAccountStatus$), + withLatestFrom(this.desktopSettingsService.sshAgentEnabled$), + concatMap(async ([message, enabled]) => { + if (!enabled) { + await ipc.platform.sshAgent.signRequestResponse(message.requestId as number, false); + } + return { message, enabled }; + }), + filter(({ enabled }) => enabled), + map(({ message }) => message), + withLatestFrom(this.authService.activeAccountStatus$, this.accountService.activeAccount$), // This switchMap handles unlocking the vault if it is locked: // - If the vault is locked, we will wait for it to be unlocked. // - If the vault is not unlocked within the timeout, we will abort the flow. // - If the vault is unlocked, we will continue with the flow. // switchMap is used here to prevent multiple requests from being processed at the same time, // and will cancel the previous request if a new one is received. - switchMap(([message, status]) => { + switchMap(([message, status, account]) => { if (status !== AuthenticationStatus.Unlocked) { ipc.platform.focusWindow(); this.toastService.showToast({ @@ -120,15 +117,15 @@ export class SshAgentService implements OnDestroy { throw error; }), - map(() => message), + map(() => [message, account.id]), ); } - return of(message); + return of([message, account.id]); }), // This switchMap handles fetching the ciphers from the vault. - switchMap((message) => - from(this.cipherService.getAllDecrypted()).pipe( + switchMap(([message, userId]: [Record, UserId]) => + from(this.cipherService.getAllDecrypted(userId)).pipe( map((ciphers) => [message, ciphers] as const), ), ), @@ -138,6 +135,8 @@ export class SshAgentService implements OnDestroy { const isListRequest = message.isListRequest as boolean; const requestId = message.requestId as number; let application = message.processName as string; + const namespace = message.namespace as string; + const isAgentForwarding = message.isAgentForwarding as boolean; if (application == "") { application = this.i18nService.t("unknownApplication"); } @@ -171,6 +170,8 @@ export class SshAgentService implements OnDestroy { this.dialogService, cipher.name, application, + isAgentForwarding, + namespace, ); const result = await firstValueFrom(dialogRef.closed); @@ -236,7 +237,7 @@ export class SshAgentService implements OnDestroy { return; } - const ciphers = await this.cipherService.getAllDecrypted(); + const ciphers = await this.cipherService.getAllDecrypted(activeAccount.id); if (ciphers == null) { await ipc.platform.sshAgent.lock(); return; diff --git a/apps/desktop/src/vault/app/accounts/premium.component.html b/apps/desktop/src/billing/app/accounts/premium.component.html similarity index 100% rename from apps/desktop/src/vault/app/accounts/premium.component.html rename to apps/desktop/src/billing/app/accounts/premium.component.html diff --git a/apps/desktop/src/vault/app/accounts/premium.component.ts b/apps/desktop/src/billing/app/accounts/premium.component.ts similarity index 85% rename from apps/desktop/src/vault/app/accounts/premium.component.ts rename to apps/desktop/src/billing/app/accounts/premium.component.ts index 4b547384545..1b4573fe6d6 100644 --- a/apps/desktop/src/vault/app/accounts/premium.component.ts +++ b/apps/desktop/src/billing/app/accounts/premium.component.ts @@ -1,15 +1,14 @@ import { Component } from "@angular/core"; -import { PremiumComponent as BasePremiumComponent } from "@bitwarden/angular/vault/components/premium.component"; +import { PremiumComponent as BasePremiumComponent } from "@bitwarden/angular/billing/components/premium.component"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; @Component({ selector: "app-premium", @@ -20,22 +19,22 @@ export class PremiumComponent extends BasePremiumComponent { i18nService: I18nService, platformUtilsService: PlatformUtilsService, apiService: ApiService, - configService: ConfigService, logService: LogService, dialogService: DialogService, environmentService: EnvironmentService, billingAccountProfileStateService: BillingAccountProfileStateService, + toastService: ToastService, accountService: AccountService, ) { super( i18nService, platformUtilsService, apiService, - configService, logService, dialogService, environmentService, billingAccountProfileStateService, + toastService, accountService, ); } diff --git a/apps/desktop/src/key-management/biometrics/biometrics.service.spec.ts b/apps/desktop/src/key-management/biometrics/biometrics.service.spec.ts deleted file mode 100644 index e69ebca3630..00000000000 --- a/apps/desktop/src/key-management/biometrics/biometrics.service.spec.ts +++ /dev/null @@ -1,187 +0,0 @@ -import { mock, MockProxy } from "jest-mock-extended"; - -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { UserId } from "@bitwarden/common/types/guid"; -import { - BiometricsService, - BiometricsStatus, - BiometricStateService, -} from "@bitwarden/key-management"; - -import { WindowMain } from "../../main/window.main"; - -import { MainBiometricsService } from "./main-biometrics.service"; -import OsBiometricsServiceLinux from "./os-biometrics-linux.service"; -import OsBiometricsServiceMac from "./os-biometrics-mac.service"; -import OsBiometricsServiceWindows from "./os-biometrics-windows.service"; -import { OsBiometricService } from "./os-biometrics.service"; - -jest.mock("@bitwarden/desktop-napi", () => { - return { - biometrics: jest.fn(), - passwords: jest.fn(), - }; -}); - -describe("biometrics tests", function () { - const i18nService = mock(); - const windowMain = mock(); - const logService = mock(); - const messagingService = mock(); - const biometricStateService = mock(); - - it("Should call the platformspecific methods", async () => { - const sut = new MainBiometricsService( - i18nService, - windowMain, - logService, - messagingService, - process.platform, - biometricStateService, - ); - - const mockService = mock(); - (sut as any).osBiometricsService = mockService; - - await sut.authenticateBiometric(); - expect(mockService.authenticateBiometric).toBeCalled(); - }); - - describe("Should create a platform specific service", function () { - it("Should create a biometrics service specific for Windows", () => { - const sut = new MainBiometricsService( - i18nService, - windowMain, - logService, - messagingService, - "win32", - biometricStateService, - ); - - const internalService = (sut as any).osBiometricsService; - expect(internalService).not.toBeNull(); - expect(internalService).toBeInstanceOf(OsBiometricsServiceWindows); - }); - - it("Should create a biometrics service specific for MacOs", () => { - const sut = new MainBiometricsService( - i18nService, - windowMain, - logService, - messagingService, - "darwin", - biometricStateService, - ); - const internalService = (sut as any).osBiometricsService; - expect(internalService).not.toBeNull(); - expect(internalService).toBeInstanceOf(OsBiometricsServiceMac); - }); - - it("Should create a biometrics service specific for Linux", () => { - const sut = new MainBiometricsService( - i18nService, - windowMain, - logService, - messagingService, - "linux", - biometricStateService, - ); - - const internalService = (sut as any).osBiometricsService; - expect(internalService).not.toBeNull(); - expect(internalService).toBeInstanceOf(OsBiometricsServiceLinux); - }); - }); - - describe("can auth biometric", () => { - let sut: BiometricsService; - let innerService: MockProxy; - - beforeEach(() => { - sut = new MainBiometricsService( - i18nService, - windowMain, - logService, - messagingService, - process.platform, - biometricStateService, - ); - - innerService = mock(); - (sut as any).osBiometricsService = innerService; - }); - - it("should return the correct biometric status for system status", async () => { - const testCases = [ - // happy path - [true, false, false, BiometricsStatus.Available], - [false, true, true, BiometricsStatus.AutoSetupNeeded], - [false, true, false, BiometricsStatus.ManualSetupNeeded], - [false, false, false, BiometricsStatus.HardwareUnavailable], - - // should not happen - [false, false, true, BiometricsStatus.HardwareUnavailable], - [true, true, true, BiometricsStatus.Available], - [true, true, false, BiometricsStatus.Available], - [true, false, true, BiometricsStatus.Available], - ]; - - for (const [supportsBiometric, needsSetup, canAutoSetup, expected] of testCases) { - innerService.osSupportsBiometric.mockResolvedValue(supportsBiometric as boolean); - innerService.osBiometricsNeedsSetup.mockResolvedValue(needsSetup as boolean); - innerService.osBiometricsCanAutoSetup.mockResolvedValue(canAutoSetup as boolean); - - const actual = await sut.getBiometricsStatus(); - expect(actual).toBe(expected); - } - }); - - it("should return the correct biometric status for user status", async () => { - const testCases = [ - // system status, biometric unlock enabled, require password on start, has key half, result - [BiometricsStatus.Available, false, false, false, BiometricsStatus.NotEnabledLocally], - [BiometricsStatus.Available, false, true, false, BiometricsStatus.NotEnabledLocally], - [BiometricsStatus.Available, false, false, true, BiometricsStatus.NotEnabledLocally], - [BiometricsStatus.Available, false, true, true, BiometricsStatus.NotEnabledLocally], - - [ - BiometricsStatus.PlatformUnsupported, - true, - true, - true, - BiometricsStatus.PlatformUnsupported, - ], - [BiometricsStatus.ManualSetupNeeded, true, true, true, BiometricsStatus.ManualSetupNeeded], - [BiometricsStatus.AutoSetupNeeded, true, true, true, BiometricsStatus.AutoSetupNeeded], - - [BiometricsStatus.Available, true, false, true, BiometricsStatus.Available], - [BiometricsStatus.Available, true, true, false, BiometricsStatus.UnlockNeeded], - [BiometricsStatus.Available, true, false, true, BiometricsStatus.Available], - ]; - - for (const [ - systemStatus, - unlockEnabled, - requirePasswordOnStart, - hasKeyHalf, - expected, - ] of testCases) { - sut.getBiometricsStatus = jest.fn().mockResolvedValue(systemStatus as BiometricsStatus); - biometricStateService.getBiometricUnlockEnabled.mockResolvedValue(unlockEnabled as boolean); - biometricStateService.getRequirePasswordOnStart.mockResolvedValue( - requirePasswordOnStart as boolean, - ); - (sut as any).clientKeyHalves = new Map(); - const userId = "test" as UserId; - if (hasKeyHalf) { - (sut as any).clientKeyHalves.set(userId, "test"); - } - - const actual = await sut.getBiometricsStatusForUser(userId); - expect(actual).toBe(expected); - } - }); - }); -}); diff --git a/apps/desktop/src/key-management/biometrics/desktop.biometrics.service.ts b/apps/desktop/src/key-management/biometrics/desktop.biometrics.service.ts index 0c0efea78f9..6415443bfbc 100644 --- a/apps/desktop/src/key-management/biometrics/desktop.biometrics.service.ts +++ b/apps/desktop/src/key-management/biometrics/desktop.biometrics.service.ts @@ -11,5 +11,5 @@ export abstract class DesktopBiometricsService extends BiometricsService { abstract setupBiometrics(): Promise; - abstract setClientKeyHalfForUser(userId: UserId, value: string): Promise; + abstract setClientKeyHalfForUser(userId: UserId, value: string | null): 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 eebafd8d48b..fe40aad54d9 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 @@ -32,6 +32,9 @@ export class MainBiometricsIPCListener { case BiometricAction.GetStatusForUser: return await this.biometricService.getBiometricsStatusForUser(message.userId as UserId); case BiometricAction.SetKeyForUser: + if (message.key == null) { + return; + } return await this.biometricService.setBiometricProtectedUnlockKeyForUser( message.userId as UserId, message.key, diff --git a/apps/desktop/src/key-management/biometrics/main-biometrics.service.spec.ts b/apps/desktop/src/key-management/biometrics/main-biometrics.service.spec.ts new file mode 100644 index 00000000000..88dd8c60ed5 --- /dev/null +++ b/apps/desktop/src/key-management/biometrics/main-biometrics.service.spec.ts @@ -0,0 +1,426 @@ +import { mock, MockProxy } from "jest-mock-extended"; + +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 { EncryptionType } from "@bitwarden/common/platform/enums"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { UserId } from "@bitwarden/common/types/guid"; +import { + BiometricsService, + BiometricsStatus, + BiometricStateService, +} from "@bitwarden/key-management"; + +import { WindowMain } from "../../main/window.main"; + +import { MainBiometricsService } from "./main-biometrics.service"; +import OsBiometricsServiceLinux from "./os-biometrics-linux.service"; +import OsBiometricsServiceMac from "./os-biometrics-mac.service"; +import OsBiometricsServiceWindows from "./os-biometrics-windows.service"; +import { OsBiometricService } from "./os-biometrics.service"; + +jest.mock("@bitwarden/desktop-napi", () => { + return { + biometrics: jest.fn(), + passwords: jest.fn(), + }; +}); + +describe("MainBiometricsService", function () { + const i18nService = mock(); + const windowMain = mock(); + const logService = mock(); + const messagingService = mock(); + const biometricStateService = mock(); + + it("Should call the platformspecific methods", async () => { + const sut = new MainBiometricsService( + i18nService, + windowMain, + logService, + messagingService, + process.platform, + biometricStateService, + ); + + const mockService = mock(); + (sut as any).osBiometricsService = mockService; + + await sut.authenticateBiometric(); + expect(mockService.authenticateBiometric).toBeCalled(); + }); + + describe("Should create a platform specific service", function () { + it("Should create a biometrics service specific for Windows", () => { + const sut = new MainBiometricsService( + i18nService, + windowMain, + logService, + messagingService, + "win32", + biometricStateService, + ); + + const internalService = (sut as any).osBiometricsService; + expect(internalService).not.toBeNull(); + expect(internalService).toBeInstanceOf(OsBiometricsServiceWindows); + }); + + it("Should create a biometrics service specific for MacOs", () => { + const sut = new MainBiometricsService( + i18nService, + windowMain, + logService, + messagingService, + "darwin", + biometricStateService, + ); + const internalService = (sut as any).osBiometricsService; + expect(internalService).not.toBeNull(); + expect(internalService).toBeInstanceOf(OsBiometricsServiceMac); + }); + + it("Should create a biometrics service specific for Linux", () => { + const sut = new MainBiometricsService( + i18nService, + windowMain, + logService, + messagingService, + "linux", + biometricStateService, + ); + + const internalService = (sut as any).osBiometricsService; + expect(internalService).not.toBeNull(); + expect(internalService).toBeInstanceOf(OsBiometricsServiceLinux); + }); + }); + + describe("can auth biometric", () => { + let sut: BiometricsService; + let innerService: MockProxy; + + beforeEach(() => { + sut = new MainBiometricsService( + i18nService, + windowMain, + logService, + messagingService, + process.platform, + biometricStateService, + ); + + innerService = mock(); + (sut as any).osBiometricsService = innerService; + }); + + it("should return the correct biometric status for system status", async () => { + const testCases = [ + // happy path + [true, false, false, BiometricsStatus.Available], + [false, true, true, BiometricsStatus.HardwareUnavailable], + [true, true, true, BiometricsStatus.AutoSetupNeeded], + [true, true, false, BiometricsStatus.ManualSetupNeeded], + + // should not happen + [false, false, true, BiometricsStatus.HardwareUnavailable], + [true, false, true, BiometricsStatus.Available], + [false, true, false, BiometricsStatus.HardwareUnavailable], + [false, false, false, BiometricsStatus.HardwareUnavailable], + ]; + + for (const [supportsBiometric, needsSetup, canAutoSetup, expected] of testCases) { + innerService.osSupportsBiometric.mockResolvedValue(supportsBiometric as boolean); + innerService.osBiometricsNeedsSetup.mockResolvedValue(needsSetup as boolean); + innerService.osBiometricsCanAutoSetup.mockResolvedValue(canAutoSetup as boolean); + + const actual = await sut.getBiometricsStatus(); + expect(actual).toBe(expected); + } + }); + + it("should return the correct biometric status for user status", async () => { + const testCases = [ + // system status, biometric unlock enabled, require password on start, has key half, result + [BiometricsStatus.Available, false, false, false, BiometricsStatus.NotEnabledLocally], + [BiometricsStatus.Available, false, true, false, BiometricsStatus.NotEnabledLocally], + [BiometricsStatus.Available, false, false, true, BiometricsStatus.NotEnabledLocally], + [BiometricsStatus.Available, false, true, true, BiometricsStatus.NotEnabledLocally], + + [ + BiometricsStatus.PlatformUnsupported, + true, + true, + true, + BiometricsStatus.PlatformUnsupported, + ], + [BiometricsStatus.ManualSetupNeeded, true, true, true, BiometricsStatus.ManualSetupNeeded], + [BiometricsStatus.AutoSetupNeeded, true, true, true, BiometricsStatus.AutoSetupNeeded], + + [BiometricsStatus.Available, true, false, true, BiometricsStatus.Available], + [BiometricsStatus.Available, true, true, false, BiometricsStatus.UnlockNeeded], + [BiometricsStatus.Available, true, false, true, BiometricsStatus.Available], + ]; + + for (const [ + systemStatus, + unlockEnabled, + requirePasswordOnStart, + hasKeyHalf, + expected, + ] of testCases) { + sut.getBiometricsStatus = jest.fn().mockResolvedValue(systemStatus as BiometricsStatus); + biometricStateService.getBiometricUnlockEnabled.mockResolvedValue(unlockEnabled as boolean); + biometricStateService.getRequirePasswordOnStart.mockResolvedValue( + requirePasswordOnStart as boolean, + ); + (sut as any).clientKeyHalves = new Map(); + const userId = "test" as UserId; + if (hasKeyHalf) { + (sut as any).clientKeyHalves.set(userId, "test"); + } + + const actual = await sut.getBiometricsStatusForUser(userId); + expect(actual).toBe(expected); + } + }); + }); + + describe("setupBiometrics", () => { + it("should call the platform specific setup method", async () => { + const sut = new MainBiometricsService( + i18nService, + windowMain, + logService, + messagingService, + process.platform, + biometricStateService, + ); + const osBiometricsService = mock(); + (sut as any).osBiometricsService = osBiometricsService; + + await sut.setupBiometrics(); + + expect(osBiometricsService.osBiometricsSetup).toHaveBeenCalled(); + }); + }); + + describe("setClientKeyHalfForUser", () => { + let sut: MainBiometricsService; + + beforeEach(() => { + sut = new MainBiometricsService( + i18nService, + windowMain, + logService, + messagingService, + process.platform, + biometricStateService, + ); + }); + + it("should set the client key half for the user", async () => { + const userId = "test" as UserId; + const keyHalf = "testKeyHalf"; + + await sut.setClientKeyHalfForUser(userId, keyHalf); + + expect((sut as any).clientKeyHalves.has(userId)).toBe(true); + expect((sut as any).clientKeyHalves.get(userId)).toBe(keyHalf); + }); + + it("should reset the client key half for the user", async () => { + const userId = "test" as UserId; + + await sut.setClientKeyHalfForUser(userId, null); + + expect((sut as any).clientKeyHalves.has(userId)).toBe(true); + expect((sut as any).clientKeyHalves.get(userId)).toBe(null); + }); + }); + + describe("authenticateWithBiometrics", () => { + it("should call the platform specific authenticate method", async () => { + const sut = new MainBiometricsService( + i18nService, + windowMain, + logService, + messagingService, + process.platform, + biometricStateService, + ); + const osBiometricsService = mock(); + (sut as any).osBiometricsService = osBiometricsService; + + await sut.authenticateWithBiometrics(); + + expect(osBiometricsService.authenticateBiometric).toHaveBeenCalled(); + }); + }); + + describe("unlockWithBiometricsForUser", () => { + let sut: MainBiometricsService; + let osBiometricsService: MockProxy; + + beforeEach(() => { + sut = new MainBiometricsService( + i18nService, + windowMain, + logService, + messagingService, + process.platform, + biometricStateService, + ); + osBiometricsService = mock(); + (sut as any).osBiometricsService = osBiometricsService; + }); + + it("should return null if no biometric key is returned ", async () => { + const userId = "test" as UserId; + (sut as any).clientKeyHalves.set(userId, "testKeyHalf"); + + const userKey = await sut.unlockWithBiometricsForUser(userId); + + expect(userKey).toBeNull(); + expect(osBiometricsService.getBiometricKey).toHaveBeenCalledWith( + "Bitwarden_biometric", + `${userId}_user_biometric`, + "testKeyHalf", + ); + }); + + it("should return the biometric key if a valid key is returned", async () => { + const userId = "test" as UserId; + (sut as any).clientKeyHalves.set(userId, "testKeyHalf"); + const biometricKey = Utils.fromBufferToB64(new Uint8Array(64)); + osBiometricsService.getBiometricKey.mockResolvedValue(biometricKey); + + const userKey = await sut.unlockWithBiometricsForUser(userId); + + expect(userKey).not.toBeNull(); + expect(userKey!.keyB64).toBe(biometricKey); + expect(userKey!.encType).toBe(EncryptionType.AesCbc256_HmacSha256_B64); + expect(osBiometricsService.getBiometricKey).toHaveBeenCalledWith( + "Bitwarden_biometric", + `${userId}_user_biometric`, + "testKeyHalf", + ); + }); + }); + + describe("setBiometricProtectedUnlockKeyForUser", () => { + let sut: MainBiometricsService; + let osBiometricsService: MockProxy; + + beforeEach(() => { + sut = new MainBiometricsService( + i18nService, + windowMain, + logService, + messagingService, + process.platform, + biometricStateService, + ); + osBiometricsService = mock(); + (sut as any).osBiometricsService = osBiometricsService; + }); + + it("should throw an error if no client key half is provided", async () => { + const userId = "test" as UserId; + const unlockKey = "testUnlockKey"; + + await expect(sut.setBiometricProtectedUnlockKeyForUser(userId, unlockKey)).rejects.toThrow( + "No client key half provided for user", + ); + }); + + it("should call the platform specific setBiometricKey method", async () => { + const userId = "test" as UserId; + const unlockKey = "testUnlockKey"; + + (sut as any).clientKeyHalves.set(userId, "testKeyHalf"); + + await sut.setBiometricProtectedUnlockKeyForUser(userId, unlockKey); + + expect(osBiometricsService.setBiometricKey).toHaveBeenCalledWith( + "Bitwarden_biometric", + `${userId}_user_biometric`, + unlockKey, + "testKeyHalf", + ); + }); + }); + + describe("deleteBiometricUnlockKeyForUser", () => { + it("should call the platform specific deleteBiometricKey method", async () => { + const sut = new MainBiometricsService( + i18nService, + windowMain, + logService, + messagingService, + process.platform, + biometricStateService, + ); + const osBiometricsService = mock(); + (sut as any).osBiometricsService = osBiometricsService; + + const userId = "test" as UserId; + + await sut.deleteBiometricUnlockKeyForUser(userId); + + expect(osBiometricsService.deleteBiometricKey).toHaveBeenCalledWith( + "Bitwarden_biometric", + `${userId}_user_biometric`, + ); + }); + }); + + describe("setShouldAutopromptNow", () => { + let sut: MainBiometricsService; + + beforeEach(() => { + sut = new MainBiometricsService( + i18nService, + windowMain, + logService, + messagingService, + process.platform, + biometricStateService, + ); + }); + + it("should set shouldAutopromptNow to false", async () => { + await sut.setShouldAutopromptNow(false); + + const shouldAutoPrompt = await sut.getShouldAutopromptNow(); + + expect(shouldAutoPrompt).toBe(false); + }); + + it("should set shouldAutopromptNow to true", async () => { + await sut.setShouldAutopromptNow(true); + + const shouldAutoPrompt = await sut.getShouldAutopromptNow(); + + expect(shouldAutoPrompt).toBe(true); + }); + }); + + describe("getShouldAutopromptNow", () => { + it("defaults shouldAutoPrompt is true", async () => { + const sut = new MainBiometricsService( + i18nService, + windowMain, + logService, + messagingService, + process.platform, + biometricStateService, + ); + + const shouldAutoPrompt = await sut.getShouldAutopromptNow(); + + expect(shouldAutoPrompt).toBe(true); + }); + }); +}); 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 06956503a05..dd2e15a2fe8 100644 --- a/apps/desktop/src/key-management/biometrics/main-biometrics.service.ts +++ b/apps/desktop/src/key-management/biometrics/main-biometrics.service.ts @@ -13,7 +13,7 @@ import { OsBiometricService } from "./os-biometrics.service"; export class MainBiometricsService extends DesktopBiometricsService { private osBiometricsService: OsBiometricService; - private clientKeyHalves = new Map(); + private clientKeyHalves = new Map(); private shouldAutoPrompt = true; constructor( @@ -25,10 +25,6 @@ export class MainBiometricsService extends DesktopBiometricsService { private biometricStateService: BiometricStateService, ) { super(); - this.loadOsBiometricService(this.platform); - } - - private loadOsBiometricService(platform: NodeJS.Platform) { if (platform === "win32") { // eslint-disable-next-line const OsBiometricsServiceWindows = require("./os-biometrics-windows.service").default; @@ -60,6 +56,8 @@ export class MainBiometricsService extends DesktopBiometricsService { */ async getBiometricsStatus(): Promise { if (!(await this.osBiometricsService.osSupportsBiometric())) { + return BiometricsStatus.HardwareUnavailable; + } else { if (await this.osBiometricsService.osBiometricsNeedsSetup()) { if (await this.osBiometricsService.osBiometricsCanAutoSetup()) { return BiometricsStatus.AutoSetupNeeded; @@ -67,8 +65,6 @@ export class MainBiometricsService extends DesktopBiometricsService { return BiometricsStatus.ManualSetupNeeded; } } - - return BiometricsStatus.HardwareUnavailable; } return BiometricsStatus.Available; } @@ -108,7 +104,7 @@ export class MainBiometricsService extends DesktopBiometricsService { return await this.osBiometricsService.osBiometricsSetup(); } - async setClientKeyHalfForUser(userId: UserId, value: string): Promise { + async setClientKeyHalfForUser(userId: UserId, value: string | null): Promise { this.clientKeyHalves.set(userId, value); } @@ -117,13 +113,16 @@ export class MainBiometricsService extends DesktopBiometricsService { } async unlockWithBiometricsForUser(userId: UserId): Promise { - return SymmetricCryptoKey.fromString( - await this.osBiometricsService.getBiometricKey( - "Bitwarden_biometric", - `${userId}_user_biometric`, - this.clientKeyHalves.get(userId), - ), - ) as UserKey; + const biometricKey = await this.osBiometricsService.getBiometricKey( + "Bitwarden_biometric", + `${userId}_user_biometric`, + this.clientKeyHalves.get(userId) ?? undefined, + ); + if (biometricKey == null) { + return null; + } + + return SymmetricCryptoKey.fromString(biometricKey) as UserKey; } async setBiometricProtectedUnlockKeyForUser(userId: UserId, value: string): Promise { @@ -137,7 +136,7 @@ export class MainBiometricsService extends DesktopBiometricsService { service, storageKey, value, - this.clientKeyHalves.get(userId), + this.clientKeyHalves.get(userId) ?? undefined, ); } diff --git a/apps/desktop/src/key-management/biometrics/os-biometrics-linux.service.ts b/apps/desktop/src/key-management/biometrics/os-biometrics-linux.service.ts index 791b4d6f885..fb150f2a653 100644 --- a/apps/desktop/src/key-management/biometrics/os-biometrics-linux.service.ts +++ b/apps/desktop/src/key-management/biometrics/os-biometrics-linux.service.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { spawn } from "child_process"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -138,23 +136,27 @@ export default class OsBiometricsServiceLinux implements OsBiometricService { // Nulls out key material in order to force a re-derive. This should only be used in getBiometricKey // when we want to force a re-derive of the key material. - private setIv(iv: string) { - this._iv = iv; + private setIv(iv?: string) { + this._iv = iv ?? null; this._osKeyHalf = null; } private async getStorageDetails({ clientKeyHalfB64, }: { - clientKeyHalfB64: string; + clientKeyHalfB64: string | undefined; }): Promise<{ key_material: biometrics.KeyMaterial; ivB64: string }> { if (this._osKeyHalf == null) { const keyMaterial = await biometrics.deriveKeyMaterial(this._iv); - // osKeyHalf is based on the iv and in contrast to windows is not locked behind user verefication! + // osKeyHalf is based on the iv and in contrast to windows is not locked behind user verification! this._osKeyHalf = keyMaterial.keyB64; this._iv = keyMaterial.ivB64; } + if (this._iv == null) { + throw new Error("Initialization Vector is null"); + } + return { key_material: { osKeyPartB64: this._osKeyHalf, diff --git a/apps/desktop/src/key-management/biometrics/os-biometrics-windows.service.ts b/apps/desktop/src/key-management/biometrics/os-biometrics-windows.service.ts index 9643c2b6f15..cd8c94329bc 100644 --- a/apps/desktop/src/key-management/biometrics/os-biometrics-windows.service.ts +++ b/apps/desktop/src/key-management/biometrics/os-biometrics-windows.service.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; @@ -104,7 +102,7 @@ export default class OsBiometricsServiceWindows implements OsBiometricService { private async getStorageDetails({ clientKeyHalfB64, }: { - clientKeyHalfB64: string; + clientKeyHalfB64: string | undefined; }): Promise<{ key_material: biometrics.KeyMaterial; ivB64: string }> { if (this._osKeyHalf == null) { // Prompts Windows Hello @@ -113,6 +111,10 @@ export default class OsBiometricsServiceWindows implements OsBiometricService { this._iv = keyMaterial.ivB64; } + if (this._iv == null) { + throw new Error("Initialization Vector is null"); + } + const result = { key_material: { osKeyPartB64: this._osKeyHalf, @@ -130,8 +132,8 @@ export default class OsBiometricsServiceWindows implements OsBiometricService { // Nulls out key material in order to force a re-derive. This should only be used in getBiometricKey // when we want to force a re-derive of the key material. - private setIv(iv: string) { - this._iv = iv; + private setIv(iv?: string) { + this._iv = iv ?? null; this._osKeyHalf = null; } @@ -149,9 +151,9 @@ export default class OsBiometricsServiceWindows implements OsBiometricService { encryptedValue: EncString, service: string, storageKey: string, - clientKeyPartB64: string, + clientKeyPartB64: string | undefined, ) { - if (encryptedValue.iv == null || encryptedValue == null) { + if (encryptedValue.iv == null) { return; } @@ -183,7 +185,7 @@ export default class OsBiometricsServiceWindows implements OsBiometricService { storageKey, }: { value: SymmetricCryptoKey; - clientKeyPartB64: string; + clientKeyPartB64: string | undefined; service: string; storageKey: string; }): Promise { @@ -214,7 +216,7 @@ export default class OsBiometricsServiceWindows implements OsBiometricService { /** Derives a witness key from a symmetric key being stored for biometric protection */ private witnessKeyMaterial( symmetricKey: SymmetricCryptoKey, - clientKeyPartB64: string, + clientKeyPartB64: string | undefined, ): biometrics.KeyMaterial { const key = symmetricKey?.macKeyB64 ?? symmetricKey?.keyB64; 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 a08e68b53f2..2a0b1282778 100644 --- a/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.ts +++ b/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.ts @@ -40,7 +40,7 @@ export class RendererBiometricsService extends DesktopBiometricsService { return await ipc.keyManagement.biometric.setupBiometrics(); } - async setClientKeyHalfForUser(userId: UserId, value: string): Promise { + async setClientKeyHalfForUser(userId: UserId, value: string | null): Promise { return await ipc.keyManagement.biometric.setClientKeyHalf(userId, value); } diff --git a/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.spec.ts b/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.spec.ts index 2cc8d770f58..6bfbc803e87 100644 --- a/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.spec.ts +++ b/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.spec.ts @@ -6,12 +6,12 @@ import { PinServiceAbstraction, UserDecryptionOptionsServiceAbstraction, } from "@bitwarden/auth/common"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { DeviceType } from "@bitwarden/common/enums"; +import { VaultTimeoutSettingsService } from "@bitwarden/common/key-management/vault-timeout"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { UserId } from "@bitwarden/common/types/guid"; import { KeyService, BiometricsService, BiometricsStatus } from "@bitwarden/key-management"; -import { UnlockOptions } from "@bitwarden/key-management/angular"; +import { UnlockOptions } from "@bitwarden/key-management-ui"; import { DesktopLockComponentService } from "./desktop-lock-component.service"; diff --git a/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.ts b/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.ts index 1d2d68c1d97..72df9336ea2 100644 --- a/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.ts +++ b/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.ts @@ -9,7 +9,7 @@ import { DeviceType } from "@bitwarden/common/enums"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { UserId } from "@bitwarden/common/types/guid"; import { BiometricsService, BiometricsStatus } from "@bitwarden/key-management"; -import { LockComponentService, UnlockOptions } from "@bitwarden/key-management/angular"; +import { LockComponentService, UnlockOptions } from "@bitwarden/key-management-ui"; export class DesktopLockComponentService implements LockComponentService { private readonly userDecryptionOptionsService = inject(UserDecryptionOptionsServiceAbstraction); @@ -54,7 +54,7 @@ export class DesktopLockComponentService implements LockComponentService { map(([biometricsStatus, userDecryptionOptions, pinDecryptionAvailable]) => { const unlockOpts: UnlockOptions = { masterPassword: { - enabled: userDecryptionOptions.hasMasterPassword, + enabled: userDecryptionOptions?.hasMasterPassword, }, pin: { enabled: pinDecryptionAvailable, diff --git a/apps/desktop/src/key-management/preload.ts b/apps/desktop/src/key-management/preload.ts index b73542ca725..c955571697b 100644 --- a/apps/desktop/src/key-management/preload.ts +++ b/apps/desktop/src/key-management/preload.ts @@ -39,7 +39,7 @@ const biometric = { ipcRenderer.invoke("biometric", { action: BiometricAction.Setup, } satisfies BiometricMessage), - setClientKeyHalf: (userId: string, value: string): Promise => + setClientKeyHalf: (userId: string, value: string | null): Promise => ipcRenderer.invoke("biometric", { action: BiometricAction.SetClientKeyHalf, userId: userId, diff --git a/apps/desktop/src/locales/af/messages.json b/apps/desktop/src/locales/af/messages.json index 1f569319953..ae324eda6bc 100644 --- a/apps/desktop/src/locales/af/messages.json +++ b/apps/desktop/src/locales/af/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Fout" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januarie" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Aantal Woorde" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Master password hint" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Wagwoordwenk" - }, - "enterEmailToGetHint": { - "message": "Voer u e-posadres in om u hoofwagwoordwenk te ontvang." - }, "getMasterPasswordHint": { "message": "Kry hoofwagwoordwenk" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Ongeldige bevestigingskode" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Onthou my" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Stuur weer e-pos met bevestigingskode" }, "useAnotherTwoStepMethod": { "message": "Gebruik ’n ander tweestapaantekenmetode" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Plaas u YubiKey in u rekenaar se USB-poort en druk dan op sy knop." }, @@ -871,6 +906,15 @@ "message": "Bevestig met Duo Security vir u organisasie d.m.v. die Duo Mobile-toep, SMS, spraakoproep of ’n U2F-beveiligingsleutel.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Opsies vir tweestapaantekening" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Selfgehuisveste omgewing" }, - "selfHostedEnvironmentFooter": { - "message": "Spesifiseer die basisbronadres van u selfgehuisveste Bitwarden-installasie." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Pasgemaakte omgewing" }, - "customEnvironmentFooter": { - "message": "Vir gevorderde gebruikers. U kan die basisbronadres van elke diens onafhanklik instel." - }, "baseUrl": { "message": "Bedienerbronadres" }, @@ -956,6 +997,9 @@ "no": { "message": "Nee" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Skryf oor wagwoord" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Koop Premie" }, - "premiumPurchaseAlert": { - "message": "U kan lidmaatskap op die bitwarden.com-webkluis koop. Wil u nou na die webwerf gaan?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Vereis wagwoord of PIN wanneer toep geopen word" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Aanbeveel vir veiligheid." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Waarmerk WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Versteek my e-posadres vir ontvangers." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Gebruik u domein se opgestelde allesomvattende inmandjie." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Lukraak" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Meld aan met meesterwagwoord" }, - "loggingInAs": { - "message": "Meld aan as" - }, "rememberEmail": { "message": "Onthou e-pos" }, - "notYou": { - "message": "Nie jy nie?" - }, "newAroundHere": { "message": "Nuut hier rond?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "'n Kennisgewing was na jou toestel gestuur." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "Tokkel karakter telling", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Probeer jy om aan te teken?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Aantekenings poging deur $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Tyd" }, - "confirmLogIn": { - "message": "Bevestig aantekening" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Weier aantekening" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Aantekening gebevestig vir $EMAIL$ op $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "Hierdie versoek is nie meer geldig nie." }, - "confirmLoginAtemptForMail": { - "message": "Bevestig aantekenings poging vir $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Aan teken versoek" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Swak wagwoord geidentifiseer en gevind in 'n data lekkasie. Gebruik 'n sterk en unieke wagwoord om jou rekening te beskerm. Is jy seker dat jy hierdie wagwoord wil gebruik?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Kontroleer bekende data lekkasies vir hierdie wagwoord" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Keur goed met hoofwagwoord" - }, "region": { "message": "Streek" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json index a15dbb5f078..9f5b1f3501c 100644 --- a/apps/desktop/src/locales/ar/messages.json +++ b/apps/desktop/src/locales/ar/messages.json @@ -249,6 +249,20 @@ "error": { "message": "خطأ" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "يناير" }, @@ -529,10 +543,6 @@ "message": "تضمين أحرف خاصة", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "عدد الكلمات" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "تسجيل الدخول إلى Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "تسجيل الدخول باستخدام مفتاح المرور" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "تلميح كلمة المرور الرئيسية" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "الانضمام إلى المؤسسة" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "أدخل عنوان البريد الإلكتروني لحسابك وسيُرسل تلميح كلمة المرور الخاصة بك إليك" }, - "passwordHint": { - "message": "تلميح كلمة المرور" - }, - "enterEmailToGetHint": { - "message": "أدخل عنوان البريد الإلكتروني لحسابك لتلقي تلميحات كلمة المرور الرئيسية." - }, "getMasterPasswordHint": { "message": "احصل على تلميح لكلمة المرور الرئيسية" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "تم إلغاء المصادقة أو استغرقت وقتا طويلا. الرجاء المحاولة مرة أخرى." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "رمز التحقق غير صالح" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "تذكرني" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "إرسال رمز التحقق إلى البريد الإلكتروني مرة أخرى" }, "useAnotherTwoStepMethod": { "message": "استخدام طريقة أخرى لتسجيل الدخول بخطوتين" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "أدخل YubiKey الخاص بك في منفذ USB في كمبيوترك، ثم لمس الزر." }, @@ -871,6 +906,15 @@ "message": "تحقق من خلال نظام الحماية الثنائي لمؤسستك باستخدام تطبيق Duo Mobile أو الرسائل القصيرة أو المكالمة الهاتفية أو مفتاح الأمان U2F.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "خيارات تسجيل الدخول بخطوتين" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "البيئة المستضافة ذاتيا" }, - "selfHostedEnvironmentFooter": { - "message": "حدد عنوان URL الأساسي لتثبيت Bitwarden المستضاف محليًا." - }, "selfHostedBaseUrlHint": { "message": "حدد عنوان URL الأساسي لمحالّك التي استضافت تثبيت Bitwarden على سبيل المثال: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "بيئة مخصصة" }, - "customEnvironmentFooter": { - "message": "للمستخدمين المتقدمين. يمكنك تحديد عنوان URL الأساسي لكل خدمة بشكل مستقل." - }, "baseUrl": { "message": "رابط الخادم" }, @@ -956,6 +997,9 @@ "no": { "message": "لا" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "الكتابة فوق كلمة المرور" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "شراء العضوية المميزة" }, - "premiumPurchaseAlert": { - "message": "يمكنك شراء العضوية المتميزة على bitwarden.com على خزانة الويب. هل تريد زيارة الموقع الآن؟" - }, "premiumPurchaseAlertV2": { "message": "يمكنك شراء Premium من إعدادات حسابك على تطبيق Bitwarden على شبكة الإنترنت." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "يتطلب كلمة مرور أو رقم التعريف الشخصي عند بدء التطبيق" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "موصى به للأمان." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "مصادقة WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "إخفاء عنوان البريد الإلكتروني الخاص بي من المستلمين." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "استخدم علبة البريد الإلكتروني الشاملة التي تم تكوينها في نطاقك." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "عشوائي" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "غير قادر على الحصول على معرف البريد الإلكتروني المحجوب $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "تسجيل الدخول باستخدام كلمة المرور الرئيسية" }, - "loggingInAs": { - "message": "تسجيل الدخول كـ" - }, "rememberEmail": { "message": "تذكر البريد الإلكتروني" }, - "notYou": { - "message": "ليس أنت؟" - }, "newAroundHere": { "message": "جديد هنا؟" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "بَدْء تسجيل الدخول" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "تم إرسال إشعار إلى جهازك." }, "aNotificationWasSentToYourDevice": { "message": "أُرسل إشعار إلى جهازك" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "تيقن من أن حسابك مفتوح وأن عبارة البصمة متطابقة على الجهاز الآخر" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "هل تحتاج إلى خيار آخر؟" @@ -2759,11 +2839,11 @@ "message": "تبديل عدد الأحرف", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "هل تحاول تسجيل الدخول؟" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "محاولة تسجيل الدخول بواسطة $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "الوقت" }, - "confirmLogIn": { - "message": "تأكيد تسجيل الدخول" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "رفض تسجيل الدخول" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "تم تأكيد تسجيل الدخول لـ $EMAIL$ في $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "هذا الطلب لم يعد صالحًا." }, - "confirmLoginAtemptForMail": { - "message": "تأكيد محاولة تسجيل الدخول لـ $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "طلب تسجيل الدخول" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "إنشاء الحساب في" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "كلمة مرور ضعيفة محددة وموجودة في خرق البيانات. استخدم كلمة مرور قوية وفريدة لحماية حسابك. هل أنت متأكد من أنك تريد استخدام كلمة المرور هذه؟" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "تحقق من خروقات البيانات المعروفة لكلمة المرور هذه" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "طلب موافقة المشرف" }, - "approveWithMasterPassword": { - "message": "الموافقة بواسطة كلمة المرور الرئيسية" - }, "region": { "message": "المنطقة" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "تم إرسال طلبك إلى المشرف الخاص بك." }, - "youWillBeNotifiedOnceApproved": { - "message": "سيتم إعلامك بمجرد الموافقة." - }, "troubleLoggingIn": { "message": "مشكلة في تسجيل الدخول؟" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "تسجيل الدخول لـ Duo من خطوتين مطلوب لحسابك." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "تشغيل دوو في المتصفح" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "لم يُعثر على أي منفذ مجاني لتسجيل الدخول." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "السماح" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "تأكيد استخدام مفتاح SSH" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "يطلب الوصول إلى" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "تطبيق" }, - "sshKeyPasswordUnsupported": { - "message": "استيراد مفاتيح SSH المحمية بكلمة المرور غير معتمد حتى الآن" - }, "invalidSshKey": { "message": "مفتاح SSH غير صالح" }, @@ -3389,8 +3517,8 @@ "importSshKeyFromClipboard": { "message": "استيراد المفتاح من الحافظة" }, - "sshKeyPasted": { - "message": "تم استيراد مفتاح SSH بنجاح" + "sshKeyImported": { + "message": "SSH key imported successfully" }, "fileSavedToDevice": { "message": "تم حفظ الملف على الجهاز. إدارة من تنزيلات جهازك." @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "تغيير البريد الإلكتروني الخاص بالحساب" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index 07519429f12..b7c7cb3cd67 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Xəta" }, + "decryptionError": { + "message": "Şifrə açma xətası" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden, aşağıda sadalanan seyf element(lər)inin şifrəsini aça bilmədi." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Əlavə data itkisini önləmək üçün", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "müştəri dəstəyi ilə əlaqə saxlayın.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Yanvar" }, @@ -529,10 +543,6 @@ "message": "Xüsusi xarakterləri daxil et", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Söz sayı" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Bitwarden-ə giriş edin" }, + "enterTheCodeSentToYourEmail": { + "message": "E-poçtunuza göndərilən kodu daxil edin" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Kimlik doğrulayıcı tətbiqinizdəki kodu daxil edin" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Kimliyi doğrulamaq üçün YubiKey-inizə basın" + }, "logInWithPasskey": { "message": "Keçid açarı ilə giriş et" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Ana parol ipucusu" }, + "passwordStrengthScore": { + "message": "Parolun güc xalı: $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Təşkilata qoşul" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Hesabınızın e-poçt ünvanını daxil edin və parolunuz üçün ipucu sizə göndəriləcək" }, - "passwordHint": { - "message": "Parol məsləhəti" - }, - "enterEmailToGetHint": { - "message": "Ana parol məsləhətini alacağınız hesabınızın e-poçt ünvanını daxil edin." - }, "getMasterPasswordHint": { "message": "Ana parol üçün məsləhət alın" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "Kimlik doğrulama ləğv edildi və ya çox uzun çəkdi. Lütfən yenidən sınayın." }, + "openInNewTab": { + "message": "Yeni vərəqdə aç" + }, "invalidVerificationCode": { "message": "Yararsız doğrulama kodu" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Məni xatırla" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Bu cihazda 30 gün ərzində soruşulmasın" + }, "sendVerificationCodeEmailAgain": { "message": "Doğrulama kodu olan e-poçtu yenidən göndər" }, "useAnotherTwoStepMethod": { "message": "Başqa bir iki mərhələli giriş metodu istifadə edin" }, + "selectAnotherMethod": { + "message": "Başqa üsul seçin", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Geri qaytarma kodunuzu istifadə edin" + }, "insertYubiKey": { "message": "\"YubiKey\"i kompüterinizin USB portuna taxın, daha sonra düyməsinə toxunun." }, @@ -871,6 +906,15 @@ "message": "Təşkilatınızını Duo Security ilə doğrulamaq üçün Duo Mobile tətbiqi, SMS, telefon zəngi və ya U2F güvənlik açarını istifadə edin.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Kimliyinizi doğrulayın" + }, + "weDontRecognizeThisDevice": { + "message": "Bu cihazı tanımırıq. Kimliyinizi doğrulamaq üçün e-poçtunuza göndərilən kodu daxil edin." + }, + "continueLoggingIn": { + "message": "Giriş etməyə davam" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "İki mərhələli giriş seçimləri" }, + "selectTwoStepLoginMethod": { + "message": "İki addımlı giriş üsulunu seçin" + }, "selfHostedEnvironment": { "message": "Self-hosted mühit" }, - "selfHostedEnvironmentFooter": { - "message": "Öz-özünə sahiblik edən Bitwarden quraşdırmasının təməl URL-sini müəyyənləşdirin." - }, "selfHostedBaseUrlHint": { "message": "Öz-özünə sahiblik edən Bitwarden quraşdırmasının təməl URL-sini müəyyənləşdirin. Nümunə: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Özəl mühit" }, - "customEnvironmentFooter": { - "message": "Qabaqcıl istifadəçilər üçündür. Hər xidmətin baza URL-sini müstəqil olaraq müəyyənləşdirə bilərsiniz." - }, "baseUrl": { "message": "Server URL-si" }, @@ -956,6 +997,9 @@ "no": { "message": "Xeyr" }, + "location": { + "message": "Yerləşmə" + }, "overwritePassword": { "message": "Parolun üzərinə yaz" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Premium satın al" }, - "premiumPurchaseAlert": { - "message": "Premium üzvlüyü bitwarden.com veb seyfində satın ala bilərsiniz. İndi saytı ziyarət etmək istəyirsiniz?" - }, "premiumPurchaseAlertV2": { "message": "Bitwarden veb tətbiqindəki hesab ayarlarınızda Premium satın ala bilərsiniz." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Tətbiq başladılanda parolu və ya PIN-i tələb et" }, + "requirePasswordWithoutPinOnStart": { + "message": "Tətbiq başladılanda parolu tələb et" + }, "recommendedForSecurity": { "message": "Güvənlik üçün tövsiyə olunur." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "WebAuthn kimlik doğrulama" }, + "readSecurityKey": { + "message": "Güvənlik açarını oxu" + }, + "awaitingSecurityKeyInteraction": { + "message": "Güvənlik açarı ilə əlaqə gözlənilir..." + }, "hideEmail": { "message": "E-poçt ünvanımı alıcılardan gizlət." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Domeninizin konfiqurasiya edilmiş hamısını yaxalama gələn qutusunu istifadə edin." }, + "useThisEmail": { + "message": "Bu e-poçtu istifadə et" + }, "random": { "message": "Təsadüfi" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ tələbinizə rədd cavabı verdi. Lütfən kömək üçün xidmət provayderinizlə əlaqə saxlayın.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ tələbinizə rədd cavabı verdi: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "$SERVICENAME$ maskalı e-poçt hesab kimliyi alına bilmir.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Ana parolla giriş et" }, - "loggingInAs": { - "message": "Giriş et" - }, "rememberEmail": { "message": "E-poçtu xatırla" }, - "notYou": { - "message": "Siz deyilsiniz?" - }, "newAroundHere": { "message": "Burada yenisiniz?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Giriş başladıldı" }, + "logInRequestSent": { + "message": "Tələb göndərildi" + }, "notificationSentDevice": { "message": "Cihazınıza bir bildiriş göndərildi." }, "aNotificationWasSentToYourDevice": { "message": "Cihazınıza bir bildiriş göndərildi" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Lütfən hesabınızın kilidinin açıq olduğuna və barmaq izi ifadəsinin digər cihazla uyuşduğuna əmin olun" + "notificationSentDevicePart1": { + "message": "Cihazınızda Bitwarden kilidini açın, ya da " + }, + "notificationSentDeviceAnchor": { + "message": "veb tətbiqinizdə" + }, + "notificationSentDevicePart2": { + "message": "Təsdiqləməzdən əvvəl Barmaq izi ifadəsinin aşağıdakı ifadə ilə uyuşduğuna əmin olun." }, "needAnotherOptionV1": { "message": "Başqa bir seçimə ehtiyacınız var?" @@ -2759,11 +2839,11 @@ "message": "Simvol sayını dəyişdir", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Giriş etməyə çalışırsınız?" + "areYouTryingToAccessYourAccount": { + "message": "Hesabınıza müraciət etməyə çalışırsınız?" }, - "logInAttemptBy": { - "message": "$EMAIL$ tərəfindən giriş cəhdi", + "accessAttemptBy": { + "message": "$EMAIL$ ilə müraciət cəhdi", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Vaxt" }, - "confirmLogIn": { - "message": "Girişi təsdiqlə" + "confirmAccess": { + "message": "Müraciəti təsdiqlə" }, - "denyLogIn": { - "message": "Girişi rədd et" + "denyAccess": { + "message": "Müraciətə rədd cavabı ver" }, "logInConfirmedForEmailOnDevice": { "message": "$DEVICE$ cihazında $EMAIL$ üçün giriş təsdiqləndi", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "Bu tələb artıq yararsızdır." }, - "confirmLoginAtemptForMail": { - "message": "$EMAIL$ üçün giriş cəhdini təsdiqlə", + "confirmAccessAttempt": { + "message": "$EMAIL$ üçün müraciət cəhdini təsdiqlə", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Giriş tələb olundu" }, + "accountAccessRequested": { + "message": "Tələb olunan hesaba müraciət" + }, "creatingAccountOn": { "message": "Hesab yaradılır" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Zəif parol məlumat pozuntusunda aşkarlandı və tapıldı. Hesabınızı qorumaq üçün güclü və unikal bir parol istifadə edin. Bu parolu istifadə etmək istədiyinizə əminsiniz?" }, + "useThisPassword": { + "message": "Bu parolu istifadə et" + }, + "useThisUsername": { + "message": "Bu istifadəçi adını istifadə et" + }, "checkForBreaches": { "message": "Bu parol üçün bilinən məlumat pozuntularını yoxlayın" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Admin təsdiqini tələb et" }, - "approveWithMasterPassword": { - "message": "Ana parolla təsdiqlə" - }, "region": { "message": "Bölgə" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Tələbiniz admininizə göndərildi." }, - "youWillBeNotifiedOnceApproved": { - "message": "Təsdiqləndikdən sonra məlumatlandırılacaqsınız." - }, "troubleLoggingIn": { "message": "Girişdə problem var?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Hesabınız üçün Duo iki addımlı giriş tələb olunur." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Hesabınız üçün Duo iki addımlı giriş tələb olunur. Giriş prosesini tamamlamaq üçün aşağıdakı addımları izləyin." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Giriş prosesini tamamlamaq üçün aşağıdakı addımları izləyin." + }, "launchDuo": { "message": "Duo-nu brauzerdə başlat" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "SSO giriş üçün açıq port tapıla bilmədi." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Əvvəlcə PIN və ya parol ilə kilid açma tələb olunduğu üçün biometrik ilə kilid açma əlçatmazdır." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrik kilid açma indi əlçatmazdır." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Yanlış konfiqurasiya edilmiş sistem fayllarına görə biometrik kilid açma əlçatmazdır." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Yanlış konfiqurasiya edilmiş sistem fayllarına görə biometrik kilid açma əlçatmazdır." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Bitwarden masaüstü tətbiqində $EMAIL$ üçün fəal olmadığına görə biometrik kilid açma əlçatmazdır.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Bilinməyən bilinməyən bir səbəbə görə biometrik kilid açma əlçatmazdır." + }, "authorize": { "message": "Səlahiyyət ver" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "SSH açar istifadəsini təsdiqlə" }, + "agentForwardingWarningTitle": { + "message": "Xəbərdarlıq: Nümayəndə yönləndirilir" + }, + "agentForwardingWarningText": { + "message": "Bu tələb, giriş etdiyiniz remote bir cihazdan gəlir" + }, "sshkeyApprovalMessageInfix": { "message": "bura müraciət tələb edir:" }, + "sshkeyApprovalMessageSuffix": { + "message": "məqsəd" + }, + "sshActionLogin": { + "message": "serverdə kimlik doğrulaması et" + }, + "sshActionSign": { + "message": "bir mesajı imzala" + }, + "sshActionGitSign": { + "message": "git commit-ini imzala" + }, "unknownApplication": { "message": "Bir tətbiq" }, - "sshKeyPasswordUnsupported": { - "message": "Parolla qorunan SSH açarlarının daxilə köçürülməsi hələ dəstəklənmir" - }, "invalidSshKey": { "message": "SSH açarı yararsızdır" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Açarı lövhədən daxilə köçür" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH açarı uğurla daxilə köçürüldü" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Hesabın e-poçtunu dəyişdir" + }, + "allowScreenshots": { + "message": "Ekranı çəkməyə icazə ver" + }, + "allowScreenshotsDesc": { + "message": "Bitwarden masaüstü tətbiqinin ekran şəkillərində yaxalanmasına və remote desktop sessiyalarında görünməsinə icazə verin. Bunu sıradan çıxartsanız, bəzi xarici ekranlarda müraciət əngəllənəcək." + }, + "confirmWindowStillVisibleTitle": { + "message": "Pəncərənin hələ də göründüyünü təsdiqlə" + }, + "confirmWindowStillVisibleContent": { + "message": "Lütfən, pəncərənin hələ də göründüyünü təsdiqləyin." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Uzantını güncəlləmək tələb olunur" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "İstifadə etdiyiniz brauzer uzantısı köhnəlib. Lütfən onu güncəlləyin, ya da masaüstü tətbiq ayarlarında brauzer inteqrasiyası üzrə barmaq izi ilə doğrulamanı sıradan çıxardın." + }, + "changeAtRiskPassword": { + "message": "Riskli parolları dəyişdir" } } diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json index e89d6b5fa6f..4c90222be42 100644 --- a/apps/desktop/src/locales/be/messages.json +++ b/apps/desktop/src/locales/be/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Памылка" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Студзень" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Колькасць слоў" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Master password hint" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Падказка да пароля" - }, - "enterEmailToGetHint": { - "message": "Увядзіце адрас электроннай пошты ўліковага запісу для атрымання падказкі да асноўнага пароля." - }, "getMasterPasswordHint": { "message": "Атрымаць падказку да асноўнага пароля" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Памылковы праверачны код" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Запомніць мяне" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Адправіць праверачны код яшчэ раз" }, "useAnotherTwoStepMethod": { "message": "Выкарыстоўваць іншы метад двухэтапнага ўваходу" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Устаўце свой YubiKey у порт USB камп'ютара, а потым націсніце на кнопку." }, @@ -871,6 +906,15 @@ "message": "Праверка з дапамогай Duo Security для вашай арганізацыі, выкарыстоўваючы праграму Duo Mobile, SMS, тэлефонны выклік або ключ бяспекі U2F.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Параметры двухэтапнага ўваходу" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Асяроддзе ўласнага хостынгу" }, - "selfHostedEnvironmentFooter": { - "message": "Увядзіце асноўны URL-адрас вашага лакальнага размяшчэння ўсталяванага Bitwarden." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Карыстальніцкае асяроддзе" }, - "customEnvironmentFooter": { - "message": "Для дасведчаных карыстальнікаў. Можна ўвесці URL-адрасы асобна для кожнай службы." - }, "baseUrl": { "message": "URL-адрас сервера" }, @@ -956,6 +997,9 @@ "no": { "message": "Не" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Перазапісаць пароль" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Купіць прэміум" }, - "premiumPurchaseAlert": { - "message": "Вы можаце купіць прэміяльны статус на bitwarden.com. Перайсці на вэб-сайт зараз?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Патрабаваць пароль або PIN пры запуску праграмы" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Рэкамендуецца ў мэтах бяспекі." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Аўтэнтыфікацыя WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Схаваць маю электронную пошту ад атрымальнікаў." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Выкарыстоўвайце сваю сканфігураваную скрыню для ўсёй пошты дамена." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Выпадкова" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Увайсці з асноўным паролем" }, - "loggingInAs": { - "message": "Увайсці як" - }, "rememberEmail": { "message": "Запомніць электронную пошту" }, - "notYou": { - "message": "Не вы?" - }, "newAroundHere": { "message": "Упершыню тут?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Ініцыяваны ўваход" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "Апавяшчэнне было адпраўлена на вашу прыладу." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "Пераключыць лічыльнік сімвалаў", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Вы спрабуеце ўвайсці?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Спроба ўваходу $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Час" }, - "confirmLogIn": { - "message": "Пацвердзіць уваход" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Адхіліць уваход" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Уваход пацверджаны для $EMAIL$ на $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "Гэты запыт больш не дзейнічае." }, - "confirmLoginAtemptForMail": { - "message": "Пацвердзіць спробу ўваходу для $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Запытаны ўваход" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Вызначаны ненадзейны пароль, які знойдзены ва ўцечках даных. Выкарыстоўвайце надзейныя і ўнікальныя паролі для абароны свайго ўліковага запісу. Вы сапраўды хочаце выкарыстоўваць гэты пароль?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Праверыць у вядомых уцечках даных для гэтага пароля" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Запытаць ухваленне адміністратара" }, - "approveWithMasterPassword": { - "message": "Ухваліць з дапамогай асноўнага пароля" - }, "region": { "message": "Рэгіён" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Ваш запыт адпраўлены адміністратару." }, - "youWillBeNotifiedOnceApproved": { - "message": "Вы атрымаеце апавяшчэння пасля яго ўхвалення." - }, "troubleLoggingIn": { "message": "Праблемы з уваходам?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index 9770e24c5ad..6a1c0983278 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Грешка" }, + "decryptionError": { + "message": "Грешка при дешифриране" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Битоурден не може да дешифрира елементите от трезора посочени по-долу." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Свържете се с поддръжката", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "за да избегнете загубата на данни.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "януари" }, @@ -529,10 +543,6 @@ "message": "Включване на специални знаци", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Брой думи" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Впишете се в Битуорден" }, + "enterTheCodeSentToYourEmail": { + "message": "Въведете кода изпратен на е-пощата Ви" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Въведете кода от Вашето приложение за удостоверяване" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Натиснете бутона на своя YubiKey за удостоверяване" + }, "logInWithPasskey": { "message": "Вписване със секретен ключ" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Подсказка за главната парола" }, + "passwordStrengthScore": { + "message": "Оценка на сложността на паролата: $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Присъединяване към организацията" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Въведете е-пощата на регистрацията си и подсказката за паролата ще Ви бъде изпратена" }, - "passwordHint": { - "message": "Подсказка за паролата" - }, - "enterEmailToGetHint": { - "message": "Въведете адреса на е-пощата си, за да получите подсказка за главната парола." - }, "getMasterPasswordHint": { "message": "Получете подсказване за главната парола" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "Удостоверяването беше отменено или отне твърде много време. Моля, опитайте отново." }, + "openInNewTab": { + "message": "Отваряне в нов раздел" + }, "invalidVerificationCode": { "message": "Грешен код за потвърждаване" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Запомняне" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Не ме питайте отново на това устройство за 30 дни" + }, "sendVerificationCodeEmailAgain": { "message": "Повторно изпращане на писмото за потвърждение" }, "useAnotherTwoStepMethod": { "message": "Използвайте друг начин на двустепенно удостоверяване" }, + "selectAnotherMethod": { + "message": "Изберете друг метод", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Използване на код за възстановяване" + }, "insertYubiKey": { "message": "Поставете устройството на YubiKey в USB порт на компютъра и натиснете бутона на устройството." }, @@ -871,6 +906,15 @@ "message": "Удостоверяване чрез Duo Security за организацията ви, с ползване на приложението Duo Mobile, SMS, телефонен разговор или устройство U2F.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Потвърдете самоличността си" + }, + "weDontRecognizeThisDevice": { + "message": "Това устройство е непознато. Въведете кода изпратен на е-пощата Ви, за да потвърдите самоличността си." + }, + "continueLoggingIn": { + "message": "Продължаване с вписването" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Настройки на двустепенното удостоверяване" }, + "selectTwoStepLoginMethod": { + "message": "Изберете начин за двустепенно удостоверяване" + }, "selfHostedEnvironment": { "message": "Собствена среда" }, - "selfHostedEnvironmentFooter": { - "message": "Укажете базовия адрес за собствената ви инсталирана среда на Bitwarden." - }, "selfHostedBaseUrlHint": { "message": "Посочете базовия адрес на Вашата собствена инсталация на Битуорден. Пример: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Специална среда" }, - "customEnvironmentFooter": { - "message": "За специални случаи. Може да укажете основните адреси на всяка ползвана услуга поотделно." - }, "baseUrl": { "message": "Адрес на сървъра" }, @@ -956,6 +997,9 @@ "no": { "message": "Не" }, + "location": { + "message": "Местоположение" + }, "overwritePassword": { "message": "Обновяване на паролата" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Покупка на платен абонамент" }, - "premiumPurchaseAlert": { - "message": "Може да платите абонамента си през сайта bitwarden.com. Искате ли да го посетите сега?" - }, "premiumPurchaseAlertV2": { "message": "Можете да закупите платената версия от настройките на регистрацията си, в приложението по уеб на Битуорден." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Изискване на парола или ПИН при стартиране на приложението" }, + "requirePasswordWithoutPinOnStart": { + "message": "Изискване на парола при стартиране на приложението" + }, "recommendedForSecurity": { "message": "Препоръчително от гледна точка на сигурността." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Идентификация WebAuthn" }, + "readSecurityKey": { + "message": "Прочитане на ключа за сигурност" + }, + "awaitingSecurityKeyInteraction": { + "message": "Изчакване на действие с ключ за сигурност…" + }, "hideEmail": { "message": "Скриване на е-пощата ми от получателите." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Използвайте конфигурираната входяща кутия за събиране на всичко." }, + "useThisEmail": { + "message": "Използване на тази е-поща" + }, "random": { "message": "Произволно" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ отказа заявката Ви. Свържете се с доставчика на услугата за съдействие.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ отказа заявката Ви: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Не може да бъде получен идентификатор на маскиран чрез е-поща акаунт от $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Вписване с главната парола" }, - "loggingInAs": { - "message": "Вписване като" - }, "rememberEmail": { "message": "Запомняне на е-пощата" }, - "notYou": { - "message": "Това не сте Вие?" - }, "newAroundHere": { "message": "За пръв път ли сте тук?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Вписването е стартирано" }, + "logInRequestSent": { + "message": "Заявката е изпратена" + }, "notificationSentDevice": { "message": "Към устройството Ви е изпратено известие." }, "aNotificationWasSentToYourDevice": { "message": "Към устройството Ви е изпратено известие" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Уверете се, че регистрацията Ви е отключена и че уникалната фраза съвпада с другото устройство" + "notificationSentDevicePart1": { + "message": "Отключете Битоурден на устройството си или в " + }, + "notificationSentDeviceAnchor": { + "message": "приложението по уеб" + }, + "notificationSentDevicePart2": { + "message": "Уверете се, че уникалната фраза съвпада с тази по-долу, преди да одобрите." }, "needAnotherOptionV1": { "message": "Предпочитате друг вариант?" @@ -2759,11 +2839,11 @@ "message": "Превключване на броя знаци", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Опитвате се да се впишете ли?" + "areYouTryingToAccessYourAccount": { + "message": "Опитвате ли се да получите достъп до акаунта си?" }, - "logInAttemptBy": { - "message": "Опит за вписване от $EMAIL$", + "accessAttemptBy": { + "message": "Опит за достъп от $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Време" }, - "confirmLogIn": { - "message": "Потвърждаване на вписването" + "confirmAccess": { + "message": "Разрешаване на достъпа" }, - "denyLogIn": { - "message": "Отказване на вписването" + "denyAccess": { + "message": "Отказване на достъпа" }, "logInConfirmedForEmailOnDevice": { "message": "Вписването за $EMAIL$ на $DEVICE$ е одобрено", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "Тази заявка вече не е активна." }, - "confirmLoginAtemptForMail": { - "message": "Потвърждаване на заявката за вписване за $EMAIL$", + "confirmAccessAttempt": { + "message": "Потвърждаване на заявката за достъп за $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Направена е заявка за вписване" }, + "accountAccessRequested": { + "message": "Има заявка за достъп до акаунта" + }, "creatingAccountOn": { "message": "Създаване на регистрация в" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Разпозната е слаба парола. Използвайте силна парола, за да защитете данните си. Наистина ли искате да използвате слаба парола?" }, + "useThisPassword": { + "message": "Използване на тази парола" + }, + "useThisUsername": { + "message": "Използване на това потребителско име" + }, "checkForBreaches": { "message": "Проверяване в известните случаи на изтекли данни за тази парола" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Подаване на заявка за одобрение от администратор" }, - "approveWithMasterPassword": { - "message": "Одобряване с главната парола" - }, "region": { "message": "Регион" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Вашата заявка беше изпратена до администратора Ви." }, - "youWillBeNotifiedOnceApproved": { - "message": "Ще получите известие, когато тя бъде одобрена." - }, "troubleLoggingIn": { "message": "Имате проблем с вписването?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Вашата регистрация изисква двустепенно удостоверяване чрез Duo." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Вашият акаунт изисква вписване чрез двустепенно удостоверяване с Duo. Следвайте стъпките по-долу, за да завършите вписването." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Следвайте стъпките по-долу, за да завършите вписването." + }, "launchDuo": { "message": "Стартирайте Duo в браузъра" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "Не могат да бъдат открити свободни портове за еднократната идентификация." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Отключването с биометрични данни не е налично, тъй като първо се изисква отключване чрез ПИН или парола." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Отключването с биометрични данни не е налично в момента." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Отключването с биометрични данни не е налично поради неправилно настроени системни файлове." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Отключването с биометрични данни не е налично поради неправилно настроени системни файлове." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Отключването с биометрични данни не е налично, тъй като не е включено за $EMAIL$ в приложението на Битуорден за компютър.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Отключването с биометрични данни не е налично по неизвестна причина." + }, "authorize": { "message": "Упълномощаване" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Потвърждаване на употребата на SSH ключа" }, + "agentForwardingWarningTitle": { + "message": "Внимание: Пренасочване на агента" + }, + "agentForwardingWarningText": { + "message": "Тази заявка идва от отдалечено устройство, в което сте вписан(а)" + }, "sshkeyApprovalMessageInfix": { "message": "иска достъп до" }, + "sshkeyApprovalMessageSuffix": { + "message": "за да" + }, + "sshActionLogin": { + "message": "се удостоверите в сървър" + }, + "sshActionSign": { + "message": "подпишете съобщение" + }, + "sshActionGitSign": { + "message": "подпишете подаване в git" + }, "unknownApplication": { "message": "Приложение" }, - "sshKeyPasswordUnsupported": { - "message": "Внасянето на SSH ключ, които са защитени с парола, все още не се поддържа" - }, "invalidSshKey": { "message": "SSH ключът е неправилен" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Внасяне на ключ от буфера за обмен" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH ключът е внесен успешно" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Промяна на е-пощата" + }, + "allowScreenshots": { + "message": "Позволяване на заснемането на екрана" + }, + "allowScreenshotsDesc": { + "message": "Позволява настолното приложение на Битурден да бъде заснемано при правене на екранни снимки и видимо при достъп до компютъра чрез отдалечен работен плот. Изключването на това ще забрани достапа на някои външни екрани." + }, + "confirmWindowStillVisibleTitle": { + "message": "Потвърждаване, че прозорецът все още е видим" + }, + "confirmWindowStillVisibleContent": { + "message": "Моля, потвърдете, че прозорецът все още е видим." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Необходимо е обновяване на добавката" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "Добавката за браузъра, която използвате, е остаряла. Моля, обновете я или изключете интеграцията за проверка на пръстов отпечатък в браузъра от настройките на самостоятелното приложение." + }, + "changeAtRiskPassword": { + "message": "Промяна на парола в риск" } } diff --git a/apps/desktop/src/locales/bn/messages.json b/apps/desktop/src/locales/bn/messages.json index 8fe871a956f..68848c9ef2a 100644 --- a/apps/desktop/src/locales/bn/messages.json +++ b/apps/desktop/src/locales/bn/messages.json @@ -249,6 +249,20 @@ "error": { "message": "ত্রুটি" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "জানুয়ারী" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "শব্দের সংখ্যা" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Master password hint" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "পাসওয়ার্ড ইঙ্গিত" - }, - "enterEmailToGetHint": { - "message": "আপনার মূল পাসওয়ার্ডের ইঙ্গিতটি পেতে আপনার অ্যাকাউন্টের ইমেল ঠিকানা প্রবেশ করুন।" - }, "getMasterPasswordHint": { "message": "মূল পাসওয়ার্ডের ইঙ্গিত পান" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "আমাকে মনে রাখবেন" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "আবার যাচাইকরণ কোড ইমেইলে প্রেরণ করুন" }, "useAnotherTwoStepMethod": { "message": "অন্য দ্বি-পদক্ষেপ প্রবেশ পদ্ধতি ব্যবহার করুন" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "আপনার কম্পিউটারের ইউএসবি পোর্টে আপনার YubiKey ঢোকান, তারপরে তার বোতামটি স্পর্শ করুন।" }, @@ -871,6 +906,15 @@ "message": "Duo Mobile app, এসএমএস, ফোন কল, বা U2F সুরক্ষা কী ব্যবহার করে আপনার সংস্থার জন্য Duo Security এর মাধ্যমে যাচাই করুন।", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "দ্বি-পদক্ষেপ লগইন বিকল্প" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "স্ব-হোস্টকৃত পরিবেশ" }, - "selfHostedEnvironmentFooter": { - "message": "আপনার অন-প্রাঙ্গনে হোস্টকৃত Bitwarden ইনস্টলেশনটির বেস URL উল্লেখ করুন।" - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "পছন্দসই পরিবেশ" }, - "customEnvironmentFooter": { - "message": "উন্নত ব্যবহারকারীদের জন্য। আপনি স্বতন্ত্রভাবে প্রতিটি পরিষেবার মূল URL নির্দিষ্ট করতে পারেন।" - }, "baseUrl": { "message": "সার্ভার URL" }, @@ -956,6 +997,9 @@ "no": { "message": "না" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "ওভাররাইট পাসওয়ার্ড" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "প্রিমিয়াম কিনুন" }, - "premiumPurchaseAlert": { - "message": "আপনি bitwarden.com ওয়েব ভল্টে প্রিমিয়াম সদস্যতা কিনতে পারেন। আপনি কি এখনই ওয়েবসাইটটি দেখতে চান?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Require password or PIN on app start" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Recommended for security." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Hide my email address from recipients." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, "rememberEmail": { "message": "Remember email" }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "Toggle character count", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Are you trying to log in?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Login attempt by $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Time" }, - "confirmLogIn": { - "message": "Confirm login" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Deny login" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Login confirmed for $EMAIL$ on $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "confirmLoginAtemptForMail": { - "message": "Confirm login attempt for $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Log in requested" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "region": { "message": "Region" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/bs/messages.json b/apps/desktop/src/locales/bs/messages.json index e90a7e16c89..885af404f95 100644 --- a/apps/desktop/src/locales/bs/messages.json +++ b/apps/desktop/src/locales/bs/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Greška" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januar" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Broj riječi" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Master password hint" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Nagovještaj lozinke" - }, - "enterEmailToGetHint": { - "message": "Unesite E-Mail adresu Vašeg računa da biste dobili nagovještaj o mogućoj glavnoj lozinki." - }, "getMasterPasswordHint": { "message": "Dobijte nagovještaj glavne lozinke" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Neispravan verifikacijski kod" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Zapamti me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Ponovo pošaljite E-Mail sa verifikacionim kodom" }, "useAnotherTwoStepMethod": { "message": "Koristite drugi način prijavljivanja u dva koraka" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Ubaci svoj YubiKey u USB slot računara, a zatim dodirni njegovu tipku." }, @@ -871,6 +906,15 @@ "message": "Potvrdi sa Duo Security za svoju organizaciju pomoću aplikacije Duo Mobile, SMS-om, telefonskim pozivom ili U2F sigurnosnim ključem.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Opcije prijave u dva koraka" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Vlastito hosting okruženje" }, - "selfHostedEnvironmentFooter": { - "message": "Navedite osnovni URL vaše lokalne Bitwarden instalacije." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Prilagođeno okruženje" }, - "customEnvironmentFooter": { - "message": "Za napredne korisnike. Samostalno možeš odrediti osnovni URL svake usluge." - }, "baseUrl": { "message": "URL servera" }, @@ -956,6 +997,9 @@ "no": { "message": "Ne" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Prebriši lozinku" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Kupi premium članstvo" }, - "premiumPurchaseAlert": { - "message": "Svojim članstvom možeš upravljati na bitwarden.com web trezoru. Želiš li sada posjetiti web stranicu?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Require password or PIN on app start" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Recommended for security." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Hide my email address from recipients." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, "rememberEmail": { "message": "Remember email" }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "Toggle character count", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Are you trying to log in?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Login attempt by $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Time" }, - "confirmLogIn": { - "message": "Confirm login" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Deny login" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Login confirmed for $EMAIL$ on $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "confirmLoginAtemptForMail": { - "message": "Confirm login attempt for $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Log in requested" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "region": { "message": "Region" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index ace7e5defc0..5707fd0c0ff 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -27,7 +27,7 @@ "message": "Nota segura" }, "typeSshKey": { - "message": "SSH key" + "message": "Clau SSH" }, "folders": { "message": "Carpetes" @@ -64,7 +64,7 @@ } }, "welcomeBack": { - "message": "Welcome back" + "message": "Benvingut/da de nou" }, "moveToOrgDesc": { "message": "Trieu una organització a la qual vulgueu desplaçar aquest element. El trasllat a una organització transfereix la propietat de l'element a aquesta organització. Ja no sereu el propietari directe d'aquest element una vegada s'haja mogut." @@ -181,16 +181,16 @@ "message": "Adreça" }, "sshPrivateKey": { - "message": "Private key" + "message": "Clau privada" }, "sshPublicKey": { - "message": "Public key" + "message": "Clau pública" }, "sshFingerprint": { - "message": "Fingerprint" + "message": "Empremta digital" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "Tipus de clau" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -205,37 +205,37 @@ "message": "RSA 4096-Bit" }, "sshKeyGenerated": { - "message": "A new SSH key was generated" + "message": "S'ha generat una nova clau SSH" }, "sshKeyWrongPassword": { - "message": "The password you entered is incorrect." + "message": "La contrasenya que heu introduït és incorrecta." }, "importSshKey": { - "message": "Import" + "message": "Importa" }, "confirmSshKeyPassword": { - "message": "Confirm password" + "message": "Confirma contrasenya" }, "enterSshKeyPasswordDesc": { - "message": "Enter the password for the SSH key." + "message": "Introduïu la contrasenya per a la clau SSH." }, "enterSshKeyPassword": { - "message": "Enter password" + "message": "Introduïu la contrasenya" }, "sshAgentUnlockRequired": { - "message": "Please unlock your vault to approve the SSH key request." + "message": "Desbloquegeu la vostra caixa forta per aprovar la sol·licitud de clau SSH." }, "sshAgentUnlockTimeout": { - "message": "SSH key request timed out." + "message": "La sol·licitud de clau SSH s'ha esgotat." }, "enableSshAgent": { - "message": "Enable SSH agent" + "message": "Habilita agent SSH" }, "enableSshAgentDesc": { - "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + "message": "Habiliteu l'agent SSH per signar sol·licituds SSH directament des de la caixa forta de Bitwarden." }, "enableSshAgentHelp": { - "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + "message": "L'agent SSH és un servei dirigit als desenvolupadors que us permet signar sol·licituds SSH directament des de la caixa forta de Bitwarden." }, "premiumRequired": { "message": "Premium requerit" @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Error de desxifrat" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden no ha pogut desxifrar els elements de la caixa forta que s'indiquen a continuació." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contacteu amb el servei d'atenció al client", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "per evitar la pèrdua de dades addicionals.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Gener" }, @@ -323,7 +337,7 @@ "message": "Genera contrasenya" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "Genera frase de pas" }, "type": { "message": "Tipus" @@ -461,13 +475,13 @@ "message": "Copia contrasenya" }, "regenerateSshKey": { - "message": "Regenerate SSH key" + "message": "Regenera clau SSH" }, "copySshPrivateKey": { - "message": "Copy SSH private key" + "message": "Copia la clau privada SSH" }, "copyPassphrase": { - "message": "Copy passphrase", + "message": "Copia frase de pas", "description": "Copy passphrase to clipboard" }, "copyUri": { @@ -498,11 +512,11 @@ "message": "Caràcters especials (!@#$%^&*)" }, "include": { - "message": "Include", + "message": "Inclou", "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "Inclou majúscules", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -510,7 +524,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "Inclou minúscules", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -518,7 +532,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "Inclou números", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -526,13 +540,9 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "Inclou caràcters especials", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Nombre de paraules" }, @@ -561,11 +571,11 @@ "description": "deprecated. Use avoidAmbiguous instead." }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "Eviteu caràcters ambigus", "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { - "message": "Enterprise policy requirements have been applied to your generator options.", + "message": "Els requisits de la política empresarial s'han aplicat a les opcions del generador.", "description": "Indicates that a policy limits the credential generator screen." }, "searchCollection": { @@ -624,7 +634,7 @@ "message": "Crea un compte" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Nou a Bitwarden?" }, "setAStrongPassword": { "message": "Estableix una contrasenya segura" @@ -636,16 +646,25 @@ "message": "Inicia sessió" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Inicia sessió a Bitwarden" + }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Inicieu sessió amb la clau de pas" }, "loginWithDevice": { - "message": "Log in with device" + "message": "Inici de sessió amb dispositiu" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "Usa inici de sessió únic" }, "submit": { "message": "Envia" @@ -690,11 +709,20 @@ "masterPassHintLabel": { "message": "Pista de la contrasenya mestra" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { - "message": "Uneix-te a l'organització" + "message": "Uniu-vos a l'organització" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "Uniu-vos a $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -709,22 +737,16 @@ "message": "Configuració" }, "accountEmail": { - "message": "Account email" + "message": "Correu electrònic del compte" }, "requestHint": { - "message": "Request hint" + "message": "Sol·licita pista" }, "requestPasswordHint": { - "message": "Request password hint" + "message": "Sol·licita pista de la contrasenya" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { - "message": "Enter your account email address and your password hint will be sent to you" - }, - "passwordHint": { - "message": "Pista per a la contrasenya" - }, - "enterEmailToGetHint": { - "message": "Introduïu l'adreça electrònica del vostre compte per rebre la contrasenya mestra." + "message": "Introduïu l'adreça de correu electrònic del compte i se us enviarà la pista de contrasenya" }, "getMasterPasswordHint": { "message": "Obteniu la pista de contrasenya mestra" @@ -764,10 +786,10 @@ "message": "El vostre compte s'ha creat correctament. Ara ja podeu iniciar sessió." }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "S'ha creat el vostre compte nou!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "Heu iniciat sessió!" }, "masterPassSent": { "message": "Hem enviat un correu electrònic amb la vostra contrasenya mestra." @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "L'autenticació s'ha cancel·lat o ha tardat massa. Torna-ho a provar." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Codi de verificació no vàlid" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Recorda'm" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Envia el codi de verificació altra vegada" }, "useAnotherTwoStepMethod": { "message": "Utilitzeu un altre mètode d'inici de sessió en dues passes" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Introduïu la vostra YubiKey al port USB de l'ordinador i, a continuació, premeu el seu botó." }, @@ -871,6 +906,15 @@ "message": "Verifiqueu amb Duo Security per a la vostra organització mitjançant l'aplicació Duo Mobile, SMS, trucada telefònica o clau de seguretat U2F.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "No reconeixem aquest dispositiu. Introduïu el codi que us hem enviat al correu electrònic per verificar la identitat." + }, + "continueLoggingIn": { + "message": "Continua l'inici de sessió" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -881,7 +925,7 @@ "message": "Correu electrònic" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "Introduïu el codi que us hem enviat al correu electrònic." }, "loginUnavailable": { "message": "Inici de sessió no disponible" @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Opcions d'inici de sessió en dues passes" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Entorn d'allotjament propi" }, - "selfHostedEnvironmentFooter": { - "message": "Especifiqueu l'URL base de la vostra instal·lació Bitwarden allotjada en un entorn propi." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,20 +957,17 @@ "customEnvironment": { "message": "Entorn personalitzat" }, - "customEnvironmentFooter": { - "message": "Per a usuaris avançats. Podeu especificar l'URL base de cada servei independentment." - }, "baseUrl": { "message": "URL del servidor" }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "Temps d'espera d'autenticació" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "La sessió d'autenticació s'ha esgotat. Reinicieu el procés d'inici de sessió." }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "URL del servidor autoallotjat", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -956,6 +997,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Sobreescriu la contrasenya" }, @@ -969,22 +1013,22 @@ "message": "Desconnectat" }, "loggedOutDesc": { - "message": "You have been logged out of your account." + "message": "Heu tancat la sessió del compte." }, "loginExpired": { "message": "La vostra sessió ha caducat." }, "restartRegistration": { - "message": "Restart registration" + "message": "Reinicia el registre" }, "expiredLink": { "message": "Enllaç caducat" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "Reinicieu el registre o proveu d'iniciar sessió." }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "És possible que ja tingueu un compte" }, "logOutConfirmation": { "message": "Segur que voleu tancar la sessió?" @@ -1073,16 +1117,16 @@ "message": "La caixa forta està bloquejada. Comproveu la contrasenya mestra per continuar." }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "El compte està bloquejat" }, "or": { - "message": "or" + "message": "o" }, "unlockWithBiometrics": { - "message": "Unlock with biometrics" + "message": "Desbloqueja amb dades biomètriques" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "Desbloqueja amb contrasenya mestra" }, "unlock": { "message": "Desbloqueja" @@ -1113,7 +1157,7 @@ "message": "Temps d'espera de la caixa forta" }, "vaultTimeout1": { - "message": "Timeout" + "message": "Temps d'espera" }, "vaultTimeoutDesc": { "message": "Trieu quan es tancarà la vostra caixa forta i feu l'acció seleccionada." @@ -1320,7 +1364,7 @@ "description": "Copy credit card number" }, "copyEmail": { - "message": "Copy email" + "message": "Copia el correu electrònic" }, "copySecurityCode": { "message": "Copia el codi de seguretat", @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Compra Premium" }, - "premiumPurchaseAlert": { - "message": "Podeu comprar la vostra subscripció a la caixa forta web de bitwarden.com. Voleu visitar el lloc web ara?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1393,13 +1434,13 @@ "message": "Historial de les contrasenyes" }, "generatorHistory": { - "message": "Generator history" + "message": "Historial del generador" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "Neteja l'historial del generador" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "Si continueu, totes les entrades se suprimiran permanentment de l'historial del generador. Esteu segur que voleu continuar?" }, "clear": { "message": "Esborra", @@ -1409,10 +1450,10 @@ "message": "No hi ha cap contrasenya a llistar." }, "clearHistory": { - "message": "Clear history" + "message": "Neteja l'historial" }, "nothingToShow": { - "message": "Nothing to show" + "message": "Res a mostrar" }, "nothingGeneratedRecently": { "message": "You haven't generated anything recently" @@ -1492,7 +1533,7 @@ } }, "copySuccessful": { - "message": "Copy Successful" + "message": "Còpia correcta" }, "errorRefreshingAccessToken": { "message": "Access Token Refresh Error" @@ -1597,31 +1638,31 @@ "message": "Format de fitxer" }, "fileEncryptedExportWarningDesc": { - "message": "This file export will be password protected and require the file password to decrypt." + "message": "Aquesta exportació de fitxers estarà protegida amb contrasenya i requerirà la contrasenya del fitxer per desxifrar-la." }, "filePassword": { "message": "Contrasenya del fitxer" }, "exportPasswordDescription": { - "message": "This password will be used to export and import this file" + "message": "Aquesta contrasenya s'utilitzarà per exportar i importar aquest fitxer" }, "accountRestrictedOptionDescription": { - "message": "Use your account encryption key, derived from your account's username and Master Password, to encrypt the export and restrict import to only the current Bitwarden account." + "message": "Utilitzeu la clau de xifratge del vostre compte, derivada del nom d'usuari i la contrasenya mestra, per xifrar l'exportació i restringir la importació només al compte de Bitwarden actual." }, "passwordProtected": { "message": "Protegit amb contrasenya" }, "passwordProtectedOptionDescription": { - "message": "Set a file password to encrypt the export and import it to any Bitwarden account using the password for decryption." + "message": "Establiu una contrasenya per xifrar l'exportació i importeu-la a qualsevol compte de Bitwarden mitjançant aquesta contrasenya." }, "exportTypeHeading": { "message": "Tipus d'exportació" }, "accountRestricted": { - "message": "Account restricted" + "message": "Compte restringit" }, "filePasswordAndConfirmFilePasswordDoNotMatch": { - "message": "“File password” and “Confirm file password“ do not match." + "message": "\"Contrasenya del fitxer\" i \"Confirma contrasenya del fitxer\" no coincideixen." }, "hCaptchaUrl": { "message": "Url hCaptcha", @@ -1729,7 +1770,7 @@ "message": "Configuració addicional de Windows Hello" }, "unlockWithPolkit": { - "message": "Unlock with system authentication" + "message": "Desbloqueja amb l'autenticació del sistema" }, "windowsHelloConsentMessage": { "message": "Verifica per Bitwarden." @@ -1747,7 +1788,7 @@ "message": "Sol·liciteu Windows Hello en iniciar" }, "autoPromptPolkit": { - "message": "Ask for system authentication on launch" + "message": "Sol·licita l'autenticació del sistema a l'inici" }, "autoPromptTouchId": { "message": "Sol·liciteu Touch ID en iniciar" @@ -1755,11 +1796,14 @@ "requirePasswordOnStart": { "message": "Requereix contrasenya o PIN a l'inici de l'aplicació" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Recomanat per seguretat." }, "lockWithMasterPassOnRestart1": { - "message": "Lock with master password on restart" + "message": "Bloqueja amb la contrasenya mestra en reiniciar" }, "deleteAccount": { "message": "Suprimeix el compte" @@ -1771,7 +1815,7 @@ "message": "La supressió del compte és permanent. No es pot desfer." }, "cannotDeleteAccount": { - "message": "Cannot delete account" + "message": "No es pot suprimir el compte" }, "cannotDeleteAccountDesc": { "message": "This action cannot be completed because your account is owned by an organization. Contact your organization administrator for additional details." @@ -1887,7 +1931,7 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "de $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -1960,7 +2004,7 @@ "message": "en qualsevol moment." }, "byContinuingYouAgreeToThe": { - "message": "By continuing, you agree to the" + "message": "En continuar, acceptes el" }, "and": { "message": "i" @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Autenticar WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Amagueu la meua adreça de correu electrònic als destinataris." }, @@ -2234,7 +2284,7 @@ "message": "Es requereix verificació per correu electrònic" }, "emailVerifiedV2": { - "message": "Email verified" + "message": "Correu electrònic verificat" }, "emailVerificationRequiredDesc": { "message": "Heu de verificar el vostre correu electrònic per utilitzar aquesta característica." @@ -2348,7 +2398,7 @@ "message": "El temps d'espera de la caixa forta supera les restriccions establertes per la vostra organització." }, "inviteAccepted": { - "message": "Invitation accepted" + "message": "Invitació acceptada" }, "resetPasswordPolicyAutoEnroll": { "message": "Inscripció automàtica" @@ -2420,7 +2470,7 @@ "message": "Canvia de compte" }, "alreadyHaveAccount": { - "message": "Already have an account?" + "message": "Ja teniu un compte?" }, "options": { "message": "Opcions" @@ -2441,10 +2491,10 @@ } }, "exportingOrganizationVaultTitle": { - "message": "Exporting organization vault" + "message": "S'està exportant la caixa forta de l’organització" }, "exportingOrganizationVaultDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "message": "Només s'exportarà la caixa forta de l'organització associada a $ORGANIZATION$. No s'inclouran els elements de les caixes fortes individuals ni d'altres organitzacions.", "placeholders": { "organization": { "content": "$1", @@ -2456,7 +2506,7 @@ "message": "Bloquejat" }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "La caixa forta està bloquejada" }, "unlocked": { "message": "Desbloquejat" @@ -2478,10 +2528,10 @@ "message": "Genera un nom d'usuari" }, "generateEmail": { - "message": "Generate email" + "message": "Genera correu electrònic" }, "spinboxBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$.", + "message": "El valor ha d'estar entre $MIN$ i $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2495,7 +2545,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "message": " Utilitzeu $RECOMMENDED$ caràcters o més per generar una contrasenya segura.", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2505,7 +2555,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "message": " Utilitzeu $RECOMMENDED$ paraules o més per generar una frase de pas segura.", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Utilitzeu la safata d'entrada global configurada del vostre domini." }, + "useThisEmail": { + "message": "Utilitza aquest correu" + }, "random": { "message": "Aleatori" }, @@ -2558,7 +2611,7 @@ "message": "Genera un àlies de correu electrònic amb un servei de reenviament extern." }, "forwarderDomainName": { - "message": "Email domain", + "message": "Domini del correu electrònic", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { @@ -2566,7 +2619,7 @@ "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { - "message": "$SERVICENAME$ error: $ERRORMESSAGE$", + "message": "Error de $SERVICENAME$: $ERRORMESSAGE$", "description": "Reports an error returned by a forwarding service to the user.", "placeholders": { "servicename": { @@ -2580,11 +2633,11 @@ } }, "forwarderGeneratedBy": { - "message": "Generated by Bitwarden.", + "message": "Generat per Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "Lloc web: $WEBSITE$. Generat per Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Inici de sessió amb contrasenya mestra" }, - "loggingInAs": { - "message": "Has iniciat sessió com" - }, "rememberEmail": { "message": "Recorda el correu electronic" }, - "notYou": { - "message": "No sou vosaltres?" - }, "newAroundHere": { "message": "Nou per ací?" }, @@ -2722,17 +2793,26 @@ "loginInitiated": { "message": "S'ha iniciat la sessió" }, + "logInRequestSent": { + "message": "Sol·licitud enviada" + }, "notificationSentDevice": { "message": "S'ha enviat una notificació al vostre dispositiu." }, "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "message": "S'ha enviat una notificació al vostre dispositiu" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "aplicació web" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "Necessiteu una altra opció?" }, "fingerprintMatchInfo": { "message": "Assegureu-vos que la vostra caixa forta estiga desbloquejada i que la frase d'empremta digital coincidisca amb l'altre dispositiu." @@ -2741,13 +2821,13 @@ "message": "Frase d'empremta digital" }, "youWillBeNotifiedOnceTheRequestIsApproved": { - "message": "You will be notified once the request is approved" + "message": "Se us notificarà un vegada s'haja aprovat la sol·licitud" }, "needAnotherOption": { "message": "L'inici de sessió amb el dispositiu ha d'estar activat a la configuració de l'aplicació Bitwarden. Necessiteu una altra opció?" }, "viewAllLogInOptions": { - "message": "View all log in options" + "message": "Veure totes les opcions d'inici de sessió" }, "viewAllLoginOptions": { "message": "Veure totes les opcions d'inici de sessió" @@ -2759,11 +2839,11 @@ "message": "Commuta el recompte de caràcters", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Esteu intentant iniciar sessió?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Intent d'inici de sessió per $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Hora" }, - "confirmLogIn": { - "message": "Confirma l'inici de sessió" + "confirmAccess": { + "message": "Confirmeu l'accés" }, - "denyLogIn": { - "message": "Denega l'inici de sessió" + "denyAccess": { + "message": "Denega l'accés" }, "logInConfirmedForEmailOnDevice": { "message": "S'ha confirmat l'inici de sessió per a $EMAIL$ a $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "Aquesta sol·licitud ja no és vàlida." }, - "confirmLoginAtemptForMail": { - "message": "Confirmeu l'intent d'inici de sessió per a $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "S'ha sol·licitat inici de sessió" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creant compte en" }, @@ -2842,16 +2925,16 @@ "message": "Seguiu l'enllaç del correu electrònic enviat a" }, "andContinueCreatingYourAccount": { - "message": "and continue creating your account." + "message": "i continua creant el compte." }, "noEmail": { - "message": "No email?" + "message": "Sense correu electrònic?" }, "goBack": { "message": "Torna arrere" }, "toEditYourEmailAddress": { - "message": "to edit your email address." + "message": "per editar l'adreça de correu electrònic." }, "exposedMasterPassword": { "message": "Contrasenya mestra exposada" @@ -2865,17 +2948,23 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Contrasenya feble identificada i trobada en una filtració de dades. Utilitzeu una contrasenya única i segura per protegir el vostre compte. Esteu segur que voleu utilitzar aquesta contrasenya?" }, + "useThisPassword": { + "message": "Utilitzeu aquesta contrasenya" + }, + "useThisUsername": { + "message": "Utilitzeu aquest nom d'usuari" + }, "checkForBreaches": { "message": "Comproveu les filtracions de dades conegudes per a aquesta contrasenya" }, "loggedInExclamation": { - "message": "Logged in!" + "message": "Connectat!" }, "important": { "message": "Important:" }, "accessing": { - "message": "Accessing" + "message": "Accedint a" }, "accessTokenUnableToBeDecrypted": { "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Sol·liciteu l'aprovació de l'administrador" }, - "approveWithMasterPassword": { - "message": "Aprova amb contrasenya mestra" - }, "region": { "message": "Regió" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "La vostra sol·licitud s'ha enviat a l'administrador." }, - "youWillBeNotifiedOnceApproved": { - "message": "Se us notificarà una vegada aprovat." - }, "troubleLoggingIn": { "message": "Teniu problemes per iniciar la sessió?" }, @@ -3072,7 +3155,7 @@ "message": "Submenú" }, "toggleSideNavigation": { - "message": "Toggle side navigation" + "message": "Canvia a la navegació lateral" }, "skipToContent": { "message": "Vés al contingut" @@ -3130,7 +3213,7 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + "message": "S'ha produït un error en connectar amb el servei Duo. Utilitzeu un mètode d'inici de sessió en dos passos diferent o poseu-vos en contacte amb Duo per obtenir ajuda." }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Inicieu DUO i seguiu els passos per finalitzar la sessió." @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Es requereix l'inici de sessió en dos passos de DUO al vostre compte." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Inicia Duo al navegador" }, @@ -3317,7 +3406,7 @@ "message": "S'ha produït un error en assignar la carpeta de destinació." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Mostra elements en $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -3327,7 +3416,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Torna a $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -3341,7 +3430,7 @@ "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Suprimeix $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -3354,31 +3443,70 @@ "message": "Dades" }, "fileSends": { - "message": "File Sends" + "message": "Sends de fitxers" }, "textSends": { - "message": "Text Sends" + "message": "Sends de text" }, "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { - "message": "Authorize" + "message": "Autoritza" }, "deny": { - "message": "Deny" + "message": "Denega" }, "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, - "unknownApplication": { - "message": "An application" + "sshkeyApprovalMessageSuffix": { + "message": "in order to" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, + "unknownApplication": { + "message": "Una aplicació" }, "invalidSshKey": { "message": "The SSH key is invalid" @@ -3389,17 +3517,17 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, "importantNotice": { - "message": "Important notice" + "message": "Noticia important" }, "setupTwoStepLogin": { - "message": "Set up two-step login" + "message": "Configura inici de sessió en dos passos" }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." @@ -3408,7 +3536,7 @@ "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." }, "remindMeLater": { - "message": "Remind me later" + "message": "Recorda-m'ho més tard" }, "newDeviceVerificationNoticePageOneFormContent": { "message": "Do you have reliable access to your email, $EMAIL$?", @@ -3420,15 +3548,36 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "No, jo no" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { "message": "Yes, I can reliably access my email" }, "turnOnTwoStepLogin": { - "message": "Turn on two-step login" + "message": "Activa l'inici de sessió en dos passos" }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Cal actualitzar l'extensió" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index f73f76f44a5..336ea2a795c 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Chyba" }, + "decryptionError": { + "message": "Chyba dešifrování" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nemohl dešifrovat níže uvedené položky v trezoru." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktujte zákaznickou podporu,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "abyste zabránili ztrátě dat.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Leden" }, @@ -529,10 +543,6 @@ "message": "Zahrnout speciální znaky", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Počet slov" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Přihlásit se do Bitwardenu" }, + "enterTheCodeSentToYourEmail": { + "message": "Zadejte kód odeslaný na Váš e-mail" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Zadejte kód z Vaší ověřovací aplikace" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Stiskněte svůj YubiKey pro ověření" + }, "logInWithPasskey": { "message": "Přihlásit se pomocí přístupového klíče" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Nápověda k hlavnímu heslu" }, + "passwordStrengthScore": { + "message": "Skóre síly hesla: $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Přidat se k organizaci" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Zadejte svou e-mailovou adresu, na kterou Vám zašleme nápovědu k heslu" }, - "passwordHint": { - "message": "Nápověda pro heslo" - }, - "enterEmailToGetHint": { - "message": "Zadejte e-mailovou adresu pro zaslání nápovědy k hlavnímu heslu." - }, "getMasterPasswordHint": { "message": "Získat nápovědu pro hlavní heslo" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "Ověření bylo zrušeno nebo trvalo příliš dlouho. Zkuste to znovu." }, + "openInNewTab": { + "message": "Otevřít v nové kartě" + }, "invalidVerificationCode": { "message": "Neplatný ověřovací kód" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Zapamatovat mě" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Neptat se na tomto zařízení 30 dnů" + }, "sendVerificationCodeEmailAgain": { "message": "Znovu zaslat ověřovací kód na e-mail" }, "useAnotherTwoStepMethod": { "message": "Použít jinou metodu dvoufázového přihlášení" }, + "selectAnotherMethod": { + "message": "Vybrat jinou metodu", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Použít obnovovací kód" + }, "insertYubiKey": { "message": "Vložte YubiKey do USB portu Vašeho počítače a stiskněte jeho tlačítko." }, @@ -871,6 +906,15 @@ "message": "Ověření pomocí Duo Security pro Vaši organizaci prostřednictvím aplikace Duo Mobile, SMS, telefonního hovoru nebo bezpečnostního klíče U2F.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Ověřte svou totožnost" + }, + "weDontRecognizeThisDevice": { + "message": "Toto zařízení nepoznáváme. Zadejte kód zaslaný na Váš e-mail pro ověření Vaší totožnosti." + }, + "continueLoggingIn": { + "message": "Pokračovat v přihlášení" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Volby dvoufázového přihlášení" }, + "selectTwoStepLoginMethod": { + "message": "Vyberte metodu dvoufázového přihlášení" + }, "selfHostedEnvironment": { "message": "Vlastní hostované prostředí" }, - "selfHostedEnvironmentFooter": { - "message": "Zadejte základní URL adresu vlastní hostované aplikace Bitwarden." - }, "selfHostedBaseUrlHint": { "message": "Zadejte základní URL adresu Vaší vlastní hostované aplikace Bitwarden. Příklad: https://bitwarden.spolecnost.cz" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Vlastní prostředí" }, - "customEnvironmentFooter": { - "message": "Pro pokročilé uživatele. Můžete zadat základní URL adresu každé služby zvlášť." - }, "baseUrl": { "message": "URL serveru" }, @@ -956,6 +997,9 @@ "no": { "message": "Ne" }, + "location": { + "message": "Umístění" + }, "overwritePassword": { "message": "Přepsat heslo" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Zakoupit členství Premium" }, - "premiumPurchaseAlert": { - "message": "Prémiové členství můžete zakoupit na webové stránce bitwarden.com. Chcete tuto stránku nyní otevřít?" - }, "premiumPurchaseAlertV2": { "message": "Premium si můžete zakoupit v nastavení účtu ve webové aplikaci Bitwarden." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Vyžadovat heslo nebo PIN při spuštění aplikace" }, + "requirePasswordWithoutPinOnStart": { + "message": "Vyžadovat heslo při spuštění aplikace" + }, "recommendedForSecurity": { "message": "Doporučeno pro bezpečnost." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Ověřit WebAuthn" }, + "readSecurityKey": { + "message": "Přečíst bezpečnostní klíč" + }, + "awaitingSecurityKeyInteraction": { + "message": "Čeká se na interakci s bezpečnostním klíčem..." + }, "hideEmail": { "message": "Skrýt moji e-mailovou adresu před příjemci." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Použijte doručenou poštu nakonfigurovanou doménou catch-all" }, + "useThisEmail": { + "message": "Použít tento e-mail" + }, "random": { "message": "Náhodný" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ odmítl Váš požadavek. Kontaktujte svého poskytovatele služeb o pomoc.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ odmítl Váš požadavek: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Nelze získat maskované ID e-mailového účtu $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Přihlásit se pomocí hlavního hesla" }, - "loggingInAs": { - "message": "Přihlášení jako" - }, "rememberEmail": { "message": "Zapamatovat si e-mail" }, - "notYou": { - "message": "Nejste to Vy?" - }, "newAroundHere": { "message": "Jste tu noví?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Bylo zahájeno přihlášení" }, + "logInRequestSent": { + "message": "Požadavek odeslán" + }, "notificationSentDevice": { "message": "Na Vaše zařízení bylo odesláno oznámení." }, "aNotificationWasSentToYourDevice": { "message": "Na Vaše zařízení bylo odesláno oznámení" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Ujistěte se, že je Váš účet odemčen a fráze otisku prstu se shoduje s tou na druhém zařízení" + "notificationSentDevicePart1": { + "message": "Odemknout Bitwarden na Vašem zařízení nebo na " + }, + "notificationSentDeviceAnchor": { + "message": "webová aplikace" + }, + "notificationSentDevicePart2": { + "message": "Před schválením se ujistěte, že fráze otisku prstu odpovídá frázi níže." }, "needAnotherOptionV1": { "message": "Potřebujete další volby?" @@ -2759,11 +2839,11 @@ "message": "Zobrazit počet znaků", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Pokoušíte se přihlásit?" + "areYouTryingToAccessYourAccount": { + "message": "Pokoušíte se získat přístup k Vašemu účtu?" }, - "logInAttemptBy": { - "message": "Pokus o přihlášení z $EMAIL$", + "accessAttemptBy": { + "message": "Pokus o přístup z $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Čas" }, - "confirmLogIn": { - "message": "Potvrdit přihlášení" + "confirmAccess": { + "message": "Potvrdit přístup" }, - "denyLogIn": { - "message": "Zamítnout přihlášení" + "denyAccess": { + "message": "Zamítnout přístup" }, "logInConfirmedForEmailOnDevice": { "message": "Přihlášení bylo potvrzeno z $EMAIL$ pro $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "Tento požadavek již není platný." }, - "confirmLoginAtemptForMail": { - "message": "Potvrďte pokus o přihlášení z $EMAIL$", + "confirmAccessAttempt": { + "message": "Potvrďte pokus o přístup z $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Bylo vyžádáno přihlášení" }, + "accountAccessRequested": { + "message": "Požadován přístup k účtu" + }, "creatingAccountOn": { "message": "Vytváření účtu na" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Slabé heslo bylo nalezeno mezi odhalenými hesly. K zabezpečení Vašeho účtu používejte silné a jedinečné heslo. Opravdu chcete používat toto heslo?" }, + "useThisPassword": { + "message": "Použít toto heslo" + }, + "useThisUsername": { + "message": "Použít toto uživatelské jméno" + }, "checkForBreaches": { "message": "Zkontrolovat heslo, zda nebylo odhaleno" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Žádost o schválení správcem" }, - "approveWithMasterPassword": { - "message": "Schválit hlavním heslem" - }, "region": { "message": "Oblast" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Váš požadavek byl odeslán Vašemu správci." }, - "youWillBeNotifiedOnceApproved": { - "message": "Po schválení budete upozorněni." - }, "troubleLoggingIn": { "message": "Potíže s přihlášením?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Pro Váš účet je vyžadováno dvoufázové přihlášení DUO." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Pro Váš účet je nutné dvoufázové přihlášení. Pro dokončení přihlášení postupujte podle následujících kroků." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Postupujte podle kroků níže pro dokončení přihlášení." + }, "launchDuo": { "message": "Spustit DUO v prohlížeči" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "Pro přihlášení SSO nebyly nalezeny žádné volné porty." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrické odemknutí je nedostupné, protože je potřeba nejprve odemknout pomocí PIN nebo hesla." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrické odemknutí je momentálně nedostupné." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrické odemknutí není dostupné kvůli chybnému nastavení systémových souborů." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrické odemknutí není dostupné kvůli chybnému nastavení systémových souborů." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometrické odemknutí není k dispozici, protože není povoleno pro $EMAIL$ v desktopové aplikaci Bitwarden.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrické odemknutí je momentálně z neznámého důvodu nedostupné." + }, "authorize": { "message": "Autorizovat" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Potvrdit použití SSH klíče" }, + "agentForwardingWarningTitle": { + "message": "Varování: Agent přesměrování" + }, + "agentForwardingWarningText": { + "message": "Tento požadavek pochází ze vzdáleného zařízení, ke kterému jste přihlášeni" + }, "sshkeyApprovalMessageInfix": { "message": "žádá o přístup k" }, + "sshkeyApprovalMessageSuffix": { + "message": "za účelem" + }, + "sshActionLogin": { + "message": "ověření na serveru" + }, + "sshActionSign": { + "message": "podepsání zprávy" + }, + "sshActionGitSign": { + "message": "podepsání commitu na gitu" + }, "unknownApplication": { "message": "Aplikace" }, - "sshKeyPasswordUnsupported": { - "message": "Import šifrovaných SSH klíčů není zatím podporován" - }, "invalidSshKey": { "message": "SSH klíč je neplatný" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Importovat klíč ze schránky" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH klíč byl úspěšně importován" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Změnit e-mail účtu" + }, + "allowScreenshots": { + "message": "Povolit záznam obrazovky" + }, + "allowScreenshotsDesc": { + "message": "Povolit zachycení aplikace Bitwarden ve snímcích obrazovky a zobrazení ve vzdálené desktopové relaci. Zakázáním této funkce se zabrání přístupu na některé externí displeje." + }, + "confirmWindowStillVisibleTitle": { + "message": "Potvrdit stále viditelné okno" + }, + "confirmWindowStillVisibleContent": { + "message": "Potvrďte, že okno je stále viditelné." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Je vyžadována aktualiazce rozšíření" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "Použité rozšíření prohlížeče je zastaralé. Aktualizujte jej nebo zakažte ověření otisků prstů při integraci prohlížeče v nastavení aplikace." + }, + "changeAtRiskPassword": { + "message": "Změnit ohrožené heslo" } } diff --git a/apps/desktop/src/locales/cy/messages.json b/apps/desktop/src/locales/cy/messages.json index 00d79c80ec8..1c6d824c22e 100644 --- a/apps/desktop/src/locales/cy/messages.json +++ b/apps/desktop/src/locales/cy/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Number of words" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Master password hint" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -871,6 +906,15 @@ "message": "Verify with Duo Security for your organization using the Duo Mobile app, SMS, phone call, or U2F security key.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Self-hosted environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Custom environment" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "Server URL" }, @@ -956,6 +997,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Overwrite password" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Purchase Premium" }, - "premiumPurchaseAlert": { - "message": "You can purchase premium membership on the bitwarden.com web vault. Do you want to visit the website now?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Require password or PIN on app start" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Recommended for security." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Hide my email address from recipients." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, "rememberEmail": { "message": "Remember email" }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "Toggle character count", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Are you trying to log in?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Login attempt by $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Time" }, - "confirmLogIn": { - "message": "Confirm login" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Deny login" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Login confirmed for $EMAIL$ on $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "confirmLoginAtemptForMail": { - "message": "Confirm login attempt for $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Log in requested" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "region": { "message": "Region" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json index b2867c4072e..ef5b22e33f4 100644 --- a/apps/desktop/src/locales/da/messages.json +++ b/apps/desktop/src/locales/da/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Fejl" }, + "decryptionError": { + "message": "Dekrypteringsfejl" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden kunne ikke dekryptere boks-emne(r) anført nedenfor." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontakt kundeservice", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "for at undgå yderligere tab af data.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januar" }, @@ -529,10 +543,6 @@ "message": "Inkludér specialtegn", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Antal ord" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log ind på Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log ind med adgangsnøgle" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Hovedadgangskodetip" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Bliv medlem af organisation" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Angiv kontoens e-mailadresse og få et adgangskodetip fremsendt" }, - "passwordHint": { - "message": "Adgangskodetip" - }, - "enterEmailToGetHint": { - "message": "Indtast din kontos e-mailadresse for at modtage dit hovedadgangskodetip." - }, "getMasterPasswordHint": { "message": "Få hovedadgangskodetip" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "Godkendelsen blev afbrudt eller tog for lang tid. Forsøg igen." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Ugyldig bekræftelseskode" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Husk mig" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send bekræftelseskodee-mail igen" }, "useAnotherTwoStepMethod": { "message": "Brug en anden totrins-loginmetode" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Indsæt din YubiKey i din computers USB-port og tryk derefter på dens knap." }, @@ -871,6 +906,15 @@ "message": "Bekræft med Duo Security for din organisation vha. Duo Mobile-app, SMS, telefonopkald eller U2F-sikkerhedsnøgle.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "Denne enhed er ikke genkendt. Angiv koden i den tilsendte e-mail for at bekræfte identiteten." + }, + "continueLoggingIn": { + "message": "Fortsæt med at logge ind" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Totrins-login indstillinger" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Selv-hostet miljø" }, - "selfHostedEnvironmentFooter": { - "message": "Angiv grund-URL'en til den lokal-hostede Bitwarden-installation." - }, "selfHostedBaseUrlHint": { "message": "Angiv basis-URL'en for den lokalt-hosted Bitwarden-installation. Eks.: https://bitwarden.firma.dk" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Tilpasset miljø" }, - "customEnvironmentFooter": { - "message": "Til avancerede brugere. Hver tjenestes basis-URL kan angives uafhængigt." - }, "baseUrl": { "message": "Server-URL" }, @@ -956,6 +997,9 @@ "no": { "message": "Nej" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Overskriv adgangskode" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Køb Premium" }, - "premiumPurchaseAlert": { - "message": "Premium-medlemskab kan købes via bitwarden.com web-boksen. Besøg webstedet nu?" - }, "premiumPurchaseAlertV2": { "message": "Der kan købes Premium fra kontoindstillingerne via Bitwarden web-appen." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Kræv adgangskode eller PIN-kode ved app-start" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Anbefales af sikkerhedshensyn." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Godkend WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Skjul min e-mailadresse for modtagere." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Brug domænets opsatte fang-alle indbakke." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Tilfældig" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Kan ikke få $SERVICENAME$ maskeret e-mailkonto-ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Log ind med hovedadgangskoden" }, - "loggingInAs": { - "message": "Logger ind som" - }, "rememberEmail": { "message": "Husk e-mail" }, - "notYou": { - "message": "Ikke dig?" - }, "newAroundHere": { "message": "Ny her?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Indlogning påbegyndt" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "En notifikation er sendt til din enhed." }, "aNotificationWasSentToYourDevice": { "message": "En notifikation er sendt til enheden" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Sørg for, at boksen er oplåst, samt at fingeraftrykssætningen matcher på den anden enhed" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Behov for en anden mulighed?" @@ -2759,11 +2839,11 @@ "message": "Vis/skjul tegnantal", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Forsøges at logge ind?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Loginforsøg fra $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Tid" }, - "confirmLogIn": { - "message": "Bekræft login" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Afvis login" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Login bekræftet for $EMAIL$ på $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "Anmodningen er ikke længere gyldig." }, - "confirmLoginAtemptForMail": { - "message": "Bekræft loginforsøg for $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Login anmodet" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Opretter konto på" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Svag adgangskode identificeret og fundet i datalæk. Brug en unik adgangskode til at beskytte din konto. Sikker på, at at denne adgangskode skal bruges?" }, + "useThisPassword": { + "message": "Anvend denne adgangskode" + }, + "useThisUsername": { + "message": "Anvend dette brugernavn" + }, "checkForBreaches": { "message": "Tjek kendte datalæk for denne adgangskode" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Anmod om admin-godkendelse" }, - "approveWithMasterPassword": { - "message": "Godkend med hovedadgangskode" - }, "region": { "message": "Region" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Anmodningen er sendt til din admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "Du underrettes, når godkendelse foreligger." - }, "troubleLoggingIn": { "message": "Problemer med at logge ind?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo totrinsindlogning kræves for kontoen." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Start Duo i webbrowser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "Ingen ledige porte fundet til SSO-login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrisk oplåsning er utilgængelig, da PIN- eller adgangskode kræves først." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrisk oplåsning er p.t. utilgængelig." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrisk oplåsning er utilgængelig grundet fejlopsatte systemfiler." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrisk oplåsning er utilgængelig grundet fejlopsatte systemfiler." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometrisk oplåsning er utilgængelig, da det ikke er aktiveret for $EMAIL$ i Bitwarden computer-appen.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrisk oplåsning er p.t. utilgængelig grundet en ukendt årsag." + }, "authorize": { "message": "Godkend" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Bekræft brug af SSH-nøgle" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "anmoder om adgang til" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "En applikation" }, - "sshKeyPasswordUnsupported": { - "message": "Import af adgangskodebeskyttede SSH-nøgler understøttes endnu ikke" - }, "invalidSshKey": { "message": "SSH-nøglen er ugyldig" }, @@ -3389,8 +3517,8 @@ "importSshKeyFromClipboard": { "message": "Importér nøgle fra udklipsholder" }, - "sshKeyPasted": { - "message": "SSH-nøgle er importeret" + "sshKeyImported": { + "message": "SSH key imported successfully" }, "fileSavedToDevice": { "message": "Fil gemt på enheden. Håndtér fra enhedens downloads." @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Skift kontoe-mailadresse" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Udvidelsesopdatering krævet" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "Den anvendte webbrowserudvidelse er forældet. Opdatér den venligst eller deaktivér webbrowserintegreret fingeraftryksbekræftelse i computer-app indstillingerne." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index ae20bc0931d..79f61475ab0 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -211,7 +211,7 @@ "message": "Dein eingegebenes Passwort ist falsch." }, "importSshKey": { - "message": "Import" + "message": "Importieren" }, "confirmSshKeyPassword": { "message": "Passwort bestätigen" @@ -249,6 +249,20 @@ "error": { "message": "Fehler" }, + "decryptionError": { + "message": "Entschlüsselungsfehler" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden konnte folgende(n) Tresor-Eintrag/Einträge nicht entschlüsseln." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktiere unser Customer Success Team", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": ", um zusätzlichen Datenverlust zu vermeiden.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januar" }, @@ -529,10 +543,6 @@ "message": "Sonderzeichen einschließen", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Anzahl der Wörter" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Bei Bitwarden anmelden" }, + "enterTheCodeSentToYourEmail": { + "message": "Gib den an deine E-Mail-Adresse gesendeten Code ein" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Gib den Code aus deiner Authenticator-App ein" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Drücke zum Authentifizieren auf deinen YubiKey" + }, "logInWithPasskey": { "message": "Mit Passkey anmelden" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Master-Passwort-Hinweis" }, + "passwordStrengthScore": { + "message": "Bewertung der Passwortstärke $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Organisation beitreten" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Gib deine E-Mail-Adresse ein und dein Passwort-Hinweis wird dir zugesandt" }, - "passwordHint": { - "message": "Passwort-Hinweis" - }, - "enterEmailToGetHint": { - "message": "Gib die E-Mail-Adresse deines Kontos ein, um den Hinweis für dein Master-Passwort zu erhalten." - }, "getMasterPasswordHint": { "message": "Hinweis zum Master-Passwort zusenden" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "Die Authentifizierung wurde abgebrochen oder hat zu lange gedauert. Bitte versuche es erneut." }, + "openInNewTab": { + "message": "In neuem Tab öffnen" + }, "invalidVerificationCode": { "message": "Ungültiger Verifizierungscode" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Angemeldet bleiben" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Für 30 Tage auf diesem Gerät nicht mehr fragen" + }, "sendVerificationCodeEmailAgain": { "message": "E-Mail mit Bestätigungscode erneut versenden" }, "useAnotherTwoStepMethod": { "message": "Eine andere Zwei-Faktor-Anmeldemethode verwenden" }, + "selectAnotherMethod": { + "message": "Wähle eine andere Methode", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Verwende deinen Wiederherstellungscode" + }, "insertYubiKey": { "message": "Stecke deinen YubiKey in einen USB-Anschluss deines Computers, dann berühre den Button." }, @@ -871,6 +906,15 @@ "message": "Nutze Duo Security, um dich mit der Duo-Mobile-App, SMS, per Anruf oder U2F-Sicherheitsschlüssel bei deiner Organisation zu verifizieren.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verifiziere deine Identität" + }, + "weDontRecognizeThisDevice": { + "message": "Wir erkennen dieses Gerät nicht. Gib den an deine E-Mail-Adresse gesendeten Code ein, um deine Identität zu verifizieren." + }, + "continueLoggingIn": { + "message": "Anmeldung fortsetzen" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Optionen für Zwei-Faktor-Authentifizierung" }, + "selectTwoStepLoginMethod": { + "message": "Zwei-Faktor-Authentifizierungsmethode auswählen" + }, "selfHostedEnvironment": { "message": "Selbst gehostete Umgebung" }, - "selfHostedEnvironmentFooter": { - "message": "Bitte gib die Basis-URL deiner selbst gehosteten Bitwarden-Installation an." - }, "selfHostedBaseUrlHint": { "message": "Gib die Basis-URL deiner vor Ort gehosteten Bitwarden-Installation an. Beispiel: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Benutzerdefinierte Umgebung" }, - "customEnvironmentFooter": { - "message": "Für fortgeschrittene Benutzer. Du kannst die Basis-URL der jeweiligen Dienste unabhängig voneinander festlegen." - }, "baseUrl": { "message": "Server-URL" }, @@ -956,6 +997,9 @@ "no": { "message": "Nein" }, + "location": { + "message": "Standort" + }, "overwritePassword": { "message": "Passwort ersetzen" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Premium-Mitgliedschaft kaufen" }, - "premiumPurchaseAlert": { - "message": "Du kannst deine Premium-Mitgliedschaft im bitwarden.com Web-Tresor kaufen. Möchtest du die Webseite jetzt besuchen?" - }, "premiumPurchaseAlertV2": { "message": "Du kannst Premium über deine Kontoeinstellungen in der Bitwarden Web-App kaufen." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Passwort oder PIN beim Start der App verlangen" }, + "requirePasswordWithoutPinOnStart": { + "message": "Ein Passwort beim Starten der App verlangen" + }, "recommendedForSecurity": { "message": "Aus Sicherheitsgründen empfohlen." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "WebAuthn authentifizieren" }, + "readSecurityKey": { + "message": "Sicherheitsschlüssel auslesen" + }, + "awaitingSecurityKeyInteraction": { + "message": "Warte auf Sicherheitsschlüssel-Interaktion..." + }, "hideEmail": { "message": "Meine E-Mail-Adresse vor den Empfängern ausblenden." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Verwende den konfigurierten Catch-All-Posteingang deiner Domain." }, + "useThisEmail": { + "message": "Diese E-Mail-Adresse verwenden" + }, "random": { "message": "Zufällig" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ hat deine Anfrage abgelehnt. Bitte wende dich an deinen Dienstanbieter für Unterstützung.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ hat deine Anfrage abgelehnt: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Maskierte E-Mail-Konto-ID für $SERVICENAME$ konnte nicht abgerufen werden.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Mit Master-Passwort anmelden" }, - "loggingInAs": { - "message": "Anmelden als" - }, "rememberEmail": { "message": "E-Mail-Adresse merken" }, - "notYou": { - "message": "Nicht du?" - }, "newAroundHere": { "message": "Neu hier?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Anmeldung eingeleitet" }, + "logInRequestSent": { + "message": "Anfrage gesendet" + }, "notificationSentDevice": { "message": "Eine Benachrichtigung wurde an dein Gerät gesendet." }, "aNotificationWasSentToYourDevice": { "message": "Eine Benachrichtigung wurde an dein Gerät gesendet" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Stelle sicher, dass dein Konto entsperrt ist und die Fingerabdruck-Phrase mit der vom anderen Gerät übereinstimmt" + "notificationSentDevicePart1": { + "message": "Entsperre Bitwarden auf deinem Gerät oder mit der " + }, + "notificationSentDeviceAnchor": { + "message": "Web-App" + }, + "notificationSentDevicePart2": { + "message": "Stelle vor der Genehmigung sicher, dass die Fingerabdruck-Phrase mit der unten stehenden übereinstimmt." }, "needAnotherOptionV1": { "message": "Brauchst du eine andere Option?" @@ -2759,11 +2839,11 @@ "message": "Zeichenanzahl ein-/ausschalten", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Versuchst du dich anzumelden?" + "areYouTryingToAccessYourAccount": { + "message": "Versuchst du auf dein Konto zuzugreifen?" }, - "logInAttemptBy": { - "message": "Anmeldeversuch von $EMAIL$", + "accessAttemptBy": { + "message": "Zugriffsversuch von $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Zeit" }, - "confirmLogIn": { - "message": "Anmeldung genehmigen" + "confirmAccess": { + "message": "Zugriff bestätigen" }, - "denyLogIn": { - "message": "Anmeldung ablehnen" + "denyAccess": { + "message": "Zugriff ablehnen" }, "logInConfirmedForEmailOnDevice": { "message": "Anmeldung von $EMAIL$ auf $DEVICE$ bestätigt", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "Diese Anfrage ist nicht mehr gültig." }, - "confirmLoginAtemptForMail": { - "message": "Anmeldeversuch von $EMAIL$ genehmigen", + "confirmAccessAttempt": { + "message": "Zugriffsversuch für $EMAIL$ bestätigen", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Anmeldung angefordert" }, + "accountAccessRequested": { + "message": "Kontozugriff angefragt" + }, "creatingAccountOn": { "message": "Konto wird erstellt bei" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Schwaches Passwort erkannt und in einem Datendiebstahl gefunden. Verwende ein starkes und einzigartiges Passwort, um dein Konto zu schützen. Bist du sicher, dass du dieses Passwort verwenden möchtest?" }, + "useThisPassword": { + "message": "Dieses Passwort verwenden" + }, + "useThisUsername": { + "message": "Diesen Benutzernamen verwenden" + }, "checkForBreaches": { "message": "Bekannte Datendiebstähle auf dieses Passwort überprüfen" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Admin-Genehmigung anfragen" }, - "approveWithMasterPassword": { - "message": "Mit Master-Passwort genehmigen" - }, "region": { "message": "Region" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Deine Anfrage wurde an deinen Administrator gesendet." }, - "youWillBeNotifiedOnceApproved": { - "message": "Nach einer Genehmigung wirst du benachrichtigt." - }, "troubleLoggingIn": { "message": "Probleme beim Anmelden?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Für dein Konto ist die DUO Zwei-Faktor-Authentifizierung erforderlich." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Die Duo-Zwei-Faktor-Authentifizierung ist für dein Konto erforderlich. Folge den Schritten unten, um die Anmeldung abzuschließen." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Folge den Schritten unten, um die Anmeldung abzuschließen." + }, "launchDuo": { "message": "Duo im Browser starten" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "Es konnten keine freien Ports für die SSO-Anmeldung gefunden werden." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrisches Entsperren ist nicht verfügbar, da zuerst mit PIN oder Passwort entsperrt werden muss." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrisches Entsperren ist derzeit nicht verfügbar." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrisches Entsperren ist aufgrund falsch konfigurierter Systemdateien nicht verfügbar." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrisches Entsperren ist aufgrund falsch konfigurierter Systemdateien nicht verfügbar." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometrisches Entsperren ist nicht verfügbar, da es für $EMAIL$ in der Bitwarden Desktop-App nicht aktiviert ist.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrisches Entsperren ist derzeit aus einem unbekannten Grund nicht verfügbar." + }, "authorize": { "message": "Autorisieren" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Verwendung des SSH-Schlüssels bestätigen" }, + "agentForwardingWarningTitle": { + "message": "Warnung: Agent-Weiterleitung" + }, + "agentForwardingWarningText": { + "message": "Diese Anfrage kommt von einem entfernten Gerät, bei dem du angemeldet bist" + }, "sshkeyApprovalMessageInfix": { "message": "fragt den Zugriff an für" }, + "sshkeyApprovalMessageSuffix": { + "message": "um" + }, + "sshActionLogin": { + "message": "sich bei einem Server zu authentifizieren" + }, + "sshActionSign": { + "message": "eine Nachricht zu signieren" + }, + "sshActionGitSign": { + "message": "einen Git-Commit zu signieren" + }, "unknownApplication": { "message": "Eine Anwendung" }, - "sshKeyPasswordUnsupported": { - "message": "Das Importieren passwortgeschützter SSH-Schlüssel wird noch nicht unterstützt" - }, "invalidSshKey": { "message": "Der SSH-Schlüssel ist ungültig" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Schlüssel aus Zwischenablage importieren" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH-Schlüssel erfolgreich importiert" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "E-Mail-Adresse des Kontos ändern" + }, + "allowScreenshots": { + "message": "Bildschirmaufnahme erlauben" + }, + "allowScreenshotsDesc": { + "message": "Erlaube der Bitwarden Desktop-Anwendung in Screenshots festgehalten und in Remote Desktop-Sitzungen angezeigt zu werden. Das Deaktivieren dieser Option verhindert den Zugriff über einige externe Monitore." + }, + "confirmWindowStillVisibleTitle": { + "message": "Bestätigungsfenster noch sichtbar" + }, + "confirmWindowStillVisibleContent": { + "message": "Bitte bestätige, dass das Fenster noch sichtbar ist." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Aktualisierung der Erweiterung notwendig" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "Die von dir verwendete Browser-Erweiterung ist veraltet. Bitte aktualisiere sie oder deaktiviere die Fingerabdrucküberprüfung der Browser-Integration in den Einstellungen der Desktop-App." + }, + "changeAtRiskPassword": { + "message": "Gefährdetes Passwort ändern" } } diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json index 676c016c699..ecee8bd9894 100644 --- a/apps/desktop/src/locales/el/messages.json +++ b/apps/desktop/src/locales/el/messages.json @@ -27,7 +27,7 @@ "message": "Ασφαλής σημείωση" }, "typeSshKey": { - "message": "SSH key" + "message": "Κλειδί SSH" }, "folders": { "message": "Φάκελοι" @@ -64,7 +64,7 @@ } }, "welcomeBack": { - "message": "Welcome back" + "message": "Καλωσορίσατε και πάλι" }, "moveToOrgDesc": { "message": "Επιλέξτε έναν οργανισμό στον οποίο θέλετε να μετακινήσετε αυτό το στοιχείο. Η μετακίνηση σε έναν οργανισμό μεταβιβάζει την ιδιοκτησία του στοιχείου σε αυτό τον οργανισμό. Δεν θα είστε πλέον ο άμεσος ιδιοκτήτης αυτού του στοιχείου μόλις το μετακινήσετε." @@ -107,7 +107,7 @@ "message": "Επεξεργασία στοιχείου" }, "emailAddress": { - "message": "Διεύθυνση ηλ. ταχυδρομείου" + "message": "Διεύθυνση Email" }, "verificationCodeTotp": { "message": "Κωδικός Επαλήθευσης (TOTP)" @@ -181,16 +181,16 @@ "message": "Διεύθυνση" }, "sshPrivateKey": { - "message": "Private key" + "message": "Ιδιωτικό κλειδί" }, "sshPublicKey": { - "message": "Public key" + "message": "Δημόσιο κλειδί" }, "sshFingerprint": { - "message": "Fingerprint" + "message": "Αποτύπωμα" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "Τύπος κλειδιού" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -205,37 +205,37 @@ "message": "RSA 4096-Bit" }, "sshKeyGenerated": { - "message": "A new SSH key was generated" + "message": "Δημιουργήθηκε ένα νέο SSH κλειδί" }, "sshKeyWrongPassword": { - "message": "The password you entered is incorrect." + "message": "Ο κωδικός πρόσβασης που εισάγατε είναι λάθος." }, "importSshKey": { - "message": "Import" + "message": "Εισαγωγή" }, "confirmSshKeyPassword": { - "message": "Confirm password" + "message": "Επιβεβαίωση κωδικού πρόσβασης" }, "enterSshKeyPasswordDesc": { - "message": "Enter the password for the SSH key." + "message": "Εισάγετε τον κωδικό πρόσβασης για το SSH κλειδί." }, "enterSshKeyPassword": { - "message": "Enter password" + "message": "Εισάγετε κωδικό πρόσβασης" }, "sshAgentUnlockRequired": { - "message": "Please unlock your vault to approve the SSH key request." + "message": "Παρακαλώ ξεκλειδώστε την κρύπτη σας για να εγκρίνετε το αίτημα κλειδιού SSH." }, "sshAgentUnlockTimeout": { - "message": "SSH key request timed out." + "message": "Λήξη χρονικού ορίου αιτήματος κλειδιού SSH." }, "enableSshAgent": { - "message": "Enable SSH agent" + "message": "Ενεργοποίηση πράκτορα SSH" }, "enableSshAgentDesc": { - "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + "message": "Ενεργοποιήστε τον πράκτορα SSH για την υπογραφή αιτημάτων SSH απευθείας από την κρύπτη Bitwarden." }, "enableSshAgentHelp": { - "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + "message": "Ο πράκτορα SSH είναι μια υπηρεσία που απευθύνεται σε προγραμματιστές που σας επιτρέπει να υπογράψετε αιτήματα SSH απευθείας από την κρύπτη Bitwarden σας." }, "premiumRequired": { "message": "Απαιτείται Premium" @@ -249,6 +249,20 @@ "error": { "message": "Σφάλμα" }, + "decryptionError": { + "message": "Σφάλμα αποκρυπτογράφησης" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Το Bitwarden δεν μπόρεσε να αποκρυπτογραφήσει τα αντικείμενα κρύπτης που αναφέρονται παρακάτω." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Εποικινωνία επιτυχίας επαφής πελάτη", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "για την αποφυγή επιπλέον απώλειας δεδομένων.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Ιανουάριος" }, @@ -461,10 +475,10 @@ "message": "Αντιγραφή κωδικού πρόσβασης" }, "regenerateSshKey": { - "message": "Regenerate SSH key" + "message": "Επαναδημιουργία κλειδιού SSH" }, "copySshPrivateKey": { - "message": "Copy SSH private key" + "message": "Αντιγραφή ιδιωτικού κλειδιού SSH" }, "copyPassphrase": { "message": "Αντιγραφή φράσης πρόσβασης", @@ -529,10 +543,6 @@ "message": "Συμπερίληψη ειδικών χαρακτήρων", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Αριθμός λέξεων" }, @@ -624,7 +634,7 @@ "message": "Δημιουργία λογαριασμού" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Μόλις ήρθες στο Bitwarden;" }, "setAStrongPassword": { "message": "Ορίστε έναν ισχυρό κωδικό πρόσβασης" @@ -636,16 +646,25 @@ "message": "Είσοδος" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Σύνδεση στο Bitwarden" + }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Σύνδεση με κλειδί πρόσβασης" }, "loginWithDevice": { - "message": "Log in with device" + "message": "Σύνδεση με χρήση συσκευής" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "Χρήση single sign-on" }, "submit": { "message": "Υποβολή" @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Υπόδειξη κύριου κωδικού πρόσβασης" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Συμμετοχή σε οργανισμό" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Εισάγετε τη διεύθυνση ηλ. ταχυδρομείου του λογαριασμού σας και θα σας αποσταλεί η υπόδειξη κωδικού πρόσβασης" }, - "passwordHint": { - "message": "Υπόδειξη κωδικού πρόσβασης" - }, - "enterEmailToGetHint": { - "message": "Εισάγετε τη διεύθυνση ηλ. ταχυδρομείου του λογαριασμού σας για να λάβετε την υπόδειξη του κύριου κωδικού πρόσβασης." - }, "getMasterPasswordHint": { "message": "Λήψη υπόδειξης κύριου κωδικού" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "Η αυθεντικοποίηση ακυρώθηκε ή διήρκησε πολύ ώρα. Παρακαλώ προσπαθήστε ξανά." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Μη έγκυρος κωδικός επαλήθευσης" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Να με θυμάσαι" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Επανάληψη αποστολής κωδικού επαλήθευσης μέσω ηλ. ταχυδρομείου" }, "useAnotherTwoStepMethod": { "message": "Χρήση άλλης μεθόδου σύνδεσης δύο βημάτων" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Τοποθετήστε το YubiKey στη θύρα USB του υπολογιστή σας και έπειτα αγγίξτε το κουμπί του." }, @@ -871,6 +906,15 @@ "message": "Επαληθεύστε με το Duo Security για τον οργανισμό σας χρησιμοποιώντας την εφαρμογή Duo Mobile, μήνυμα SMS, τηλεφωνική κλήση ή κλειδί ασφαλείας U2F.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "Δεν αναγνωρίζουμε αυτή τη συσκευή. Εισάγετε τον κωδικό που στάλθηκε στο email σας για να επαληθεύσετε την ταυτότητά σας." + }, + "continueLoggingIn": { + "message": "Παραμείνετε συνδεμένοι" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Επιλογές σύνδεσης δύο βημάτων" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Αυτο-φιλοξενούμενο περιβάλλον" }, - "selfHostedEnvironmentFooter": { - "message": "Καθορίστε τη βασική διεύθυνση URL, της εγκατάστασης του Bitwarden που φιλοξενείται στο χώρο σας." - }, "selfHostedBaseUrlHint": { "message": "Καθορίστε το βασικό URL της εγκατάστασης Bitwarden που φιλοξενείται στο χώρο σας. Παράδειγμα: https://bitwarden.company.com" }, @@ -913,17 +957,14 @@ "customEnvironment": { "message": "Προσαρμοσμένο περιβάλλον" }, - "customEnvironmentFooter": { - "message": "Για προχωρημένους χρήστες. Μπορείτε να ορίσετε ανεξάρτητα τη βασική διεύθυνση URL κάθε υπηρεσίας." - }, "baseUrl": { "message": "URL Διακομιστή" }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "Χρονικό όριο επαλήθευσης" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "Λήξη χρονικού ορίου συνεδρίας επαλήθευσης. Παρακαλώ επανεκκινήστε τη διαδικασία σύνδεσης." }, "selfHostBaseUrl": { "message": "URL διακομιστή αυτο-φιλοξενίας", @@ -956,6 +997,9 @@ "no": { "message": "Όχι" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Αντικατάσταση κωδικού πρόσβασης" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Αγορά Premium έκδοσης" }, - "premiumPurchaseAlert": { - "message": "Μπορείτε να αγοράσετε συνδρομή premium στο bitwarden.com web vault. Θέλετε να επισκεφθείτε την ιστοσελίδα τώρα;" - }, "premiumPurchaseAlertV2": { "message": "Μπορείτε να αγοράσετε το Premium από τις ρυθμίσεις λογαριασμού σας στη διαδικτυακή εφαρμογή του Bitwarden." }, @@ -1393,13 +1434,13 @@ "message": "Ιστορικό κωδικού πρόσβασης" }, "generatorHistory": { - "message": "Generator history" + "message": "Ιστορικό γεννήτριας" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "Εκκαθάριση ιστορικού γεννήτριας" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "Αν συνεχίσετε, όλες οι καταχωρήσεις θα διαγραφούν οριστικά από το ιστορικό της γεννήτριας. Σίγουρα θέλετε να συνεχίσετε;" }, "clear": { "message": "Εκκαθάριση", @@ -1409,13 +1450,13 @@ "message": "Δεν υπάρχουν κωδικοί στη λίστα." }, "clearHistory": { - "message": "Clear history" + "message": "Διαγραφή ιστορικού" }, "nothingToShow": { - "message": "Nothing to show" + "message": "Τίποτα για προβολή" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "Δεν έχετε δημιουργήσει τίποτα πρόσφατα" }, "undo": { "message": "Αναίρεση" @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Να απαιτείται κωδικός πρόσβασης ή PIN κατά την εκκίνηση της εφαρμογής" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Συνίσταται για ασφάλεια." }, @@ -1771,10 +1815,10 @@ "message": "Η διαγραφή του λογαριασμού σας είναι μόνιμη. Δεν μπορεί να αναιρεθεί." }, "cannotDeleteAccount": { - "message": "Cannot delete account" + "message": "Αδυναμία διαγραφής λογαριασμού" }, "cannotDeleteAccountDesc": { - "message": "This action cannot be completed because your account is owned by an organization. Contact your organization administrator for additional details." + "message": "Αυτή η ενέργεια δεν μπορεί να ολοκληρωθεί επειδή ο λογαριασμός σας ανήκει σε έναν οργανισμό. Επικοινωνήστε με το διαχειριστή του οργανισμού σας για πρόσθετες λεπτομέρειες." }, "accountDeleted": { "message": "Ο λογαριασμός διαγράφηκε" @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Ταυτοποίηση WebAutn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Απόκρυψη της διεύθυνσης email μου από τους παραλήπτες." }, @@ -2481,7 +2531,7 @@ "message": "Δημιουργία διεύθυνσης ηλ. ταχυδρομείου" }, "spinboxBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$.", + "message": "Η τιμή πρέπει να είναι μεταξύ $MIN$ και $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2495,7 +2545,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "message": " Χρησιμοποιήστε $RECOMMENDED$ ή περισσότερους χαρακτήρες για να δημιουργήσετε έναν ισχυρό κωδικό πρόσβασης.", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2505,7 +2555,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "message": " Χρησιμοποιήστε $RECOMMENDED$ ή περισσότερες λέξεις για να δημιουργήσετε μια ισχυρή φράση πρόσβασης.", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Χρησιμοποιήστε τα διαμορφωμένα εισερχόμενα catch-all του domain σας." }, + "useThisEmail": { + "message": "Χρήση αυτού του email" + }, "random": { "message": "Τυχαίο" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Αδύνατη η απόκτηση του $SERVICENAME$ καμουφλαρισμένου ID διεύθυνσης ηλ. ταχυδρομείου.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Συνδεθείτε με τον κύριο κωδικό πρόσβασης" }, - "loggingInAs": { - "message": "Σύνδεση ως" - }, "rememberEmail": { "message": "Απομνημόνευση διεύθυνσης ηλ. ταχυδρομείου" }, - "notYou": { - "message": "Δεν είστε εσείς;" - }, "newAroundHere": { "message": "Είστε νέος/α εδώ;" }, @@ -2722,17 +2793,26 @@ "loginInitiated": { "message": "Η σύνδεση ξεκίνησε" }, + "logInRequestSent": { + "message": "Το αίτημα εστάλη" + }, "notificationSentDevice": { "message": "Μια ειδοποίηση έχει σταλεί στη συσκευή σας." }, "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "message": "Μια ειδοποίηση στάλθηκε στη συσκευή σας" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Ξεκλειδώστε το Bitwarden στη συσκευή σας ή στην " + }, + "notificationSentDeviceAnchor": { + "message": "εφαρμογή ιστού" + }, + "notificationSentDevicePart2": { + "message": "Βεβαιωθείτε ότι η φράση αποτυπωμάτων ταιριάζει με την παρακάτω φράση πριν την έγκριση." }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "Χρειάζεστε μια άλλη επιλογή;" }, "fingerprintMatchInfo": { "message": "Παρακαλώ βεβαιωθείτε ότι το θησαυ/κιό σας είναι ξεκλείδωτο και η φράση δακτυλικών αποτυπωμάτων ταιριάζει με την άλλη συσκευή." @@ -2741,13 +2821,13 @@ "message": "Φράση δακτυλικών αποτυπωμάτων" }, "youWillBeNotifiedOnceTheRequestIsApproved": { - "message": "You will be notified once the request is approved" + "message": "Θα ειδοποιηθείτε μόλις εγκριθεί η αίτηση" }, "needAnotherOption": { "message": "Η σύνδεση με τη χρήση συσκευής πρέπει να οριστεί στις ρυθμίσεις της εφαρμογής Bitwarden. Χρειάζεστε κάποια άλλη επιλογή;" }, "viewAllLogInOptions": { - "message": "View all log in options" + "message": "Δείτε όλες τις επιλογές σύνδεσης" }, "viewAllLoginOptions": { "message": "Δείτε όλες τις επιλογές σύνδεσης" @@ -2759,11 +2839,11 @@ "message": "Εναλλαγή αριθμού χαρακτήρων", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Προσπαθείτε να συνδεθείτε;" + "areYouTryingToAccessYourAccount": { + "message": "Προσπαθείτε να αποκτήσετε πρόσβαση στο λογαριασμό σας;" }, - "logInAttemptBy": { - "message": "Προσπάθεια σύνδεσης από $EMAIL$", + "accessAttemptBy": { + "message": "Προσπάθεια πρόσβασης από το $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Ώρα" }, - "confirmLogIn": { - "message": "Επιβεβαίωση σύνδεσης" + "confirmAccess": { + "message": "Επιβεβαίωση πρόσβασης" }, - "denyLogIn": { - "message": "Άρνηση σύνδεσης" + "denyAccess": { + "message": "Άρνηση πρόσβασης" }, "logInConfirmedForEmailOnDevice": { "message": "Επιβεβαιώθηκε η σύνδεση του $EMAIL$ στη συσκευή $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "Αυτό το αίτημα δεν είναι πλέον έγκυρο." }, - "confirmLoginAtemptForMail": { - "message": "Επιβεβαίωση προσπάθειας σύνδεσης για $EMAIL$", + "confirmAccessAttempt": { + "message": "Επιβεβαίωση προσπάθειας πρόσβασης για το $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Ζητήθηκε σύνδεση" }, + "accountAccessRequested": { + "message": "Ζητήθηκε πρόσβαση στον λογαριασμό" + }, "creatingAccountOn": { "message": "Δημιουργία λογαριασμού στο" }, @@ -2865,11 +2948,17 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Βρέθηκε και ταυτοποιήθηκε αδύναμος κωδικός σε μια διαρροή δεδομένων. Χρησιμοποιήστε ένα ισχυρό και μοναδικό κωδικό πρόσβασης για την προστασία του λογαριασμού σας. Είστε σίγουροι ότι θέλετε να χρησιμοποιήσετε αυτόν τον κωδικό πρόσβασης;" }, + "useThisPassword": { + "message": "Χρήση αυτού του κωδικού πρόσβασης" + }, + "useThisUsername": { + "message": "Χρήση αυτού του ονόματος χρήστη" + }, "checkForBreaches": { "message": "Ελέγξτε γνωστές διαρροές δεδομένων για αυτόν τον κωδικό" }, "loggedInExclamation": { - "message": "Logged in!" + "message": "Έχετε συνδεθεί!" }, "important": { "message": "Σημαντικό:" @@ -2902,16 +2991,16 @@ "message": "Ενημέρωση Προτεινόμενων Ρυθμίσεων" }, "rememberThisDeviceToMakeFutureLoginsSeamless": { - "message": "Remember this device to make future logins seamless" + "message": "Απομνημόνευση αυτής της συσκευής για απρόσκοπτη σύνδεση στο μέλλον" }, "deviceApprovalRequired": { "message": "Απαιτείται έγκριση συσκευής. Επιλέξτε μια επιλογή έγκρισης παρακάτω:" }, "deviceApprovalRequiredV2": { - "message": "Device approval required" + "message": "Απαιτείται έγκριση συσκευής" }, "selectAnApprovalOptionBelow": { - "message": "Select an approval option below" + "message": "Επιλέξτε μια επιλογή έγκρισης παρακάτω" }, "rememberThisDevice": { "message": "Απομνημόνευση αυτής της συσκευής" @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Αίτηση έγκρισης διαχειριστή" }, - "approveWithMasterPassword": { - "message": "Έγκριση με τον κύριο κωδικό πρόσβασης" - }, "region": { "message": "Περιοχή" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Το αίτημά σας εστάλη στον διαχειριστή σας." }, - "youWillBeNotifiedOnceApproved": { - "message": "Θα ειδοποιηθείτε μόλις εγκριθεί." - }, "troubleLoggingIn": { "message": "Πρόβλημα σύνδεσης;" }, @@ -2966,7 +3049,7 @@ "message": "Η διεύθυνση ηλ. ταχυδρομείου του χρήστη λείπει" }, "activeUserEmailNotFoundLoggingYouOut": { - "message": "Active user email not found. Logging you out." + "message": "Το email χρήστη δεν βρέθηκε. Αποσυνδεθήκατε." }, "deviceTrusted": { "message": "Αξιόπιστη συσκευή" @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Η σύνδεση δύο βημάτων Duo απαιτείται για τον λογαριασμό σας." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Εκκίνηση Duo στον περιηγητή" }, @@ -3362,56 +3451,95 @@ "ssoError": { "message": "Δεν βρέθηκαν ελεύθερες θύρες για τη σύνδεση sso." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Το βιομετρικό ξεκλείδωμα δεν είναι διαθέσιμο επειδή απαιτείται πρώτα το ξεκλείδωμα με PIN ή κωδικό πρόσβασης." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Το βιομετρικό ξεκλείδωμα δεν είναι διαθέσιμο προς το παρόν." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Το βιομετρικό ξεκλείδωμα δεν είναι διαθέσιμο λόγω εσφαλμένων ρυθμίσεων αρχείων συστήματος." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Το βιομετρικό ξεκλείδωμα δεν είναι διαθέσιμο λόγω εσφαλμένων ρυθμίσεων αρχείων συστήματος." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Το βιομετρικό ξεκλείδωμα δεν είναι διαθέσιμο επειδή δεν είναι ενεργοποιημένο για το $EMAIL$ στην εφαρμογή Bitwarden για υπολογιστές.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Το βιομετρικό ξεκλείδωμα δεν είναι διαθέσιμο για άγνωστο λόγο." + }, "authorize": { - "message": "Authorize" + "message": "Εξουσιοδότηση" }, "deny": { - "message": "Deny" + "message": "Απόρριψη" }, "sshkeyApprovalTitle": { - "message": "Confirm SSH key usage" + "message": "Επιβεβαίωση χρήσης κλειδιού SSH" + }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" }, "sshkeyApprovalMessageInfix": { - "message": "is requesting access to" + "message": "ζητά πρόσβαση σε" + }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" }, "unknownApplication": { - "message": "An application" - }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" + "message": "Μια εφαρμογή" }, "invalidSshKey": { - "message": "The SSH key is invalid" + "message": "Το SSH κλειδί δεν είναι έγκυρο" }, "sshKeyTypeUnsupported": { - "message": "The SSH key type is not supported" + "message": "Ο τύπος κλειδιού SSH δεν υποστηρίζεται" }, "importSshKeyFromClipboard": { - "message": "Import key from clipboard" + "message": "Εισαγωγή κλειδιού από το πρόχειρο" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { "message": "Το αρχείο αποθηκεύτηκε στη συσκευή. Διαχείριση από τις λήψεις της συσκευής σας." }, "importantNotice": { - "message": "Important notice" + "message": "Σημαντική ειδοποίηση" }, "setupTwoStepLogin": { - "message": "Set up two-step login" + "message": "Ρύθμιση σύνδεσης δύο βημάτων" }, "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + "message": "Το Bitwarden θα στείλει έναν κωδικό στο email του λογαριασμού σας για να επαληθεύσει τις συνδέσεις από νέες συσκευές ξεκινώντας από τον Φεβρουάριο του 2025." }, "newDeviceVerificationNoticeContentPage2": { - "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + "message": "Μπορείτε να ορίσετε σύνδεση δύο βημάτων ως εναλλακτικό τρόπο προστασίας του λογαριασμού σας ή να αλλάξετε το email σας σε ένα που μπορείτε να έχετε πρόσβαση." }, "remindMeLater": { - "message": "Remind me later" + "message": "Υπενθύμιση αργότερα" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", + "message": "Έχετε αξιόπιστη πρόσβαση στο email σας, $EMAIL$;", "placeholders": { "email": { "content": "$1", @@ -3420,15 +3548,36 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "Όχι, δεν έχω" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" + "message": "Ναι, μπορώ να συνδεθώ αξιόπιστα στο ηλ. ταχυδρομείο email μου" }, "turnOnTwoStepLogin": { - "message": "Turn on two-step login" + "message": "Ενεργοποίηση σύνδεσης δύο βημάτων" }, "changeAcctEmail": { - "message": "Change account email" + "message": "Αλλαγή email λογαριασμού" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Απαιτείται ενημέρωση επέκτασης" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "Η επέκταση περιηγητή που χρησιμοποιείτε είναι ξεπερασμένη. Παρακαλούμε ενημερώστε την ή απενεργοποιήστε την επικύρωση δακτυλικών αποτυπωμάτων του προγράμματος περιήγησης στις ρυθμίσεις της εφαρμογής για την επιφάνεια εργασίας." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 35d5cf8c03c..f93db44aa69 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -543,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Number of words" }, @@ -652,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -704,6 +709,15 @@ "masterPassHintLabel": { "message": "Master password hint" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -734,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -816,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -846,12 +857,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -885,6 +906,15 @@ "message": "Verify with Duo Security for your organization using the Duo Mobile app, SMS, phone call, or U2F security key.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -909,12 +939,12 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Self-hosted environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -927,9 +957,6 @@ "customEnvironment": { "message": "Custom environment" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "Server URL" }, @@ -970,6 +997,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Overwrite password" }, @@ -1379,9 +1409,6 @@ "premiumPurchase": { "message": "Purchase Premium" }, - "premiumPurchaseAlert": { - "message": "You can purchase premium membership on the bitwarden.com web vault. Do you want to visit the website now?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1769,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Require password or PIN on app start" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Recommended for security." }, @@ -2238,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Hide my email address from recipients." }, @@ -2544,6 +2580,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random" }, @@ -2631,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2709,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, "rememberEmail": { "message": "Remember email" }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -2736,14 +2793,23 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2773,11 +2839,11 @@ "message": "Toggle character count", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Are you trying to log in?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Login attempt by $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2794,11 +2860,11 @@ "time": { "message": "Time" }, - "confirmLogIn": { - "message": "Confirm login" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Deny login" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Login confirmed for $EMAIL$ on $DEVICE$", @@ -2834,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "confirmLoginAtemptForMail": { - "message": "Confirm login attempt for $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2846,6 +2912,9 @@ "logInRequested": { "message": "Log in requested" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2879,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -2939,9 +3014,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "region": { "message": "Region" }, @@ -2967,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3152,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3409,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3427,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3468,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json index ee1be4df2e6..f70a70e975a 100644 --- a/apps/desktop/src/locales/en_GB/messages.json +++ b/apps/desktop/src/locales/en_GB/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Number of words" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Master password hint" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organisation" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -871,6 +906,15 @@ "message": "Verify with Duo Security for your organisation using the Duo Mobile app, SMS, phone call, or U2F security key.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognise this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Self-hosted environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premise hosted Bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Custom environment" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "Server URL" }, @@ -956,6 +997,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Overwrite password" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Purchase Premium" }, - "premiumPurchaseAlert": { - "message": "You can purchase premium membership on the bitwarden.com web vault. Do you want to visit the website now?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Require password or PIN on app start" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Recommended for security." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Hide my email address from recipients." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, "rememberEmail": { "message": "Remember email" }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "Toggle character count", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Are you trying to log in?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Login attempt by $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Time" }, - "confirmLogIn": { - "message": "Confirm login" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Deny login" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Login confirmed for $EMAIL$ on $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "confirmLoginAtemptForMail": { - "message": "Confirm login attempt for $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Log in requested" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "region": { "message": "Region" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorise" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json index b8afb36f3a6..f91a7b21876 100644 --- a/apps/desktop/src/locales/en_IN/messages.json +++ b/apps/desktop/src/locales/en_IN/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Number of words" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Master password hint" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organisation" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -871,6 +906,15 @@ "message": "Verify with Duo Security for your organisation using the Duo Mobile app, SMS, phone call, or U2F security key.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognise this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Self-hosted environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premise hosted Bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Custom environment" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "Server URL" }, @@ -956,6 +997,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Overwrite password" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Purchase Premium" }, - "premiumPurchaseAlert": { - "message": "You can purchase premium membership on the bitwarden.com web vault. Do you want to visit the website now?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Require password or PIN on app start" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Recommended for security." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Hide my email address from recepients." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, "rememberEmail": { "message": "Remember email" }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "Toggle character count", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Are you trying to log in?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Login attempt by $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Time" }, - "confirmLogIn": { - "message": "Confirm login" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Deny login" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Login confirmed for $EMAIL$ on $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "confirmLoginAtemptForMail": { - "message": "Confirm login attempt for $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Log in requested" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "region": { "message": "Region" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorise" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json index b7fbb2fbe93..200c3f4a1de 100644 --- a/apps/desktop/src/locales/eo/messages.json +++ b/apps/desktop/src/locales/eo/messages.json @@ -36,7 +36,7 @@ "message": "Kolektoj" }, "searchVault": { - "message": "Traserĉi trezorejon" + "message": "Traserĉi la trezorejon" }, "addItem": { "message": "Aldoni eron" @@ -249,6 +249,20 @@ "error": { "message": "Eraro" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "januaro" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Number of words" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Master password hint" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -871,6 +906,15 @@ "message": "Verify with Duo Security for your organization using the Duo Mobile app, SMS, phone call, or U2F security key.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Self-hosted environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Custom environment" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "Server URL" }, @@ -956,6 +997,9 @@ "no": { "message": "Ne" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Overwrite password" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Purchase Premium" }, - "premiumPurchaseAlert": { - "message": "You can purchase premium membership on the bitwarden.com web vault. Do you want to visit the website now?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Require password or PIN on app start" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Recommended for security." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Hide my email address from recipients." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Hazarda" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, "rememberEmail": { "message": "Remember email" }, - "notYou": { - "message": "Ĉu ne vi?" - }, "newAroundHere": { "message": "New around here?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "Toggle character count", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Are you trying to log in?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Login attempt by $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Tempo" }, - "confirmLogIn": { - "message": "Confirm login" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Deny login" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Login confirmed for $EMAIL$ on $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "confirmLoginAtemptForMail": { - "message": "Confirm login attempt for $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Log in requested" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "region": { "message": "Regiono" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json index 9cb82ceb92d..665721beb3a 100644 --- a/apps/desktop/src/locales/es/messages.json +++ b/apps/desktop/src/locales/es/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Enero" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Número de palabras" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Pista de la contraseña maestra" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Unirse a organización" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Introduce la dirección de correo electrónico de tu cuenta y se te enviará la pista de tu contraseña" }, - "passwordHint": { - "message": "Pista de contraseña" - }, - "enterEmailToGetHint": { - "message": "Introduce el correo electrónico de tu cuenta para recibir la pista de tu contraseña maestra." - }, "getMasterPasswordHint": { "message": "Obtener pista de la contraseña maestra" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "La autenticación fue cancelada o tardó demasiado. Por favor, inténtalo de nuevo." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Código de verificación incorrecto" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Recordarme" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Reenviar código de verificación por correo electrónico" }, "useAnotherTwoStepMethod": { "message": "Utilizar otro método de autenticación en dos pasos" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Inserta tu YubiKey en el puerto USB de tu equipo y posteriormente pulsa su botón." }, @@ -871,6 +906,15 @@ "message": "Verificar con Duo Security para tu organización usando la aplicación Duo Mobile, SMS, llamada telefónica o llave de seguridad U2F.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Opciones de la autenticación en dos pasos" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Entorno de alojamiento propio" }, - "selfHostedEnvironmentFooter": { - "message": "Especifica la URL base de tu instalación de Bitwarden de alojamiento propio." - }, "selfHostedBaseUrlHint": { "message": "Especifica la dirección URL base de tu instalación alojada localmente de Bitwarden. Por ejemplo: https://bitwarden.empresa.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Entorno personalizado" }, - "customEnvironmentFooter": { - "message": "Para usuarios avanzados. Puedes especificar la URL base de cada servicio de forma independiente." - }, "baseUrl": { "message": "URL del servidor" }, @@ -956,6 +997,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Sobreescribir contraseña" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Comprar Premium" }, - "premiumPurchaseAlert": { - "message": "Puedes comprar la membresía Premium en la caja fuerte web de bitwarden.com. ¿Quieres visitar el sitio web ahora?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Solicitar contraseña o PIN al iniciar la aplicación" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Recomendado por seguridad." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Autenticar WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Ocultar mi dirección de correo electrónico a los destinatarios." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Utiliza la bandeja de entrada Catch-All configurada por tu dominio." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Aleatorio" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "No se puede obtener el ID de la cuenta de correo electrónico enmascarado de $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Iniciar sesión con contraseña maestra" }, - "loggingInAs": { - "message": "Iniciando sesión como" - }, "rememberEmail": { "message": "Recordar email" }, - "notYou": { - "message": "¿No eres tú?" - }, "newAroundHere": { "message": "¿Nuevo por aquí?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Inicio de sesión iniciado" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "Se ha enviado una notificación a tu dispositivo." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "Activar recuento de caracteres", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "¿Estás intentando iniciar sesión?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Intento de inicio de sesión de $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Hora" }, - "confirmLogIn": { - "message": "Confirmar inicio de sesión" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Denegar inicio de sesión" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Inicio de sesión confirmado para $EMAIL$ en $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "Esta solicitud ya no es válida." }, - "confirmLoginAtemptForMail": { - "message": "Confirmar intento de inicio de sesión para $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Inicio de sesión solicitado" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creando una cuenta en" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Contraseña débil encontrada en una filtración de datos. Utilice una contraseña única para proteger su cuenta. ¿Está seguro de que desea utilizar una contraseña comprometida?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Comprobar filtración de datos conocidos para esta contraseña" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Solicitar aprobación del administrador" }, - "approveWithMasterPassword": { - "message": "Aprobar con contraseña maestra" - }, "region": { "message": "Región" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Tu solicitud ha sido enviada a tu administrador." }, - "youWillBeNotifiedOnceApproved": { - "message": "Se te notificará una vez aprobado." - }, "troubleLoggingIn": { "message": "¿Tienes problemas para iniciar sesión?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Se requiere el inicio de sesión en dos pasos de Duo para tu cuenta." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Inicia Duo en el navegador" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json index 276d3565f1a..c4ca9bef886 100644 --- a/apps/desktop/src/locales/et/messages.json +++ b/apps/desktop/src/locales/et/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Viga" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Jaanuar" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Sõnade arv" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Ülemparooli vihje" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Liitu organisatsiooniga" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Sisesta oma e-maili aadress ja sulle saadetakse sinna parooli vihje" }, - "passwordHint": { - "message": "Parooli vihje" - }, - "enterEmailToGetHint": { - "message": "Ülemparooli vihje saamiseks sisesta oma konto e-posti aadress." - }, "getMasterPasswordHint": { "message": "Tuleta ülemparooli vihjega meelde" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "Autentimine tühistati või kestis liiga kaua aega. Palun proovi uuesti." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Vale kinnituskood" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Jäta mind meelde" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Saada kinnituskood uuesti e-postile" }, "useAnotherTwoStepMethod": { "message": "Kasuta teist kaheastmelist sisselogimise meetodit" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Sisesta oma YubiKey arvuti USB porti ja kliki sellele nupule." }, @@ -871,6 +906,15 @@ "message": "Kinnita organisatsiooni jaoks Duo Security abil, kasutades selleks Duo Mobile rakendust, SMS-i, telefonikõnet või U2F turvavõtit.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Kaheastmelise sisselogimise valikud" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Self-hosted Environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premise hosted bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Sisesta enda ise-majutatud Bitwardeni serveri nimi (base URL). Näiteks: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Kohandatud keskkond" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "Serveri URL" }, @@ -956,6 +997,9 @@ "no": { "message": "Ei" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Kirjuta parool üle" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Osta Preemium" }, - "premiumPurchaseAlert": { - "message": "Saad Preemium versiooni osta bitwarden.com veebihoidlas. Soovid seda kohe teha?" - }, "premiumPurchaseAlertV2": { "message": "Sa saad hankida Preemiumi oma konto seadetes veebibrauseris." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Nõua parooli või PINi rakenduse kävitumisel" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Soovitatud turvalisuse huvides." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "WebAuthn kinnitamine" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Ära näita saajatele minu e-posti aadressi." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Kasuta domeenipõhist kogumisaadressi." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Juhuslik" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Ei õnnestunud hankida pakkuja $SERVICENAME$ maskeeritud emaili konto ID-d.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Logi sisse ülemparooliga" }, - "loggingInAs": { - "message": "Sisselogimas kui" - }, "rememberEmail": { "message": "Mäleta e-posti aadressi" }, - "notYou": { - "message": "Pole sina?" - }, "newAroundHere": { "message": "Oled siin uus?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Alustan sisselogimist" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "Sinu seadmesse saadeti teavitus." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "Loenda kirjatähtede hulka", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Oled sisse logimas?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Sisselogimise päring $EMAIL$-lt", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Aeg" }, - "confirmLogIn": { - "message": "Kinnita sisselogimine" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Tühista sisselogimine" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "$EMAIL$ sisselogimine seadmes $DEVICE$ on kinnitatud", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "See päring ei ole enam kehtiv." }, - "confirmLoginAtemptForMail": { - "message": "Kinnita sisselogimise katse $EMAIL$-le", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Sisselogimise päring on saadetud" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Loon kontot asukohas" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Tuvastati nõrk ning andmelekkes lekkinud ülemparool. Kasuta konto paremaks turvamiseks tugevamat parooli. Oled kindel, et soovid nõrga parooliga jätkata?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Otsi seda parooli teadaolevatest andmeleketest" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Küsi administraatori nõusolekut" }, - "approveWithMasterPassword": { - "message": "Kinnita ülemparooliga" - }, "region": { "message": "Piirkond" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Sinu taotlus saadeti administraatorile." }, - "youWillBeNotifiedOnceApproved": { - "message": "Kinnitamise järel saad selle kohta teavituse." - }, "troubleLoggingIn": { "message": "Ei õnnestu sisse logida?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo kahe-astmeline sisselogimine on sinu kontol nõutud." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Käivita Duo brauseris" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "SSO-ga sisselogimiseks ei leitud ühtegi vaba porti." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/eu/messages.json b/apps/desktop/src/locales/eu/messages.json index 41e8eda96c7..1abc24612ca 100644 --- a/apps/desktop/src/locales/eu/messages.json +++ b/apps/desktop/src/locales/eu/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Akatsa" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Urtarrila" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Hitz kopurua" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Master password hint" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Pasahitza gogoratzeko pista" - }, - "enterEmailToGetHint": { - "message": "Sartu zure kontuko emaila pasahitz nagusiaren pista jasotzeko." - }, "getMasterPasswordHint": { "message": "Jaso pasahitz nagusiaren pista" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Egiaztatze-kodea ez da baliozkoa" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Gogora nazazu" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Berbidali email bidezko egiaztatze-kodea" }, "useAnotherTwoStepMethod": { "message": "Erabili bi urratseko saio hasierarako beste modu bat" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Sartu zure YubiKey-a ordenagailuko USB atakan, ondoren, sakatu bere botoia." }, @@ -871,6 +906,15 @@ "message": "Egiaztatu zure erakunderako Duo Securityrekin Duo Mobile aplikazioa, SMS, telefono deia edo U2F segurtasun-gakoa erabiliz.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Bi urratseko saio hasieraren aukerak" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Ostatze ingurune propioa" }, - "selfHostedEnvironmentFooter": { - "message": "Bitwarden instalatzeko, zehaztu ostatatze propioaren oinarrizko URL-a." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Ingurune pertsonalizatua" }, - "customEnvironmentFooter": { - "message": "Erabiltzaile aurreratuentzat. Zerbitzu bakoitzarentzako oinarrizko URL-a zehaztu dezakezu independienteki." - }, "baseUrl": { "message": "Zerbitzariaren URL-a" }, @@ -956,6 +997,9 @@ "no": { "message": "Ez" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Berridatzi pasahitza" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Premium erosi" }, - "premiumPurchaseAlert": { - "message": "Zure premium bazkidetza bitwarden.com webguneko kutxa gotorrean ordaindu dezakezu. Orain bisitatu nahi duzu webgunea?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Require password or PIN on app start" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Recommended for security." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "WebAuthn autentifikatu" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Ezkutatu nire emaila hartzaileei." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Erabili zure domeinuan konfiguratutako sarrerako ontzia." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Ausazkoa" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Hasi saioa pasahitz nagusiarekin" }, - "loggingInAs": { - "message": "gisa saioa hasi" - }, "rememberEmail": { "message": "Emaila gogoratu" }, - "notYou": { - "message": "Zu ez?" - }, "newAroundHere": { "message": "Berria hemendik?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "Toggle character count", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Are you trying to log in?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Login attempt by $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Time" }, - "confirmLogIn": { - "message": "Confirm login" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Deny login" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Login confirmed for $EMAIL$ on $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "confirmLoginAtemptForMail": { - "message": "Confirm login attempt for $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Log in requested" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "region": { "message": "Region" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json index fdc9ba6d31b..2d13b86c4a8 100644 --- a/apps/desktop/src/locales/fa/messages.json +++ b/apps/desktop/src/locales/fa/messages.json @@ -249,6 +249,20 @@ "error": { "message": "خطا" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "ژانویه" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "تعداد کلمات" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Master password hint" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "یادآور کلمه عبور" - }, - "enterEmailToGetHint": { - "message": "برای دریافت یادآور کلمه عبور اصلی خود نشانی ایمیل‌تان را وارد کنید." - }, "getMasterPasswordHint": { "message": "دریافت یادآور کلمه عبور اصلی" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "کد تأیید نامعتبر است" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "مرا به خاطر بسپار" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "ارسال دوباره ایمیل کد تأیید" }, "useAnotherTwoStepMethod": { "message": "استفاده از روش ورود دو مرحله‌ای دیگر" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "YubiKey خود را وارد پورت USB رایانه کنید، بعد دکمه آن را بفشارید." }, @@ -871,6 +906,15 @@ "message": "از Duo Security با استفاده از برنامه تلفن همراه، پیامک، تماس تلفنی یا کلید امنیتی U2F برای تأیید سازمان خود استفاده کنید.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "گزینه‌های ورود دو مرحله‌ای" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "محیط خود میزبان" }, - "selfHostedEnvironmentFooter": { - "message": "نشانی اینترنتی پایه فرضی نصب Bitwarden میزبانی شده را مشخص کنید." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "محیط سفارشی" }, - "customEnvironmentFooter": { - "message": "برای کاربران پیشرفته. شما می‌توانید نشانی پایه هر سرویس را مستقلاً تعیین کنید." - }, "baseUrl": { "message": "نشانی اینترنتی سرور" }, @@ -956,6 +997,9 @@ "no": { "message": "خیر" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "بازنویسی کلمه عبور" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "خرید پرمیوم" }, - "premiumPurchaseAlert": { - "message": "شما می‌توانید عضویت پرمیوم را از گاوصندوق وب bitwarden.com خریداری کنید. مایلید اکنون از وب‌سایت بازید کنید؟" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "هنگام شروع برنامه، رمز عبور یا پین مورد نیاز است" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "برای امنیت توصیه می‌شود." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "تأیید اعتبار در WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "نشانی ایمیلم را از گیرندگان مخفی کن." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "از صندوق ورودی پیکربندی شده دامنه خود استفاده کنید." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "تصادفی" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "با کلمه عبور اصلی وارد شوید" }, - "loggingInAs": { - "message": "در حال ورود به عنوان" - }, "rememberEmail": { "message": "ایمیل را به خاطر بسپار" }, - "notYou": { - "message": "شما نیستید؟" - }, "newAroundHere": { "message": "اینجا تازه واردی؟" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "ورود به سیستم آغاز شد" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "یک اعلان به دستگاه شما ارسال شده است." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "تغییر تعداد کاراکترها", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "آیا در تلاش برای ورود به سیستم هستید؟" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "تلاش برای ورود به سیستم توسط $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "زمان" }, - "confirmLogIn": { - "message": "تأیید ورود" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "رد ورود" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "ورود به سیستم برای $EMAIL$ در $DEVICE$ تأیید شد", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "این درخواست دیگر معتبر نیست." }, - "confirmLoginAtemptForMail": { - "message": "تأیید تلاش برای ورود به سیستم برای $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "ورود الزامیست" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "کلمه عبور ضعیف شناسایی و در یک نقض داده پیدا شد. از یک کلمه عبور قوی و منحصر به فرد برای محافظت از حساب خود استفاده کنید. آیا مطمئنید که می‌خواهید از این کلمه عبور استفاده کنید؟" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "نقض اطلاعات شناخته شده برای این کلمه عبور را بررسی کنید" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "درخواست تأیید مدیر" }, - "approveWithMasterPassword": { - "message": "تأیید با کلمه عبور اصلی" - }, "region": { "message": "منطقه" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "درخواست شما به مدیرتان فرستاده شد." }, - "youWillBeNotifiedOnceApproved": { - "message": "به محض تأیید مطلع خواهید شد." - }, "troubleLoggingIn": { "message": "در ورود مشکلی دارید؟" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index 69092054f28..2da2b7dcf7a 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -101,7 +101,7 @@ "message": "Salasana" }, "passphrase": { - "message": "Salauslauseke" + "message": "Salauslause" }, "editItem": { "message": "Muokkaa kohdetta" @@ -190,19 +190,19 @@ "message": "Sormenjälki" }, "sshKeyAlgorithm": { - "message": "Avaintyyppi" + "message": "Avaimen tyyppi" }, "sshKeyAlgorithmED25519": { "message": "ED25519" }, "sshKeyAlgorithmRSA2048": { - "message": "RSA 2048-Bit" + "message": "RSA 2048-bit" }, "sshKeyAlgorithmRSA3072": { - "message": "RSA 3072-Bit" + "message": "RSA 3072-bit" }, "sshKeyAlgorithmRSA4096": { - "message": "RSA 4096-Bit" + "message": "RSA 4096-bit" }, "sshKeyGenerated": { "message": "Uusi SSH-avain luotiin" @@ -223,7 +223,7 @@ "message": "Syötä salasana" }, "sshAgentUnlockRequired": { - "message": "Ole hyvä ja avaa holvisi hyväksyäksesi SSH-avainpyynnön." + "message": "Hyväksy SSH-avainpyyntö avaamalla holvisi." }, "sshAgentUnlockTimeout": { "message": "SSH-avainpyyntö aikakatkaistiin." @@ -249,6 +249,20 @@ "error": { "message": "Virhe" }, + "decryptionError": { + "message": "Virhe salauksen purussa" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden ei voinut purkaa alla olevien holvin kohteiden salausta." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Tammikuu" }, @@ -529,10 +543,6 @@ "message": "Sisällytä erikoismerkkejä", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Sanojen määrä" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Kirjaudu Bitwardeniin" }, + "enterTheCodeSentToYourEmail": { + "message": "Syötä sähköpostitse vastaanottamasi koodi" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Syötä todennussovelluksesi näyttämä koodi" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Tunnistaudu koskettamalla YubiKeytäsi" + }, "logInWithPasskey": { "message": "Kirjaudu pääsyavaimella" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Pääsalasanan vihje" }, + "passwordStrengthScore": { + "message": "Salasanan vahvuusarvio $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Liity organisaatioon" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Syötä tilisi sähköpostiosoite, niin salasanavihjeesi lähetetään sinulle sähköpostitse" }, - "passwordHint": { - "message": "Salasanavihje" - }, - "enterEmailToGetHint": { - "message": "Syötä tilisi sähköpostiosoite saadaksesi pääsalasanan vihjeen." - }, "getMasterPasswordHint": { "message": "Pyydä pääsalasanan vihjettä" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "Tunnistautuminen peruttiin tai se kesti liian kauan. Yritä uudelleen." }, + "openInNewTab": { + "message": "Avaa uudessa välilehdessä" + }, "invalidVerificationCode": { "message": "Virheellinen todennuskoodi" }, @@ -832,14 +857,24 @@ "rememberMe": { "message": "Muista minut" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Älä kysy uudelleen tällä laitteella 30 päivään" + }, "sendVerificationCodeEmailAgain": { "message": "Lähetä todennuskoodi sähköpostitse uudelleen" }, "useAnotherTwoStepMethod": { "message": "Käytä vaihtoehtoista todennustapaa" }, + "selectAnotherMethod": { + "message": "Valitse vaihtoehtoinen tapa", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Käytä palautuskoodiasi" + }, "insertYubiKey": { - "message": "Kytke YubiKey-todennuslaitteesi tietokoneen USB-porttiin ja paina sen painiketta." + "message": "Kytke YubiKey-suojausavaimesi tietokoneen USB-porttiin ja kosketa sen painiketta." }, "insertU2f": { "message": "Kytke suojausavaimesi tietokoneen USB-porttiin ja jos laitteessa on painike, paina sitä." @@ -861,7 +896,7 @@ "message": "Yubico OTP -suojausavain" }, "yubiKeyDesc": { - "message": "Käytä YubiKey-todennuslaitetta tilisi avaukseen. Toimii YubiKey 4, 4 Nano, 4C sekä NEO -laitteiden kanssa." + "message": "Käytä YubiKey-suojausavainta tilisi avaukseen. Toimii YubiKey 4, 4 Nano, 4C ja NEO -laitteiden kanssa." }, "duoDescV2": { "message": "Syötä Duo Securityn luoma koodi.", @@ -871,6 +906,15 @@ "message": "Vahvista organisaatiollesi Duo Securityn avulla käyttäen Duo Mobile ‑sovellusta, tekstiviestiä, puhelua tai U2F-suojausavainta.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Vahvista henkilöllisyytesi" + }, + "weDontRecognizeThisDevice": { + "message": "Laitetta ei tunnistettu. Vahvista henkilöllisyytesi syöttämällä sähköpostitse saamasi koodi." + }, + "continueLoggingIn": { + "message": "Jatka kirjautumista" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Kaksivaiheisen kirjautumisen asetukset" }, + "selectTwoStepLoginMethod": { + "message": "Valitse todennustapa" + }, "selfHostedEnvironment": { "message": "Itse ylläpidetty palvelinympäristö" }, - "selfHostedEnvironmentFooter": { - "message": "Määritä omassa palvelinympäristössäsi suoritettavan Bitwarden-asennuksen pääverkkotunnus." - }, "selfHostedBaseUrlHint": { "message": "Määritä itse ylläpitämäsi Bitwarden-asennuksen perusosoite. Esimerkki: https://bitwarden.yritys.fi." }, @@ -913,17 +957,14 @@ "customEnvironment": { "message": "Mukautettu palvelinympäristö" }, - "customEnvironmentFooter": { - "message": "Edistyneille käyttäjille. Voit määrittää jokaiselle palvelulle oman pääverkkotunnuksen." - }, "baseUrl": { "message": "Palvelimen URL" }, "authenticationTimeout": { - "message": "Todennuksen aikakatkaisu" + "message": "Todennus aikakatkaistiin" }, "authenticationSessionTimedOut": { - "message": "Todennusistunto aikakatkaistiin. Ole hyvä ja aloita kirjautumisprosessi uudelleen." + "message": "Todennusistunto aikakatkaistiin. Aloita kirjautumisprosessi alusta." }, "selfHostBaseUrl": { "message": "Itse ylläpidetyn palvelimen URL-osoite", @@ -956,6 +997,9 @@ "no": { "message": "En" }, + "location": { + "message": "Sijainti" + }, "overwritePassword": { "message": "Korvaa salasana" }, @@ -1047,11 +1091,11 @@ "message": "Voit vaihtaa pääsalasanasi Bitwardenin verkkosovelluksessa." }, "fingerprintPhrase": { - "message": "Tunnistelauseke", + "message": "Tunnistelause", "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": "Tilisi tunnistelauseke", + "message": "Tilisi tunnistelause", "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": { @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Osta Premium" }, - "premiumPurchaseAlert": { - "message": "Voit ostaa Premium-jäsenyyden bitwarden.com-verkkoholvista. Haluatko käydä sivustolla nyt?" - }, "premiumPurchaseAlertV2": { "message": "Voit ostaa Premiumin tiliasetuksistasi Bitwardenin verkkosovelluksen kautta." }, @@ -1701,7 +1742,7 @@ "message": "Heikko pääsalasana" }, "weakMasterPasswordDesc": { - "message": "Valitsemasi pääsalasana on heikko. Sinun tulisi käyttää vahvaa pääsalasanaa (tai salauslauseketta) suojataksesi Bitwarden-tilisi kunnolla. Haluatko varmasti käyttää tätä pääsalasanaa?" + "message": "Valitsemasi pääsalasana on heikko. Sinun tulisi käyttää vahvaa pääsalasanaa (tai salauslausetta) suojataksesi Bitwarden-tilisi kunnolla. Haluatko varmasti käyttää tätä pääsalasanaa?" }, "pin": { "message": "PIN", @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Pyydä salasana tai PIN-koodi sovelluksen käynnistyessä" }, + "requirePasswordWithoutPinOnStart": { + "message": "Vaadi salasana sovelluksen käynnistyessä" + }, "recommendedForSecurity": { "message": "Suositeltavaa parempaa suojausta varten." }, @@ -2005,7 +2049,7 @@ "message": "Vaadi selainintegraation vahvistus" }, "enableBrowserIntegrationFingerprintDesc": { - "message": "Paranna tietoturvaa vaatimalla työpöytäsovelluksen ja selainlaajennuksen kytköksen todennus tunnistelausekkeella. Tämä vaatii aina yhteyden muodostuessa vahvistuksen käyttäjältä." + "message": "Paranna tietoturvaa vaatimalla työpöytäsovelluksen ja selainlaajennuksen kytköksen todennus tunnistelauseella. Tämä vaatii aina yhteyden muodostuessa vahvistuksen käyttäjältä." }, "enableHardwareAcceleration": { "message": "Käytä laitteistokiihdytystä" @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "WebAuthn-todennus" }, + "readSecurityKey": { + "message": "Lue suojausavain" + }, + "awaitingSecurityKeyInteraction": { + "message": "Odotetaan suojausavaimen aktivointia..." + }, "hideEmail": { "message": "Piilota sähköpostiosoitteeni vastaanottajilta." }, @@ -2505,7 +2555,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " Käytä $RECOMMENDED$ tai useampaa sanaa vahvan salalauseen luomiseksi.", + "message": " Käytä vahvaan salalauseeseen ainakin $RECOMMENDED$ sanaa.", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Käytä verkkotunnuksesi catch-all-postilaatikkoa." }, + "useThisEmail": { + "message": "Käytä tätä sähköpostia" + }, "random": { "message": "Satunnainen" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "$SERVICENAME$ -palvelun peittämän sähköpostitilin tunnusta ei saatu.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Kirjaudu pääsalasanalla" }, - "loggingInAs": { - "message": "Kirjaudutaan tunnuksella" - }, "rememberEmail": { "message": "Muista sähköpostiosoite" }, - "notYou": { - "message": "Etkö se ollut sinä?" - }, "newAroundHere": { "message": "Oletko uusi täällä?" }, @@ -2722,23 +2793,32 @@ "loginInitiated": { "message": "Kirjautuminen aloitettu" }, + "logInRequestSent": { + "message": "Pyyntö lähetetty" + }, "notificationSentDevice": { "message": "Laitteellesi on lähetetty ilmoitus." }, "aNotificationWasSentToYourDevice": { "message": "Laitteeseesi lähetettiin ilmoitus" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Varmista, että vahvistavan laitteen holvi on avattu ja että se näyttää saman tunnistelausekkeen" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Ennen hyväksyntää varmista, että tunnistelause vastaa alla olevaa lausetta." }, "needAnotherOptionV1": { "message": "Tarvitsetko toisen vaihtoehdon?" }, "fingerprintMatchInfo": { - "message": "Varmista, että vahvistavan laitteen holvi on avattu ja että se näyttää saman tunnistelausekkeen." + "message": "Varmista, että vahvistavan laitteen holvi on avattu ja että se näyttää saman tunnistelauseen." }, "fingerprintPhraseHeader": { - "message": "Tunnistelauseke" + "message": "Tunnistelause" }, "youWillBeNotifiedOnceTheRequestIsApproved": { "message": "Ilmoitamme sinulle, kun pyyntösi on hyväksytty" @@ -2759,11 +2839,11 @@ "message": "Näytä/piilota merkkikohtainen numerointi", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Yritätkö kirjautua sisään?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Kirjautumisyritys tunnuksella $EMAIL$.", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Aika" }, - "confirmLogIn": { - "message": "Vahvista kirjautuminen" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Hylkää kirjautuminen" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Kirjautuminen vahvistettu tunnuksella $EMAIL$ alustalla $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "Pyyntö ei ole enää voimassa." }, - "confirmLoginAtemptForMail": { - "message": "Vahvista kirjautuminen tunnuksella $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Kirjautumista pyydetty" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Luodaan tili palvelimelle" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Havaittiin heikko ja tietovuodosta löytynyt salasana. Sinun tulisi suojata tilisi vahvalla ja ainutlaatuisella salasanalla. Haluatko varmasti käyttää tätä salasanaa?" }, + "useThisPassword": { + "message": "Käytä tätä salasanaa" + }, + "useThisUsername": { + "message": "Käytä tätä käyttäjätunnusta" + }, "checkForBreaches": { "message": "Tarkasta esiintyykö salasanaa tunnetuissa tietovuodoissa" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Pyydä hyväksyntää ylläpidolta" }, - "approveWithMasterPassword": { - "message": "Hyväksy pääsalasanalla" - }, "region": { "message": "Alue" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Pyyntösi on välitetty ylläpidollesi." }, - "youWillBeNotifiedOnceApproved": { - "message": "Saat ilmoituksen kun se on hyväksytty." - }, "troubleLoggingIn": { "message": "Ongelmia kirjautumisessa?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Tilillesi kirjautuminen vaatii Duo-vahvistuksen." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo edellyttää tililtäsi kaksivaiheista tunnistautumista. Viimeistele kirjautuminen seuraamalla seuraavia vaiheita." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Avaa Duo verkkoselaimessa" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "Kertakirjautumiselle ei löytynyt vapaita portteja." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrinen avaus ei ole käytettävissä, koska PIN-koodi tai salasanan lukituksen avaus vaaditaan ensin." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrinen lukituksen avaus ei ole tällä hetkellä käytettävissä." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometrinen avaus ei ole käytettävissä, koska sitä ei ole otettu käyttöön osoitteelle $EMAIL$ Bitwardenin työpöytäsovelluksessa.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrinen avaus ei ole tällä hetkellä käytettävissä tuntemattomasta syystä." + }, "authorize": { "message": "Valtuuta" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Vahvista SSH-avainkäyttö" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "pyytää pääsyä" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "Sovellus" }, - "sshKeyPasswordUnsupported": { - "message": "Salasanasuojattujen SSH-avainten tuontia ei vielä tueta" - }, "invalidSshKey": { "message": "SSH-avain on virheellinen" }, @@ -3389,8 +3517,8 @@ "importSshKeyFromClipboard": { "message": "Tuo avain leikepöydältä" }, - "sshKeyPasted": { - "message": "SSH-avain on tuotu" + "sshKeyImported": { + "message": "SSH key imported successfully" }, "fileSavedToDevice": { "message": "Tiedosto tallennettiin laitteelle. Hallitse sitä laitteesi latauksista." @@ -3405,7 +3533,7 @@ "message": "Bitwarden lähettää tilisi sähköpostiosoitteeseen koodin, jolla voit vahvistaa kirjautumiset uusista laitteista helmikuusta 2025 alkaen." }, "newDeviceVerificationNoticeContentPage2": { - "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + "message": "Voit määrittää kaksivaiheisen kirjautumisen tilisi vaihtoehtoiseksi suojaustavaksi tai vaihtaa sähköpostiosoitteesi sellaiseen, johon sinulla on pääsy." }, "remindMeLater": { "message": "Muistuta myöhemmin" @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Muuta tilin sähköpostiosoitetta" + }, + "allowScreenshots": { + "message": "Salli kuvankaappaus" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Vahvista, että ikkuna on vielä näkyvissä" + }, + "confirmWindowStillVisibleContent": { + "message": "Vahvista, että ikkuna on vielä näkyvissä." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Laajennus on päivitettävä" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Vaihda vaarantunut salasana" } } diff --git a/apps/desktop/src/locales/fil/messages.json b/apps/desktop/src/locales/fil/messages.json index a8a36cd94af..d54c5a21a87 100644 --- a/apps/desktop/src/locales/fil/messages.json +++ b/apps/desktop/src/locales/fil/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Mali" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Enero" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Ang bilang ng mga salitaNumero ng mga salita" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Master password hint" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Mungkahi sa Password" - }, - "enterEmailToGetHint": { - "message": "Ipasok ang iyong email address ng account para makatanggap ng hint ng iyong master password." - }, "getMasterPasswordHint": { "message": "Makuha ang Punong Password na Hint" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Maling verification code" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Tandaan mo ako" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Ipadala muli ang email ng verification code" }, "useAnotherTwoStepMethod": { "message": "Gamitin ang isa pang two-step na paraan ng pag-login" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Ipasok ang iyong YubiKey sa USB port ng iyong computer, pagkatapos ay hawakan ang pindutan nito." }, @@ -871,6 +906,15 @@ "message": "Patunayan sa Duo Security para sa iyong samahan gamit ang Duo Mobile app, SMS, tawag sa telepono, o U2F security key.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Mga pagpipilian para sa two-step login" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Kapaligirang self-hosted" }, - "selfHostedEnvironmentFooter": { - "message": "Tukuyin ang base URL ng iyong on premises na naka host sa pag install ng Bitwarden." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Kapaligirang Custom" }, - "customEnvironmentFooter": { - "message": "Para sa mga advanced na gumagamit. Maaari mong tukuyin ang base URL ng bawat serbisyo nang independiyente." - }, "baseUrl": { "message": "URL ng Server" }, @@ -956,6 +997,9 @@ "no": { "message": "Hindi" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "I-overwrite ang password" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Bumili ng Premium" }, - "premiumPurchaseAlert": { - "message": "Maaari kang bumili ng premium membership sa bitwarden.com web vault. Gusto mo bang bisitahin ang website ngayon?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Require password or PIN on app start" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Recommended for security." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "I-authenticate ang WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Itago ang aking email address mula sa mga tatanggap." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Gamitin ang naka configure na inbox ng catch all ng iyong domain." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Mag-login gamit ang pangunahing password" }, - "loggingInAs": { - "message": "Pag log in bilang" - }, "rememberEmail": { "message": "Tandaan ang email" }, - "notYou": { - "message": "Hindi ikaw?" - }, "newAroundHere": { "message": "Mag-login gamit ang pangunahing password?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "Naipadala na ang notification sa iyong device." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "I-toggle ang bilang ng character", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Sinusubukan mo bang mag-log in?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Pag-login pagtatangka sa pamamagitan ng $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Oras na" }, - "confirmLogIn": { - "message": "Kumpirmahin ang pag-login" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Tanggihan ang pag login" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Nakumpirma ang pag-login para sa $EMAIL$ sa $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "Hindi na valid ang request na ito." }, - "confirmLoginAtemptForMail": { - "message": "Kumpirmahin ang pagtatangka sa pag-login para sa $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Hiniling ang pag log in" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Mahinang password na nakilala at nakita sa data breach. Gamitin ang malakas at natatanging password upang makaproteksyon sa iyong account. Sigurado ka ba na gusto mong gamitin ang password na ito?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Tingnan ang kilalang breaches ng data para sa password na ito" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "region": { "message": "Region" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index a353653e1e3..431a70ef373 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -169,7 +169,7 @@ "message": "Numéro de passeport" }, "licenseNumber": { - "message": "Numéro de permis" + "message": "Numéro de licence" }, "email": { "message": "Courriel" @@ -217,25 +217,25 @@ "message": "Confirmez le mot de passe" }, "enterSshKeyPasswordDesc": { - "message": "Entrez le mot de passe de la clé SSH." + "message": "Saisissez le mot de passe de la clé SSH." }, "enterSshKeyPassword": { - "message": "Entrez le mot de passe" + "message": "Saisissez le mot de passe" }, "sshAgentUnlockRequired": { "message": "Veuillez déverrouiller votre coffre pour approuver la demande de clé SSH." }, "sshAgentUnlockTimeout": { - "message": "La requête de clé SSH a expiré." + "message": "La demande de clé SSH a expiré." }, "enableSshAgent": { - "message": "Activer l'agent SSH" + "message": "Activez l'agent SSH" }, "enableSshAgentDesc": { - "message": "Activez l'agent SSH pour signer les requêtes SSH directement depuis votre coffre Bitwarden." + "message": "Activez l'agent SSH pour signer les demandes SSH directement depuis votre coffre Bitwarden." }, "enableSshAgentHelp": { - "message": "L'agent SSH est un service destiné aux développeurs qui vous permet de signer des requêtes SSH directement depuis votre coffre Bitwarden." + "message": "L'agent SSH est un service - destiné aux développeurs - qui vous permet de signer des demandes SSH directement depuis votre coffre Bitwarden." }, "premiumRequired": { "message": "Premium requis" @@ -249,6 +249,20 @@ "error": { "message": "Erreur" }, + "decryptionError": { + "message": "Erreur de déchiffrement" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden n'a pas pu déchiffrer le(s) élément(s) du coffre listé(s) ci-dessous." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contacter le service clientèle", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "afin d'éviter toute perte de données supplémentaire.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Janvier" }, @@ -529,10 +543,6 @@ "message": "Inclure des caractères spéciaux", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Nombre de mots" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Se connecter à Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Saisissez le code envoyé à votre adresse courriel" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Saisissez le code de votre application d'authentification" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Touchez votre YubiKey pour vous authentifier" + }, "logInWithPasskey": { "message": "Se connecter avec une clé d'accès" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Indice du mot de passe principal" }, + "passwordStrengthScore": { + "message": "Score de force du mot de passe $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Rejoindre l'organisation" }, @@ -715,17 +743,11 @@ "message": "Demander l'indice" }, "requestPasswordHint": { - "message": "Obtenir l'indice du mot de passe" + "message": "Demande d'indice de mot de passe" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Saisissez l'adresse courriel de votre compte et votre indice de mot de passe vous sera envoyé" }, - "passwordHint": { - "message": "Indice pour le mot de passe" - }, - "enterEmailToGetHint": { - "message": "Saisissez l'adresse électronique de votre compte pour recevoir l'indice de votre mot de passe principal." - }, "getMasterPasswordHint": { "message": "Obtenir l'indice du mot de passe principal" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "L'authentification a été annulée ou a pris trop de temps. Veuillez réessayer." }, + "openInNewTab": { + "message": "Ouvrir dans un nouvel onglet" + }, "invalidVerificationCode": { "message": "Code de vérification invalide" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Rester connecté" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Ne plus demander sur cet appareil pendant 30 jours" + }, "sendVerificationCodeEmailAgain": { "message": "Envoyer à nouveau le courriel de code de vérification" }, "useAnotherTwoStepMethod": { "message": "Utiliser une autre méthode de connexion en deux étapes" }, + "selectAnotherMethod": { + "message": "Sélectionner une autre méthode", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Utilisez votre code de récupération" + }, "insertYubiKey": { "message": "Insérez votre YubiKey dans le port USB de votre ordinateur puis appuyez sur son bouton." }, @@ -871,6 +906,15 @@ "message": "Sécurisez votre organisation avec Duo Security à l'aide de l'application Duo Mobile, l'envoi d'un SMS, un appel vocal ou une clé de sécurité U2F.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Vérifiez votre identité" + }, + "weDontRecognizeThisDevice": { + "message": "Nous ne reconnaissons pas cet appareil. Saisissez le code envoyé à votre adresse courriel pour vérifier votre identité." + }, + "continueLoggingIn": { + "message": "Continuer à se connecter" + }, "webAuthnTitle": { "message": "WebAuthn FIDO2" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Options d'authentification à deux facteurs" }, + "selectTwoStepLoginMethod": { + "message": "Sélectionnez la méthode d'authentification à deux facteurs" + }, "selfHostedEnvironment": { "message": "Environnement auto-hébergé" }, - "selfHostedEnvironmentFooter": { - "message": "Spécifiez l'URL de base de votre installation Bitwarden auto-hébergée." - }, "selfHostedBaseUrlHint": { "message": "Spécifiez l'URL de base de votre installation Bitwarden hébergée sur site. Exemple : https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Environnement personnalisé" }, - "customEnvironmentFooter": { - "message": "Pour utilisateurs avancés. Vous pouvez spécifier une URL de base indépendante pour chaque service." - }, "baseUrl": { "message": "URL du serveur" }, @@ -956,6 +997,9 @@ "no": { "message": "Non" }, + "location": { + "message": "Emplacement" + }, "overwritePassword": { "message": "Écraser le mot de passe" }, @@ -1320,7 +1364,7 @@ "description": "Copy credit card number" }, "copyEmail": { - "message": "Copy email" + "message": "Copier le courriel" }, "copySecurityCode": { "message": "Copier le code de sécurité", @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Acheter Premium" }, - "premiumPurchaseAlert": { - "message": "Vous pouvez acheter une adhésion Premium sur le coffre web de bitwarden.com. Voulez-vous visiter le site web maintenant ?" - }, "premiumPurchaseAlertV2": { "message": "Vous pouvez faire des achats Prenium à partir des paramètres de votre compte sur l'application web Bitwarden." }, @@ -1606,13 +1647,13 @@ "message": "Ce mot de passe sera utilisé pour exporter et importer ce fichier" }, "accountRestrictedOptionDescription": { - "message": "Utilisez la clé de chiffrement de votre compte, dérivée du nom d'utilisateur et du mot de passe principal de votre compte, pour chiffrer l'export et restreindre l'import au seul compte Bitwarden actuel." + "message": "Utilisez la clé de chiffrement de votre compte, dérivée du nom d'utilisateur et du mot de passe principal de votre compte, pour chiffrer l'exportation et restreindre l'importation au seul compte Bitwarden actuel." }, "passwordProtected": { "message": "Protégé par mot de passe" }, "passwordProtectedOptionDescription": { - "message": "Définissez un mot de passe de fichier pour chiffrer l'export et déchiffrer son import sur n'importe quel compte Bitwarden." + "message": "Définissez un mot de passe de fichier pour chiffrer l'exportation et déchiffrer son importation sur n'importe quel compte Bitwarden." }, "exportTypeHeading": { "message": "Type d'exportation" @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Exiger un mot de passe ou un code PIN au démarrage de Bitwarden" }, + "requirePasswordWithoutPinOnStart": { + "message": "Exiger un mot de passe au démarrage de l'application" + }, "recommendedForSecurity": { "message": "Recommandé pour la sécurité." }, @@ -2044,10 +2088,10 @@ "message": "Les options de biométrie dans le navigateur nécessitent au préalable l'activation des options de biométrie dans l'application de bureau." }, "biometricsManualSetupTitle": { - "message": "Automatic setup not available" + "message": "Configuration automatique non disponible" }, "biometricsManualSetupDesc": { - "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + "message": "En raison de la méthode d'installation, la prise en charge de la biométrie n'a pas pu être activée automatiquement. Souhaitez-vous ouvrir la documentation sur la manière de procéder manuellement ?" }, "personalOwnershipSubmitError": { "message": "En raison d'une politique d'entreprise, il vous est interdit d'enregistrer des éléments dans votre coffre personnel. Sélectionnez une organisation dans l'option Propriété et choisissez parmi les collections disponibles." @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Authentifier WebAuthn" }, + "readSecurityKey": { + "message": "Lire la clé de sécurité" + }, + "awaitingSecurityKeyInteraction": { + "message": "En attente de l'interaction de la clé de sécurité..." + }, "hideEmail": { "message": "Masquer mon adresse électronique aux destinataires." }, @@ -2261,7 +2311,7 @@ "message": "Votre mot de passe principal ne répond pas aux exigences de politique 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." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "Votre organisation a désactivé le chiffrement des appareils de confiance. Veuillez définir un mot de passe principal pour accéder à votre coffre." }, "tryAgain": { "message": "Essayez de nouveau" @@ -2444,7 +2494,7 @@ "message": "Export du coffre-fort de l'organisation" }, "exportingOrganizationVaultDesc": { - "message": "Seul le coffre-fort de l'organisation associé à $ORGANIZATION$ sera exporté. Les coffres individuels ou d'autres organisations ne seront pas inclus.", + "message": "Seul le coffre de l'organisation associé à $ORGANIZATION$ sera exporté. Les coffres individuels ou d'autres organisations ne seront pas inclus.", "placeholders": { "organization": { "content": "$1", @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Utilisez la boîte de réception du collecteur configurée de votre domaine." }, + "useThisEmail": { + "message": "Utiliser ce courriel" + }, "random": { "message": "Aléatoire" }, @@ -2558,11 +2611,11 @@ "message": "Générer un alias de courriel avec un service de transfert externe." }, "forwarderDomainName": { - "message": "Email domain", + "message": "Domaine du courriel", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Choose a domain that is supported by the selected service", + "message": "Choisissez un domaine qui est pris en charge par le service sélectionné", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ a refusé votre demande. Veuillez contacter votre fournisseur de service pour assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ a refusé votre demande : $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Impossible d'obtenir l'ID de compte de messagerie masqué de $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2648,7 +2725,7 @@ } }, "forwarderUnknownError": { - "message": "Unknown $SERVICENAME$ error occurred.", + "message": "Une erreur inconnue $SERVICENAME$ s'est produite.", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -2658,7 +2735,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Unknown forwarder: '$SERVICENAME$'.", + "message": "Émetteur inconnu : '$SERVICENAME$'.", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Se connecter avec le mot de passe principal" }, - "loggingInAs": { - "message": "Connexion en tant que" - }, "rememberEmail": { "message": "Se souvenir du courriel" }, - "notYou": { - "message": "Ce n'est pas vous ?" - }, "newAroundHere": { "message": "Vous êtes nouveau ici ?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Connexion initiée" }, + "logInRequestSent": { + "message": "Demande envoyée" + }, "notificationSentDevice": { "message": "Une notification a été envoyée à votre appareil." }, "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "message": "Une notification a été envoyée à votre appareil" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Assurez-vous que votre compte est déverrouillé et que la phrase d'empreinte digitale correspond à celle de l'autre appareil" + "notificationSentDevicePart1": { + "message": "Déverrouillez Bitwarden sur votre appareil ou sur le " + }, + "notificationSentDeviceAnchor": { + "message": "application web" + }, + "notificationSentDevicePart2": { + "message": "Assurez-vous que la phrase d'empreinte correspond à celle ci-dessous avant d'approuver." }, "needAnotherOptionV1": { "message": "Besoin d'une autre option ?" @@ -2759,11 +2839,11 @@ "message": "Activer / désactiver le compte de caractères", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Essayez-vous de vous connecter ?" + "areYouTryingToAccessYourAccount": { + "message": "Essayez-vous d'accéder à votre compte ?" }, - "logInAttemptBy": { - "message": "Tentative de connexion par $EMAIL$", + "accessAttemptBy": { + "message": "Tentative d'accès par $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Heure" }, - "confirmLogIn": { - "message": "Confirmer la connexion" + "confirmAccess": { + "message": "Confirmer l'accès" }, - "denyLogIn": { - "message": "Refuser la connexion" + "denyAccess": { + "message": "Refuser l'accès" }, "logInConfirmedForEmailOnDevice": { "message": "Connexion confirmée pour $EMAIL$ sur $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "Cette demande n'est plus valide." }, - "confirmLoginAtemptForMail": { - "message": "Confirmer la tentative de connexion pour $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirmer la tentative d’accès pour $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Connexion demandée" }, + "accountAccessRequested": { + "message": "Accès au compte demandé" + }, "creatingAccountOn": { "message": "Création du compte sur" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Mot de passe faible identifié et trouvé dans une brèche de données. Utilisez un mot de passe robuste et unique pour protéger votre compte. Êtes-vous sûr de vouloir utiliser ce mot de passe ?" }, + "useThisPassword": { + "message": "Utiliser ce mot de passe" + }, + "useThisUsername": { + "message": "Utiliser ce nom d'utilisateur" + }, "checkForBreaches": { "message": "Vérifier les brèches de données connues pour ce mot de passe" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Demander l'approbation de l'administrateur" }, - "approveWithMasterPassword": { - "message": "Approuver avec le mot de passe principal" - }, "region": { "message": "Région" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Votre demande a été envoyée à votre administrateur." }, - "youWillBeNotifiedOnceApproved": { - "message": "Vous serez notifié une fois approuvé." - }, "troubleLoggingIn": { "message": "Problème pour vous connecter ?" }, @@ -3072,7 +3155,7 @@ "message": "Sous-menu" }, "toggleSideNavigation": { - "message": "Toggle side navigation" + "message": "Basculer la navigation latérale" }, "skipToContent": { "message": "Accéder au contenu" @@ -3130,7 +3213,7 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + "message": "Erreur de connexion avec le service Duo. Utilisez une autre méthode d'authentification à deux facteurs ou contactez Duo pour obtenir de l'aide." }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Lancez Duo et suivez les étapes pour terminer la connexion." @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "L'authentification à double facteur Duo est requise pour votre compte." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "L'authentification à deux facteurs Duo est requise pour votre compte. Suivez les étapes ci-dessous afin de réussir à vous connecter." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Suivez les étapes ci-dessous afin de réussir à vous connecter." + }, "launchDuo": { "message": "Lancer Duo dans le navigateur" }, @@ -3213,7 +3302,7 @@ "message": "Confirmez le mot de passe du fichier" }, "exportSuccess": { - "message": "Vault data exported" + "message": "Données du coffre exportées" }, "multifactorAuthenticationCancelled": { "message": "Authentification multi-facteurs annulée" @@ -3317,7 +3406,7 @@ "message": "Erreur lors de l'assignation du dossier cible." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Afficher les éléments dans $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -3327,7 +3416,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Revenir à $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -3354,13 +3443,37 @@ "message": "Données" }, "fileSends": { - "message": "File Sends" + "message": "Envois de fichiers" }, "textSends": { - "message": "Text Sends" + "message": "Envois de texte" }, "ssoError": { - "message": "No free ports could be found for the sso login." + "message": "Aucun port libre n’a pu être trouvé pour la connexion SSO." + }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Le déverrouillage par biométrie n’est pas disponible parce qu'il faut au préalable déverrouiller avec le code PIN ou le mot de passe." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Le déverrouillage par biométrie n'est pas disponible actuellement." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Le déverrouillage par biométrie n'est pas disponible en raison de fichiers système mal configurés." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Le déverrouillage par biométrie n'est pas disponible en raison de fichiers système mal configurés." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Le déverrouillage par biométrie n'est pas disponible car elle n'est pas activée pour $EMAIL$ dans l'application de bureau Bitwarden.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Le déverrouillage par biométrie n'est pas disponible actuellement pour une raison inconnue." }, "authorize": { "message": "Autoriser" @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirmer l'utilisation de la clé SSH" }, + "agentForwardingWarningTitle": { + "message": "Avertissement : agent émetteur" + }, + "agentForwardingWarningText": { + "message": "Cette demande provient d’un appareil distant auquel vous êtes connecté" + }, "sshkeyApprovalMessageInfix": { "message": "demande l'accès à" }, + "sshkeyApprovalMessageSuffix": { + "message": "afin de" + }, + "sshActionLogin": { + "message": "s’authentifier sur un serveur" + }, + "sshActionSign": { + "message": "signer un message" + }, + "sshActionGitSign": { + "message": "signer un engagement Git" + }, "unknownApplication": { "message": "Une application" }, - "sshKeyPasswordUnsupported": { - "message": "L'importation de clés SSH protégées par mot de passe n'est pas encore prise en charge" - }, "invalidSshKey": { "message": "La clé SSH est invalide" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Importer une clé depuis le presse-papiers" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "Clé SSH importée avec succès" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Changer le courriel du compte" + }, + "allowScreenshots": { + "message": "Autoriser les captures d'écran" + }, + "allowScreenshotsDesc": { + "message": "Permettre à l'application de bureau Bitwarden d'être enregistrée dans des captures d'écran et affichée dans des sessions de bureau à distance. La désactivation de cette fonction empêchera l'accès à certains écrans externes." + }, + "confirmWindowStillVisibleTitle": { + "message": "Fenêtre de confirmation toujours visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Veuillez confirmer que la fenêtre est toujours visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Mise à jour de l'extension requise" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "L’extension de navigateur que vous utilisez est obsolète. Veuillez la mettre à jour ou désactiver la validation de l'empreinte digitale par l'intégration du navigateur dans les paramètres de l'application de bureau." + }, + "changeAtRiskPassword": { + "message": "Changer le mot de passe à risque" } } diff --git a/apps/desktop/src/locales/gl/messages.json b/apps/desktop/src/locales/gl/messages.json index 323d0cd3f7b..f93db44aa69 100644 --- a/apps/desktop/src/locales/gl/messages.json +++ b/apps/desktop/src/locales/gl/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Number of words" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Master password hint" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -871,6 +906,15 @@ "message": "Verify with Duo Security for your organization using the Duo Mobile app, SMS, phone call, or U2F security key.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Self-hosted environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Custom environment" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "Server URL" }, @@ -956,6 +997,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Overwrite password" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Purchase Premium" }, - "premiumPurchaseAlert": { - "message": "You can purchase premium membership on the bitwarden.com web vault. Do you want to visit the website now?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Require password or PIN on app start" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Recommended for security." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Hide my email address from recipients." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, "rememberEmail": { "message": "Remember email" }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "Toggle character count", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Are you trying to log in?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Login attempt by $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Time" }, - "confirmLogIn": { - "message": "Confirm login" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Deny login" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Login confirmed for $EMAIL$ on $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "confirmLoginAtemptForMail": { - "message": "Confirm login attempt for $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Log in requested" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "region": { "message": "Region" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json index dfaa195895e..7f98ab977ae 100644 --- a/apps/desktop/src/locales/he/messages.json +++ b/apps/desktop/src/locales/he/messages.json @@ -208,34 +208,34 @@ "message": "נוצר מפתח SSH חדש" }, "sshKeyWrongPassword": { - "message": "The password you entered is incorrect." + "message": "הסיסמה שהזנת שגויה." }, "importSshKey": { - "message": "Import" + "message": "ייבוא" }, "confirmSshKeyPassword": { - "message": "Confirm password" + "message": "אשר סיסמה" }, "enterSshKeyPasswordDesc": { - "message": "Enter the password for the SSH key." + "message": "הזן את הסיסמה עבור מפתח ה־SSH." }, "enterSshKeyPassword": { - "message": "Enter password" + "message": "הזן סיסמה" }, "sshAgentUnlockRequired": { - "message": "Please unlock your vault to approve the SSH key request." + "message": "נא לפתוח את הכספת שלך על מנת לאשר את בקשת מפתח ה־SSH." }, "sshAgentUnlockTimeout": { - "message": "SSH key request timed out." + "message": "זמן בקשת מפתח SSH תם." }, "enableSshAgent": { - "message": "הפעלת סוכן SSH" + "message": "אפשר סוכן SSH" }, "enableSshAgentDesc": { - "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + "message": "אפשר את סוכן ה־SSH על מנת לחתום בקשות SSH היישר מכספת ה־Bitwarden שלך." }, "enableSshAgentHelp": { - "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + "message": "סוכן ה־SSH הוא שירות המיועד למפתחים שמאפשר לך לחתום בקשות SSH היישר מכספת ה־Bitwarden שלך." }, "premiumRequired": { "message": "נדרש חשבון פרימיום" @@ -249,6 +249,20 @@ "error": { "message": "שגיאה" }, + "decryptionError": { + "message": "שגיאת פענוח" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden לא יכל לפענח את פריט(י) הכספת המפורט(ים) להלן." + }, + "contactCSToAvoidDataLossPart1": { + "message": "צור קשר עם הצלחת לקוחות", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "כדי למנוע אובדן נתונים נוסף.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "ינואר" }, @@ -302,7 +316,7 @@ "message": "העלמה" }, "mx": { - "message": "Mx" + "message": "מיקס" }, "dr": { "message": "דוקטור" @@ -323,7 +337,7 @@ "message": "צור סיסמה" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "צור ביטוי סיסמה" }, "type": { "message": "סוג" @@ -461,13 +475,13 @@ "message": "העתק סיסמה" }, "regenerateSshKey": { - "message": "Regenerate SSH key" + "message": "צור מחדש מפתח SSH" }, "copySshPrivateKey": { - "message": "Copy SSH private key" + "message": "העתק מפתח SSH פרטי" }, "copyPassphrase": { - "message": "Copy passphrase", + "message": "העתק ביטוי סיסמה", "description": "Copy passphrase to clipboard" }, "copyUri": { @@ -480,7 +494,7 @@ "message": "אורך" }, "passwordMinLength": { - "message": "אורך סיסמה מזערי" + "message": "אורך סיסמה מינימלי" }, "uppercase": { "message": "אותיות גדולות (A-Z)", @@ -498,11 +512,11 @@ "message": "תווים מיוחדים (!@#$%^&*)" }, "include": { - "message": "Include", + "message": "כלול", "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "כלול תווי אות גדולה", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -510,7 +524,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "כלול תווי אות קטנה", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -518,7 +532,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "כלול מספרים", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -526,13 +540,9 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "כלול תווים מיוחדים", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "כמות מילים" }, @@ -561,11 +571,11 @@ "description": "deprecated. Use avoidAmbiguous instead." }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "הימנע מתווים דו־משמעיים", "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { - "message": "Enterprise policy requirements have been applied to your generator options.", + "message": "דרישות מדיניות ארגונית הוחלו על אפשרויות המחולל שלך.", "description": "Indicates that a policy limits the credential generator screen." }, "searchCollection": { @@ -603,7 +613,7 @@ "message": "גודל הקובץ המירבי הוא 500 מגה." }, "encryptionKeyMigrationRequired": { - "message": "נדרשת הסבת מפתח הצפנה. נא להיכנס דרך הכספת לאתרים כדי לעדכן את מפתח ההצפנה שלך." + "message": "נדרשת הגירת מפתח הצפנה. נא להיכנס דרך כספת הרשת כדי לעדכן את מפתח ההצפנה שלך." }, "editedFolder": { "message": "תיקייה שנשמרה" @@ -624,28 +634,37 @@ "message": "צור חשבון" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "חדש ב־Bitwarden?" }, "setAStrongPassword": { - "message": "הגדרת סיסמה חזקה" + "message": "הגדר סיסמה חזקה" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "Finish creating your account by setting a password" + "message": "סיים ליצור את החשבון שלך על ידי הגדרת סיסמה" }, "logIn": { "message": "התחבר" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "היכנס אל Bitwarden" + }, + "enterTheCodeSentToYourEmail": { + "message": "הזן את הקוד שנשלח לדוא\"ל שלך" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "הזן את הקוד מיישום המאמת שלך" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "לחץ על ה־YubiKey שלך כדי לאמת" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "כניסה עם מפתח גישה" }, "loginWithDevice": { - "message": "Log in with device" + "message": "כניסה עם מכשיר" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "השתמש בכניסה יחידה" }, "submit": { "message": "שלח" @@ -666,7 +685,7 @@ "message": "רמז לסיסמה ראשית (אופציונאלי)" }, "masterPassHintText": { - "message": "If you forget your password, the password hint can be sent to your email. $CURRENT$/$MAXIMUM$ character maximum.", + "message": "אם תשכח את הסיסמה שלך, הרמז לסיסמה יכול להישלח לדוא\"ל שלך. $CURRENT$/$MAXIMUM$ תווים לכל היותר.", "placeholders": { "current": { "content": "$1", @@ -679,22 +698,31 @@ } }, "masterPassword": { - "message": "Master password" + "message": "סיסמה ראשית" }, "masterPassImportant": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "הסיסמה הראשית שלך לא ניתנת לשחזור אם אתה שוכח אותה!" }, "confirmMasterPassword": { - "message": "Confirm master password" + "message": "אמת סיסמה ראשית" }, "masterPassHintLabel": { - "message": "Master password hint" + "message": "רמז לסיסמה הראשית" + }, + "passwordStrengthScore": { + "message": "ציון חוזק סיסמה $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } }, "joinOrganization": { - "message": "הצטרפות לארגון" + "message": "הצטרף לארגון" }, "joinOrganizationName": { - "message": "הצטרפות אל $ORGANIZATIONNAME$", + "message": "הצטרף אל $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -703,28 +731,22 @@ } }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Finish joining this organization by setting a master password." + "message": "סיים להצטרף לארגון זה על ידי הגדרת סיסמה ראשית." }, "settings": { "message": "הגדרות" }, "accountEmail": { - "message": "Account email" + "message": "דוא\"ל חשבון" }, "requestHint": { - "message": "Request hint" + "message": "בקש רמז" }, "requestPasswordHint": { - "message": "Request password hint" + "message": "בקש רמז לסיסמה" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { - "message": "Enter your account email address and your password hint will be sent to you" - }, - "passwordHint": { - "message": "רמז לסיסמה" - }, - "enterEmailToGetHint": { - "message": "הכנס את כתובת האימייל שלך לקבלת רמז עבור הסיסמה הראשית." + "message": "הזן את כתובת דוא\"ל החשבון שלך והרמז לסיסמה שלך יישלח אליך" }, "getMasterPasswordHint": { "message": "הצג את הרמז לסיסמה הראשית" @@ -755,7 +777,7 @@ "message": "נכנסת בהצלחה" }, "youMayCloseThisWindow": { - "message": "אפשר לסגור את החלון הזה" + "message": "אתה רשאי לסגור חלון זה" }, "masterPassDoesntMatch": { "message": "אימות סיסמה ראשית אינו תואם." @@ -764,10 +786,10 @@ "message": "החשבון החדש שלך נוצר בהצלחה! כעת ניתן להתחבר למערכת." }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "החשבון החדש שלך נוצר!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "נכנסת!" }, "masterPassSent": { "message": "שלחנו לך אימייל עם רמז לסיסמה הראשית." @@ -800,7 +822,10 @@ "message": "נדרש קוד אימות." }, "webauthnCancelOrTimeout": { - "message": "The authentication was cancelled or took too long. Please try again." + "message": "האימות בוטל או לקח זמן רב מדי. נא לנסות שוב." + }, + "openInNewTab": { + "message": "פתח בכרטיסיה חדשה" }, "invalidVerificationCode": { "message": "קוד אימות שגוי" @@ -832,12 +857,22 @@ "rememberMe": { "message": "זכור אותי" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "אל תשאל אותי שוב במכשיר זה למשך 30 יום" + }, "sendVerificationCodeEmailAgain": { "message": "שלח שוב קוד אימות לאימייל" }, "useAnotherTwoStepMethod": { "message": "השתמש בשיטה אחרת עבור כניסה דו שלבית" }, + "selectAnotherMethod": { + "message": "בחר שיטה אחרת", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "השתמש בקוד השחזור שלך" + }, "insertYubiKey": { "message": "הכנס את ה-YubiKey אל כניסת ה-USB במחשבך, ואז גע בכפתור שלו." }, @@ -854,23 +889,32 @@ "message": "אפליקציית אימות" }, "authenticatorAppDescV2": { - "message": "Enter a code generated by an authenticator app like Bitwarden Authenticator.", + "message": "הזן קוד שנוצר על ידי יישום מאמת כמו מאמת Bitwarden.", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP security key" + "message": "מפתח אבטחה OTP של Yubico" }, "yubiKeyDesc": { "message": "השתמש בYubiKey עבור גישה לחשבון שלך. עובד עם YubiKey בגירסאות 4, 4C, 4Nano, ומכשירי NEO." }, "duoDescV2": { - "message": "Enter a code generated by Duo Security.", + "message": "הזן קוד שנוצר על ידי Duo Security.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { "message": "בצע אימות מול Duo Security עבור הארגון שלך באמצעות אפליקצית Duo לפלאפון, SMS, שיחת טלפון, או מפתח אבטחה U2F.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "אמת את זהותך" + }, + "weDontRecognizeThisDevice": { + "message": "אנחנו לא מזהים את המכשיר הזה. הזן את הקוד שנשלח לדוא\"ל שלך כדי לאמת את זהותך." + }, + "continueLoggingIn": { + "message": "המשך להיכנס" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -881,7 +925,7 @@ "message": "אימייל" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "הזן קוד שנשלח לדוא\"ל שלך." }, "loginUnavailable": { "message": "פרטי כניסה לא זמינים" @@ -895,38 +939,35 @@ "twoStepOptions": { "message": "אפשרויות כניסה דו שלבית" }, + "selectTwoStepLoginMethod": { + "message": "בחר שיטת כניסה דו־שלבית" + }, "selfHostedEnvironment": { "message": "סביבה על שרתים מקומיים" }, - "selfHostedEnvironmentFooter": { - "message": "הזן את כתובת השרת המקומי של Bitwarden." - }, "selfHostedBaseUrlHint": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" + "message": "ציין את בסיס ה־URL של התקנת Bitwarden באירוח מקומי שלך. דוגמה: https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { - "message": "For advanced configuration, you can specify the base URL of each service independently." + "message": "עבור תצורה מתקדמת, באפשרותך לציין את בסיס ה־URL של כל שירות בנפרד." }, "selfHostedEnvFormInvalid": { - "message": "You must add either the base Server URL or at least one custom environment." + "message": "אתה מוכרח להוסיף או את בסיס ה־URL של השרת או לפחות סביבה מותאמת אישית אחת." }, "customEnvironment": { "message": "סביבה מותאמת אישית" }, - "customEnvironmentFooter": { - "message": "למשתמשים מתקדמים. באפשרותך לציין את כתובת השרת עבור כל שירות בנפרד." - }, "baseUrl": { "message": "כתובת שרת" }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "פסק זמן לאימות" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "זמן אימות ההפעלה תם. נא להתחיל מחדש את תהליך הכניסה." }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "URL של שרת אירוח עצמי", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -956,6 +997,9 @@ "no": { "message": "לא" }, + "location": { + "message": "מיקום" + }, "overwritePassword": { "message": "דרוס סיסמה" }, @@ -969,22 +1013,22 @@ "message": "בוצעה יציאה" }, "loggedOutDesc": { - "message": "You have been logged out of your account." + "message": "יצאת מהחשבון שלך." }, "loginExpired": { "message": "תוקף החיבור שלך הסתיים." }, "restartRegistration": { - "message": "Restart registration" + "message": "התחל הרשמה מחדש" }, "expiredLink": { - "message": "Expired link" + "message": "קישור פג תוקף" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "נא להתחיל הרשמה מחדש או לנסות להיכנס." }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "ייתכן שכבר יש לך חשבון" }, "logOutConfirmation": { "message": "האם אתה בטוח שברצונך להתנתק?" @@ -1041,10 +1085,10 @@ "message": "החלף סיסמה ראשית" }, "continueToWebApp": { - "message": "Continue to web app?" + "message": "להמשיך אל יישום רשת?" }, "changeMasterPasswordOnWebConfirmation": { - "message": "You can change your master password on the Bitwarden web app." + "message": "אתה יכול לשנות את הסיסמה הראשית שלך ביישום הרשת של Bitwarden." }, "fingerprintPhrase": { "message": "סיסמת טביעת אצבע", @@ -1073,16 +1117,16 @@ "message": "הכספת שלך נעולה. אמת את זהותך כדי להמשיך." }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "החשבון שלך נעול" }, "or": { - "message": "or" + "message": "או" }, "unlockWithBiometrics": { - "message": "Unlock with biometrics" + "message": "בטל נעילה עם זיהוי ביומטרי" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "בטל נעילה עם סיסמה ראשית" }, "unlock": { "message": "בטל נעילה" @@ -1113,7 +1157,7 @@ "message": "סגירת כספת אוטומטית" }, "vaultTimeout1": { - "message": "Timeout" + "message": "פסק זמן" }, "vaultTimeoutDesc": { "message": "בחר כמה זמן יעבור כדי שהכספת תסגר לאחר חוסר פעילות ותבצע את הפעולה שנבחרה." @@ -1214,34 +1258,34 @@ "message": "הצג תמיד אייקון במגש המערכת." }, "startToTray": { - "message": "התחל עם אייקון במגש המערכת" + "message": "התחל לסמל מגש" }, "startToTrayDesc": { "message": "בהפעלה הראשונה של האפליקציה, הצג אייקון במגש המערכת (מבלי לפתוח את החלון הראשי)." }, "startToMenuBar": { - "message": "הפעל במגש המערכת" + "message": "התחל לשורת התפריטים" }, "startToMenuBarDesc": { - "message": "בהפעלה הראשונה של האפליקציה, הצג סמל במגש המערכת." + "message": "בעת הפעלה ראשונית של היישום, הצג רק סמל בשורת התפריטים." }, "openAtLogin": { - "message": "הפעלה אוטומטית באתחול המערכת" + "message": "התחל אוטומטית בכניסה" }, "openAtLoginDesc": { - "message": "התחל באופן אוטומטי את אפליקציית שולחן העבודה של Bitwarden בהתחברות." + "message": "התחל את יישום שולחן העבודה של Bitwarden אוטומטית בכניסה." }, "alwaysShowDock": { "message": "הצג תמיד ב-Dock" }, "alwaysShowDockDesc": { - "message": "הצג את הסמל של Bitwarden ב-Dock גם כשהתוכנה ממוזערת במגש המערכת." + "message": "הצג את הסמל של Bitwarden ב־Dock גם כשהיישום ממוזער לשורת התפריטים." }, "confirmTrayTitle": { - "message": "אשר השבתת מגש המערכת" + "message": "אשר הסתרת מגש" }, "confirmTrayDesc": { - "message": "השבתת הגדרה זו תשבית גם את כל ההגדרות האחרות הקשורות למגש." + "message": "כיבוי הגדרה זו תכבה גם את כל ההגדרות האחרות הקשורות למגש." }, "language": { "message": "שפה" @@ -1268,7 +1312,7 @@ "description": "Copy to clipboard" }, "checkForUpdates": { - "message": "חפש עדכונים" + "message": "בדוק אם קיימים עדכונים…" }, "version": { "message": "גירסה $VERSION_NUM$", @@ -1320,35 +1364,35 @@ "description": "Copy credit card number" }, "copyEmail": { - "message": "Copy email" + "message": "העתק דוא\"ל" }, "copySecurityCode": { "message": "העתק קוד אבטחה", "description": "Copy credit card security code (CVV)" }, "premiumMembership": { - "message": "חשבון פרימיום" + "message": "חברות פרימיום" }, "premiumManage": { - "message": "נהל חשבון" + "message": "נהל חברות" }, "premiumManageAlert": { "message": "באפשרותך לנהל את החשבון שלך דרך הכספת באתר bitwarden.com. האם ברצונך לפתוח את האתר כעת?" }, "premiumRefresh": { - "message": "רענן פרטי חשבון" + "message": "רענן חברות" }, "premiumNotCurrentMember": { - "message": "חשבונך אינו חשבון פרמיום כרגע." + "message": "אתה לא חבר פרימיום כרגע." }, "premiumSignUpAndGet": { - "message": "צור חשבון פרמיום לשנה, וקבל:" + "message": "הירשם לחברות פרימיום וקבל:" }, "premiumSignUpStorage": { "message": "1 ג'יגה של מקום אחסון מוצפן עבור קבצים מצורפים." }, "premiumSignUpTwoStepOptions": { - "message": "אפשרויות כניסה דו שלבית קנייניות כמו YubiKey ו־Duo." + "message": "אפשרויות כניסה דו־שלבית קנייניות כגון YubiKey ו־Duo." }, "premiumSignUpReports": { "message": "היגיינת סיסמאות, מצב בריאות החשבון, ודיווחים מעודכנים על פרצות חדשות בכדי לשמור על הכספת שלך בטוחה." @@ -1365,11 +1409,8 @@ "premiumPurchase": { "message": "רכוש פרימיום" }, - "premiumPurchaseAlert": { - "message": "באפשרותך לרכוש מנוי פרימיום בכספת באתר bitwarden.com. האם ברצונך לפתוח את האתר כעת?" - }, "premiumPurchaseAlertV2": { - "message": "You can purchase Premium from your account settings on the Bitwarden web app." + "message": "אתה יכול לרכוש פרימיום מהגדרות החשבון שלך ביישום הרשת של Bitwarden." }, "premiumCurrentMember": { "message": "אתה מנוי פרימיום!" @@ -1393,13 +1434,13 @@ "message": "היסטוריית סיסמאות" }, "generatorHistory": { - "message": "Generator history" + "message": "היסטוריית מחולל" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "נקה היסטוריית מחולל" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "אם תמשיך, כל הרשומות יימחקו לצמיתות מהיסטוריית המחולל. האם אתה בטוח שברצונך להמשיך?" }, "clear": { "message": "נקה", @@ -1409,13 +1450,13 @@ "message": "אין סיסמאות להצגה ברשימה." }, "clearHistory": { - "message": "Clear history" + "message": "נקה היסטוריה" }, "nothingToShow": { - "message": "Nothing to show" + "message": "אין מה להראות" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "לא יצרת שום דבר לאחרונה" }, "undo": { "message": "בטל" @@ -1441,16 +1482,16 @@ "message": "הקטן" }, "resetZoom": { - "message": "איפוס זום" + "message": "אפס זום" }, "toggleFullScreen": { - "message": "הפעל/הפסק מצב מסך מלא" + "message": "החלף מצב מסך מלא" }, "reload": { "message": "רענן" }, "toggleDevTools": { - "message": "הצג/הסתר כלי פיתוח" + "message": "החלף מצב כלי פיתוח" }, "minimize": { "message": "מזער", @@ -1460,7 +1501,7 @@ "message": "זום" }, "bringAllToFront": { - "message": "העבר קדימה", + "message": "הבא הכל לחזית", "description": "Bring all windows to front (foreground)" }, "aboutBitwarden": { @@ -1492,13 +1533,13 @@ } }, "copySuccessful": { - "message": "Copy Successful" + "message": "העתקה מוצלחת" }, "errorRefreshingAccessToken": { - "message": "Access Token Refresh Error" + "message": "שגיאת רענון אסימון גישה" }, "errorRefreshingAccessTokenDesc": { - "message": "No refresh token or API keys found. Please try logging out and logging back in." + "message": "לא נמצאו אסימון רענון או מפתחות API. נא לנסות לצאת ולהיכנס חזרה." }, "help": { "message": "עזרה" @@ -1588,7 +1629,7 @@ "description": "ex. Date this password was updated" }, "exportFrom": { - "message": "Export from" + "message": "ייצוא מ־" }, "exportVault": { "message": "יצוא כספת" @@ -1597,31 +1638,31 @@ "message": "תבנית קובץ" }, "fileEncryptedExportWarningDesc": { - "message": "This file export will be password protected and require the file password to decrypt." + "message": "קובץ ייצוא זה יהיה מוגן סיסמה ודורש את סיסמת הקובץ כדי לפענח." }, "filePassword": { - "message": "File password" + "message": "סיסמת קובץ" }, "exportPasswordDescription": { - "message": "This password will be used to export and import this file" + "message": "סיסמה זו תשמש כדי לייצא ולייבא קובץ זה" }, "accountRestrictedOptionDescription": { - "message": "Use your account encryption key, derived from your account's username and Master Password, to encrypt the export and restrict import to only the current Bitwarden account." + "message": "השתמש במפתח הצפנת החשבון שלך, הנגזר משם המשתמש והסיסמה הראשית של חשבונך, כדי להצפין את הייצוא ולהגביל את הייבוא רק לחשבון Bitwarden הנוכחי." }, "passwordProtected": { - "message": "Password protected" + "message": "מוגן סיסמה" }, "passwordProtectedOptionDescription": { - "message": "Set a file password to encrypt the export and import it to any Bitwarden account using the password for decryption." + "message": "הגדר סיסמת קובץ כדי להצפין את הייצוא ולייבא אותו לכל חשבון Bitwarden באמצעות הסיסמה לפענוח." }, "exportTypeHeading": { - "message": "Export type" + "message": "סוג ייצוא" }, "accountRestricted": { - "message": "Account restricted" + "message": "מוגבל חשבון" }, "filePasswordAndConfirmFilePasswordDoNotMatch": { - "message": "“File password” and “Confirm file password“ do not match." + "message": "\"סיסמת קובץ\" ו\"אשר סיסמת קובץ\" אינם תואמים." }, "hCaptchaUrl": { "message": "כתובת אתר hCaptcha", @@ -1720,16 +1761,16 @@ "message": "קוד PIN לא תקין." }, "tooManyInvalidPinEntryAttemptsLoggingOut": { - "message": "יותר מדי ניסיונות שגויים להקלדת קוד אישי. יצאת מהמערכת." + "message": "יותר מדי ניסיונות פסולים להזנת PIN. יוצא." }, "unlockWithWindowsHello": { "message": "שחרור נעילה עם Windows Hello" }, "additionalWindowsHelloSettings": { - "message": "הגדרות נוספות של Windows Hello" + "message": "הגדרות Windows Hello נוספות" }, "unlockWithPolkit": { - "message": "Unlock with system authentication" + "message": "בטל נעילה עם אימות מערכת" }, "windowsHelloConsentMessage": { "message": "אימות עבור Bitwarden." @@ -1747,19 +1788,22 @@ "message": "הצג את Windows Hello בפתיחת האפליקציה" }, "autoPromptPolkit": { - "message": "Ask for system authentication on launch" + "message": "בקש אימות מערכת בפתיחה" }, "autoPromptTouchId": { "message": "הצג בקשה של Touch ID בפתיחת האפליקציה" }, "requirePasswordOnStart": { - "message": "לדרוש סיסמה או קוד אישי עם הפעלת היישום" + "message": "דרוש סיסמה או PIN בפתיחת היישום" + }, + "requirePasswordWithoutPinOnStart": { + "message": "דרוש סיסמה בפתיחת היישום" }, "recommendedForSecurity": { - "message": "מומלץ לשיפור אבטחת מידע." + "message": "מומלץ לאבטחה." }, "lockWithMasterPassOnRestart1": { - "message": "Lock with master password on restart" + "message": "נעל בעזרת הסיסמה הראשית בהפעלה מחדש" }, "deleteAccount": { "message": "מחק חשבון" @@ -1771,10 +1815,10 @@ "message": "מחיקת חשבון היא פעולה בלתי הפיכה." }, "cannotDeleteAccount": { - "message": "Cannot delete account" + "message": "לא ניתן למחוק חשבון" }, "cannotDeleteAccountDesc": { - "message": "This action cannot be completed because your account is owned by an organization. Contact your organization administrator for additional details." + "message": "פעולה זו אינה ניתנת להשלמה בגלל שהחשבון שלך נמצא בבעלות ארגון. צור קשר עם מנהל הארגון שלך עבור פרטים נוספים." }, "accountDeleted": { "message": "החשבון נמחק" @@ -1838,7 +1882,7 @@ "message": "יש לבצע אימות מחדש כדי לגשת לכספת שוב." }, "unlockMethodNeededToChangeTimeoutActionDesc": { - "message": "יש להגדיר שיטת שחרור נעילה כדי לשנות את פעולת תום הזמן של הכספת שלך." + "message": "הגדר שיטת ביטול נעילה כדי לשנות את פעולת פסק הזמן לכספת שלך." }, "lock": { "message": "נעילה", @@ -1849,19 +1893,19 @@ "description": "Noun: a special folder to hold deleted items" }, "searchTrash": { - "message": "חפש בסל המחזור" + "message": "חפש באשפה" }, "permanentlyDeleteItem": { - "message": "מחק לצמיתות פריט שנבחר" + "message": "מחק פריט לצמיתות" }, "permanentlyDeleteItemConfirmation": { "message": "האם אתה בטוח שברצונך למחוק את הפריט הזה לצמיתות?" }, "permanentlyDeletedItem": { - "message": "פריט שנמחק לצמיתות" + "message": "הפריט נמחק לצמיתות" }, "restoredItem": { - "message": "פריט ששוחזר" + "message": "הפריט שוחזר" }, "permanentlyDelete": { "message": "מחק לצמיתות" @@ -1873,21 +1917,21 @@ "message": "אישור פעולת אימות לאחר חוסר פעילות" }, "enterpriseSingleSignOn": { - "message": "כניסה אחודה ארגונית" + "message": "כניסה יחידה ארגונית" }, "setMasterPassword": { - "message": "הגדרת סיסמה ראשית" + "message": "הגדר סיסמה ראשית" }, "orgPermissionsUpdatedMustSetPassword": { - "message": "Your organization permissions were updated, requiring you to set a master password.", + "message": "הרשאות הארגון שלך עודכנו, מה שמאלץ אותך להגדיר סיסמה ראשית.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "orgRequiresYouToSetPassword": { - "message": "Your organization requires you to set a master password.", + "message": "הארגון שלך דורש ממך להגדיר סיסמה ראשית.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "מתוך $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -1900,13 +1944,13 @@ "description": "Default title for the user verification dialog." }, "currentMasterPass": { - "message": "Current master password" + "message": "סיסמה ראשית נוכחית" }, "newMasterPass": { "message": "סיסמה ראשית חדשה" }, "confirmNewMasterPass": { - "message": "אימות סיסמה ראשית חדשה" + "message": "אשר סיסמה ראשית חדשה" }, "masterPasswordPolicyInEffect": { "message": "אחד או יותר מאילוצי המדיניות של הארגון דורשים שהסיסמה הראשית שלך תעמוד בדרישות הבאות:" @@ -1948,34 +1992,34 @@ } }, "masterPasswordPolicyRequirementsNotMet": { - "message": "הסיסמה הראשית החדשה השלך לא עומדת בדרישות המדיניות." + "message": "הסיסמה הראשית החדשה שלך לא עומדת בדרישות המדיניות." }, "receiveMarketingEmailsV2": { - "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." + "message": "קבל עצות, הכרזות, והזדמנויות מחקר מאת Bitwarden בדואר הנכנס שלך." }, "unsubscribe": { - "message": "Unsubscribe" + "message": "בטל הרשמה" }, "atAnyTime": { - "message": "at any time." + "message": "בכל זמן." }, "byContinuingYouAgreeToThe": { - "message": "By continuing, you agree to the" + "message": "על ידי המשך, אתה מסכים ל" }, "and": { - "message": "and" + "message": "ו" }, "acceptPolicies": { "message": "סימון תיבה זו מהווה את הסכמתך לתנאים הבאים:" }, "acceptPoliciesRequired": { - "message": "תנאי השירות ומדיניות הפרטיות לא אושרו." + "message": "תנאי השימוש ומדיניות הפרטיות לא הוכרו." }, "enableBrowserIntegration": { - "message": "אפשר אינטגרציה עם הדפדפן" + "message": "אפשר שילוב דפדפן" }, "enableBrowserIntegrationDesc1": { - "message": "משמש כדי לאפשר שחרור עם אמצעים ביומטריים בדפדפנים שאינם Safari." + "message": "משמש כדי לאפשר ביטול נעילה ביוטמרי בדפדפנים שאינם Safari." }, "enableDuckDuckGoBrowserIntegration": { "message": "אפשר שילוב דפדפן DuckDuckGo" @@ -1984,46 +2028,46 @@ "message": "השתמש בכספת Bitwarden שלך בעת גלישה עם DuckDuckGo." }, "browserIntegrationUnsupportedTitle": { - "message": "שילוב הדפדפן אינו נתמך" + "message": "שילוב דפדפן אינו נתמך" }, "browserIntegrationErrorTitle": { - "message": "Error enabling browser integration" + "message": "שגיאה באפשור שילוב דפדפן" }, "browserIntegrationErrorDesc": { - "message": "An error has occurred while enabling browser integration." + "message": "אירעה שגיאה בעת אפשור שילוב דפדפן." }, "browserIntegrationMasOnlyDesc": { - "message": "למצער, אינטגרצייה עם הדפדפן בשלב זה נתמכת רק על ידי גרסת חנות האפליקציות של מקינטוש." + "message": "למרבה הצער שילוב דפדפן נתמך רק בגרסת Mac App Store לעת עתה." }, "browserIntegrationWindowsStoreDesc": { - "message": "למרבה הצער שילוב הדפדפן אינו נתמך כרגע בגרסת ה-Windows Store." + "message": "למרבה הצער שילוב דפדפן אינו נתמך כרגע בגרסת ה־Microsoft Store." }, "browserIntegrationLinuxDesc": { - "message": "לצערינו בגרסאות ה-Linux תכונת השילוב הדפדפן אינה נתמכת כעת." + "message": "למרבה הצער שילוב דפדפן אינו נתמך כרגע בגרסת הלינוקס." }, "enableBrowserIntegrationFingerprint": { - "message": "דרוש אימות לצורך שילוב הדפדפן" + "message": "דרוש אימות עבור שילוב דפדפן" }, "enableBrowserIntegrationFingerprintDesc": { - "message": "הפעל שכבת אבטחה נוספת באמצעות בקשת אימות טביעות אצבע בעת יצירת קישור בין שולחן העבודה לדפדפן. כשהוא מופעל, זה דורש התערבות ואימות משתמש בכל פעם שנוצר חיבור." + "message": "הוסף שכבת אבטחה נוספת על ידי דרישת אישור ביטוי טביעת אצבע בעת יצירת קישור בין שולחן העבודה והדפדפן שלך. זה דורש פעולה ואימות של המשתמש בכל פעם שנוצר חיבור." }, "enableHardwareAcceleration": { - "message": "להשתמש בהאצת חומרה" + "message": "השתמש בהאצת חומרה" }, "enableHardwareAccelerationDesc": { - "message": "ההגדרה הזאת פעילה כברירת מחדל. יש לכבות רק אם יש כל מיני תקלות גרפיות. צריך להפעיל מחדש." + "message": "כברירת מחדל הגדרה זו מופעלת. כבה רק אם אתה חווה בעיות גרפיות. נדרשת הפעלה מחדש." }, "approve": { - "message": "לְאַשֵׁר" + "message": "אשר" }, "verifyBrowserTitle": { - "message": "אמת את חיבור הדפדפן" + "message": "אמת חיבור דפדפן" }, "verifyBrowserDesc": { - "message": "בבקשה, וודא כי חתימת האצבע הדיגיטלית המוצגת זהה לחתימה שמופיע בתוסף הדפדפן." + "message": "נא לוודא שטביעת האצבע המוצגת זהה לטביעת האצבע שמוצגת בהרחבת הדפדפן." }, "verifyNativeMessagingConnectionTitle": { - "message": "$APPID$ רוצה להתחבר ל-Bitwarden", + "message": "$APPID$ רוצה להתחבר אל Bitwarden", "placeholders": { "appid": { "content": "$1", @@ -2038,31 +2082,31 @@ "message": "אם לא יזמת בקשה זו, אל תאשר אותה." }, "biometricsNotEnabledTitle": { - "message": "לא הופעלו אמצעי זיהוי ביומטריים" + "message": "זיהוי ביומטרי אינו מוגדר" }, "biometricsNotEnabledDesc": { - "message": "בכדי להשתמש באמצעי אימות ביומטריים בדפדפן, אפשר תכונה זו באפליקציה בשולחן העבודה." + "message": "זיהוי ביומטרי בדפדפן דורש שזיהוי ביומטרי בשולחן העבודה יהיה מוגדר בהגדרות קודם." }, "biometricsManualSetupTitle": { - "message": "Automatic setup not available" + "message": "הגדרה אוטומטית אינה זמינה" }, "biometricsManualSetupDesc": { - "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + "message": "בגלל שיטת ההתקנה, לא היה ניתן לאפשר תמיכת זיהוי ביומטרי באופן אוטומטי. האם תרצה לפתוח את התיעוד על איך לעשות זאת ידנית?" }, "personalOwnershipSubmitError": { - "message": "בשל מדיניות ארגונית, אתה מוגבל לשמירת פריטים בכספת האישית שלך. שנה את ההגדרות בעלות החשבון לחשבון ארגוני ובחר מתוך האוספים הזמינים." + "message": "בשל מדיניות ארגונית, אתה מוגבל מלשמור פריטים לכספת האישית שלך. שנה את אפשרות הבעלות לארגון ובחר מאוספים זמינים." }, "hintEqualsPassword": { - "message": "הרמז לסיסמה לא יכול להיות זהה לסיסמה." + "message": "רמז הסיסמה שלך לא יכול להיות אותו הדבר כמו הסיסמה שלך." }, "personalOwnershipPolicyInEffect": { - "message": "מדיניות ארגון מסויימת משפיעה על אפשרויות הבעלות." + "message": "מדיניות ארגון משפיעה על אפשרויות הבעלות שלך." }, "personalOwnershipPolicyInEffectImports": { - "message": "An organization policy has blocked importing items into your individual vault." + "message": "מדיניות ארגון חסמה ייבוא פריטים אל תוך הכספת האישית שלך." }, "allSends": { - "message": "כל השליחות", + "message": "כל הסֵנְדים", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTypeFile": { @@ -2072,11 +2116,11 @@ "message": "טקסט" }, "searchSends": { - "message": "חיפוש שליחויות", + "message": "חפש סֵנְדים", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editSend": { - "message": "ערוך Send", + "message": "ערוך סֵנְד", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "myVault": { @@ -2089,72 +2133,72 @@ "message": "תאריך מחיקה" }, "deletionDateDesc": { - "message": "המשלוח יימחק לצמיתות בתאריך ובשעה שצוינו.", + "message": "הסֵנְד יימחק לצמיתות בתאריך ובשעה שצוינו.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { "message": "תאריך תפוגה" }, "expirationDateDesc": { - "message": "במדה והגדרת זמן, הגישה לשליחה זו תושבת בתאריך ובשעה שהוגדרו.", + "message": "אם מוגדר, גישה לסֵנְד זה תפוג בתאריך ובשעה שצוינו.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "maxAccessCount": { - "message": "מונה גישה מרבי", + "message": "מספר גישות מרבי", "description": "This text will be displayed after a Send has been accessed the maximum amount of times." }, "maxAccessCountDesc": { - "message": "אם תגדיר, משתמשים כבר לא יוכלו לגשת לשליחה זו לאחר שמספר הגישות המרבי יושג.", + "message": "אם מוגדר, משתמשים לא יוכלו עוד לגשת לסֵנְד זה ברגע שמספר הגישות המרבי הושג.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "currentAccessCount": { - "message": "מונה גישה נוכחית" + "message": "מספר גישות נוכחי" }, "disableSend": { - "message": "השבת את השליחה הזאת ואל תאפשר גישה אליה.", + "message": "השבת את סֵנְד זה כך שאף אחד לא יוכל לגשת אליו.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendPasswordDesc": { - "message": "לחלופין, דרוש סיסמה לכל המשתמשים כדי לגשת לשליחה זאת.", + "message": "דרוש סיסמה באופן אופציונלי כדי שמשתמשים יוכלו לגשת אל סֵנְד זה.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendNotesDesc": { - "message": "הערות אישיות אודות השליחה הזאת.", + "message": "הערות פרטיות לגבי סֵנְד זה.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLink": { - "message": "שלח קישור", + "message": "קישור סֵנְד", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLinkLabel": { - "message": "שלח קישור", + "message": "קישור סֵנְד", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "textHiddenByDefault": { - "message": "בעת גישה לשליחה, הסתר את הטקסט בברירת מחדל", + "message": "בעת גישה לסֵנְד, הסתר את הטקסט כברירת מחדל", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSend": { - "message": "ה-Send נוצר", + "message": "סֵנְד נוסף", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { - "message": "שליחה שנערכה", + "message": "סֵנְד נשמר", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletedSend": { - "message": "השליחה נמחקה", + "message": "סֵנְד נמחק", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "newPassword": { "message": "סיסמה חדשה" }, "whatTypeOfSend": { - "message": "איזה סוג של שליחה הוא זה?", + "message": "איזה סוג של סֵנְד זה?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createSend": { - "message": "צור Send", + "message": "סֵנְד חדש", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTextDesc": { @@ -2164,7 +2208,7 @@ "message": "הקובץ שברצונך לשלוח." }, "days": { - "message": "$DAYS$ יום/ימים", + "message": "$DAYS$ ימים", "placeholders": { "days": { "content": "$1", @@ -2179,22 +2223,22 @@ "message": "מותאם אישית" }, "deleteSendConfirmation": { - "message": "האם אתה בטוח שברצונך למחוק את המשלוח הזה?", + "message": "האם אתה בטוח שברצונך למחוק סֵנְד זה?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "copySendLinkToClipboard": { - "message": "העתק את קישור ה-Send ללוח", + "message": "העתק קישור סֵנְד ללוח", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "copySendLinkOnSave": { - "message": "בשמירה העתק את הקישור ללוח כדי לשתף את ה-Send." + "message": "העתק את הקישור לשיתוף סֵנְד זה אל לוח ההעתקה שלי עם השמירה." }, "sendDisabled": { - "message": "ה-Send הושבת", + "message": "סֵנְד הוסר", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { - "message": "בשל מדיניות החברה, אתה יכול למחוק רק משלוח קיים.", + "message": "בשל מדיניות ארגונית, אתה יכול למחוק רק סֵנְד קיים.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "copyLink": { @@ -2204,13 +2248,13 @@ "message": "מושבת" }, "removePassword": { - "message": "הסרת סיסמה" + "message": "הסר סיסמה" }, "removedPassword": { "message": "הסיסמה הוסרה" }, "removePasswordConfirmation": { - "message": "להסיר את הסיסמה?" + "message": "האם אתה בטוח שברצונך להסיר את הסיסמה?" }, "maxAccessCountReached": { "message": "מספר הגישות המרבי הושג" @@ -2219,31 +2263,37 @@ "message": "פג תוקף" }, "pendingDeletion": { - "message": "ממתין להסרה" + "message": "ממתין למחיקה" }, "webAuthnAuthenticate": { - "message": "אמת WebAutn" + "message": "אמת WebAuthn" + }, + "readSecurityKey": { + "message": "קרא מפתח אבטחה" + }, + "awaitingSecurityKeyInteraction": { + "message": "ממתין לאינטראקציה עם מפתח אבטחה..." }, "hideEmail": { - "message": "הסתר את כתובת הדואר האלקטרוני שלי מהנמענים." + "message": "הסתר את כתובת הדוא\"ל שלי מנמענים." }, "sendOptionsPolicyInEffect": { - "message": "מדיניות ארגון אחת או יותר משפיעה על אפשרויות השליחה שלך." + "message": "מדיניות ארגון אחת או יותר משפיעה על אפשרויות הסֵנְד שלך." }, "emailVerificationRequired": { - "message": "נדרש כתובת אימייל לאימות" + "message": "נדרש אימות דוא\"ל" }, "emailVerifiedV2": { - "message": "Email verified" + "message": "דוא\"ל אומת" }, "emailVerificationRequiredDesc": { - "message": "נדרש אישור אימות בדוא\"ל כדי לאפשר שימוש בתכונה זו." + "message": "אתה מוכרח לאמת את הדוא\"ל שלך כדי להשתמש בתכונה זו." }, "passwordPrompt": { - "message": "בקשת חוזרת של סיסמת ראשית" + "message": "בקשה חוזרת של סיסמה ראשית" }, "passwordConfirmation": { - "message": "אישור סיסמא ראשי" + "message": "אישור סיסמה ראשית" }, "passwordConfirmationDesc": { "message": "פעולה זו מוגנת. כדי להמשיך, הזן מחדש את הסיסמה הראשית שלך כדי לאמת את זהותך." @@ -2255,49 +2305,49 @@ "message": "עדכון סיסמה ראשית" }, "updateMasterPasswordWarning": { - "message": "הסיסמה הראשית שלך שונתה לאחרונה על ידי מנהל הארגון שלך. כדי לגשת לכספת, עליך לעדכן אותה כעת. בהמשך תנותק מההפעלה הנוכחית שלך ותידרש להיכנס שוב. הפעלות אחרות הפתוחות במכשירים שונים ימשיכו להיות פעילים עד משך שעה אחת." + "message": "הסיסמה הראשית שלך שונתה לאחרונה על ידי מנהל הארגון שלך. כדי לגשת לכספת, עליך לעדכן אותה כעת. המשך התהליך יוציא אותך מההפעלה הנוכחית שלך, מה שידרוש ממך להיכנס חזרה. הפעלות פעילות במכשירים אחרים עלולות להישאר פעילות למשך עד שעה אחת." }, "updateWeakMasterPasswordWarning": { - "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." + "message": "הסיסמה הראשית שלך אינה עומדת באחת או יותר מפוליסות הארגון שלך. כדי לגשת לכספת, אתה מוכרח לעדכן את הסיסמה הראשית שלך עכשיו. בהמשך תנותק מההפעלה הנוכחית שלך, מה שידרוש ממך להיכנס חזרה. הפעלות פעילות במכשירים אחרים עלולות להישאר פעילות למשך עד שעה אחת." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "הארגון שלך השבית הצפנת מכשיר מהימן. נא להגדיר סיסמה ראשית כדי לגשת לכספת שלך." }, "tryAgain": { - "message": "לנסות שוב" + "message": "נסה שוב" }, "verificationRequiredForActionSetPinToContinue": { - "message": "צריך אימות לביצוע הפעולה הזאת. נא להגדיר קוד אישי כדי להמשיך." + "message": "נדרש אימות עבור פעולה זו. הגדר PIN כדי להמשיך." }, "setPin": { - "message": "הגדרת קוד אישי" + "message": "הגדר PIN" }, "verifyWithBiometrics": { - "message": "אימות עם זיהוי ביומטרי" + "message": "אמת עם זיהוי ביומטרי" }, "awaitingConfirmation": { - "message": "בהמתנה לאישור" + "message": "ממתין לאישור" }, "couldNotCompleteBiometrics": { - "message": "לא ניתן להשלים את האימות הביומטרי." + "message": "לא היה ניתן להשלים את הזיהוי הביומטרי." }, "needADifferentMethod": { - "message": "עדיף שיטה אחרת?" + "message": "זקוק לשיטה אחרת?" }, "useMasterPassword": { - "message": "להשתמש בסיסמה ראשית" + "message": "השתמש בסיסמה ראשית" }, "usePin": { - "message": "להשתמש בקוד אישי" + "message": "השתמש ב־PIN" }, "useBiometrics": { - "message": "להשתמש בזיהוי ביומטרי" + "message": "השתמש בזיהוי ביומטרי" }, "enterVerificationCodeSentToEmail": { - "message": "נא למלא את קוד האימות שנשלח לכתובת הדוא״ל שלך." + "message": "הזן את קוד האימות שנשלח לדוא\"ל שלך." }, "resendCode": { - "message": "שליחת הקוד מחדש" + "message": "שלח קוד מחדש" }, "hours": { "message": "שעות" @@ -2306,7 +2356,7 @@ "message": "דקות" }, "vaultTimeoutPolicyInEffect1": { - "message": "$HOURS$ hour(s) and $MINUTES$ minute(s) maximum.", + "message": "$HOURS$ שעות ו־$MINUTES$ דקות לכל היותר.", "placeholders": { "hours": { "content": "$1", @@ -2319,7 +2369,7 @@ } }, "vaultTimeoutPolicyWithActionInEffect": { - "message": "Your organization policies are affecting your vault timeout. Maximum allowed vault timeout is $HOURS$ hour(s) and $MINUTES$ minute(s). Your vault timeout action is set to $ACTION$.", + "message": "פוליסות הארגון שלך משפיעות על פסק הזמן לכספת שלך. פסק זמן מרבי המותר הוא $HOURS$ שעות ו־$MINUTES$ דקות. פעולת פסק הזמן לכספת שלך מוגדרת ל$ACTION$.", "placeholders": { "hours": { "content": "$1", @@ -2336,7 +2386,7 @@ } }, "vaultTimeoutActionPolicyInEffect": { - "message": "Your organization policies have set your vault timeout action to $ACTION$.", + "message": "פוליסות הארגון שלך הגדירו את פעולת פסק הזמן לכספת שלך ל$ACTION$.", "placeholders": { "action": { "content": "$1", @@ -2345,34 +2395,34 @@ } }, "vaultTimeoutTooLarge": { - "message": "הזמן הקצוב לכספת שלך חורג מהמגבלות שנקבעו על ידי הארגון שלך." + "message": "פסק הזמן לכספת שלך חורג מהמגבלות שנקבעו על ידי הארגון שלך." }, "inviteAccepted": { - "message": "Invitation accepted" + "message": "ההזמנה התקבלה" }, "resetPasswordPolicyAutoEnroll": { "message": "רישום אוטומטי" }, "resetPasswordAutoEnrollInviteWarning": { - "message": "לארגון זה יש מדיניות ארגונית שרושמת אותך אוטומטית לאיפוס סיסמה. הרישום יאפשר למנהלי הארגון לשנות את סיסמת האב שלך." + "message": "לארגון זה יש מדיניות ארגונית שתרשום אותך באופן אוטומטי לאיפוס סיסמה. הרישום יאפשר למנהלי הארגון לשנות את הסיסמה הראשית שלך." }, "vaultExportDisabled": { - "message": "ייצוא מהכספת מושבת" + "message": "ייצוא כספת הוסר" }, "personalVaultExportPolicyInEffect": { - "message": "מדיניות אחת או יותר של הארגון שלך מונעות ממך לייצא את הכספת האישית שלך." + "message": "מדיניות ארגון אחת או יותר מונעת ממך מלייצא את הכספת האישית שלך." }, "addAccount": { - "message": "הוספת חשבון" + "message": "הוסף חשבון" }, "removeMasterPassword": { - "message": "הסרת סיסמה ראשית" + "message": "הסר סיסמה ראשית" }, "removedMasterPassword": { - "message": "הסיסמה הראשית הוסרה." + "message": "הסיסמה הראשית הוסרה" }, "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ משתמשים ב־SSO עם שרת מפתחות באירוח עצמי. סיסמה ראשית לא נחוצה יותר לטובת כניסה לחברי הארגון.", + "message": "$ORGANIZATION$ משתמש/ת ב־SSO עם שרת מפתחות באירוח עצמי. סיסמה ראשית כבר לא נדרשת כדי להיכנס עבור חברים של ארגון זה.", "placeholders": { "organization": { "content": "$1", @@ -2381,22 +2431,22 @@ } }, "leaveOrganization": { - "message": "לעזוב את הארגון" + "message": "עזוב ארגון" }, "leaveOrganizationConfirmation": { - "message": "לעזוב את הארגון?" + "message": "האם אתה בטוח שברצונך לעזוב את הארגון הזה?" }, "leftOrganization": { "message": "עזבת את הארגון." }, "ssoKeyConnectorError": { - "message": "שגיאת בחיבור המפתח: ודא שמחבר המפתח זמין ופועל כצפוי." + "message": "שגיאת Key Connector: וודא שה־Key Connector זמין ופועל כראוי." }, "lockAllVaults": { - "message": "נעילת כל הכספות" + "message": "נעל את כל הכספות" }, "accountLimitReached": { - "message": "אפשר להיכנס לעד 5 חשבונות בו־זמנית." + "message": "לא ניתן להיכנס עם יותר מ־5 חשבונות בו־זמנית." }, "accountPreferences": { "message": "העדפות" @@ -2405,7 +2455,7 @@ "message": "הגדרות היישום (כל החשבונות)" }, "accountSwitcherLimitReached": { - "message": "הגעת למגבלת החשבונות. יש לצאת מחשבון כדי להוסיף אחד נוסף." + "message": "הגעת למגבלת החשבונות. צא מחשבון כדי להוסיף אחד נוסף." }, "settingsTitle": { "message": "הגדרות היישום עבור $EMAIL$", @@ -2420,7 +2470,7 @@ "message": "החלף חשבון" }, "alreadyHaveAccount": { - "message": "Already have an account?" + "message": "כבר יש לך חשבון?" }, "options": { "message": "אפשרויות" @@ -2429,10 +2479,10 @@ "message": "זמן ההפעלה שלך תם. נא לחזור ולנסות להיכנס שוב." }, "exportingPersonalVaultTitle": { - "message": "הכספת האישית מיוצאת" + "message": "מייצא כספת אישית" }, "exportingIndividualVaultDescription": { - "message": "Only the individual vault items associated with $EMAIL$ will be exported. Organization vault items will not be included. Only vault item information will be exported and will not include associated attachments.", + "message": "רק פריטי הכספת האישית המשויכת עם $EMAIL$ ייוצאו. פריטי כספת ארגון לא יכללו. רק פרטי פריט כספת ייוצאו ולא יכללו צרופות משויכות.", "placeholders": { "email": { "content": "$1", @@ -2441,10 +2491,10 @@ } }, "exportingOrganizationVaultTitle": { - "message": "Exporting organization vault" + "message": "מייצא כספת ארגון" }, "exportingOrganizationVaultDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "message": "רק כספת הארגון המשויכת עם $ORGANIZATION$ תיוצא. פריטים בכספת אישית או ארגונים אחרים לא יכללו.", "placeholders": { "organization": { "content": "$1", @@ -2453,35 +2503,35 @@ } }, "locked": { - "message": "נָעוּל" + "message": "נעול" }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "הכספת שלך נעולה" }, "unlocked": { - "message": "לא נעול" + "message": "פתוח" }, "generator": { "message": "מחולל", "description": "Short for 'credential generator'." }, "whatWouldYouLikeToGenerate": { - "message": "מה ברצונך לחולל?" + "message": "מה ברצונך ליצור?" }, "passwordType": { "message": "סוג סיסמה" }, "regenerateUsername": { - "message": "חולל מחדש שם משתמש" + "message": "צור מחדש שם משתמש" }, "generateUsername": { - "message": "חולל שם משתמש" + "message": "צור שם משתמש" }, "generateEmail": { - "message": "Generate email" + "message": "צור דוא\"ל" }, "spinboxBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$.", + "message": "הערך חייב להיות בין $MIN$ ל־$MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2495,7 +2545,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "message": " השתמש ב־$RECOMMENDED$ תווים או יותר כדי ליצור סיסמה חזקה.", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2505,7 +2555,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "message": " השתמש ב־$RECOMMENDED$ מילים או יותר כדי ליצור ביטוי סיסמה חזק.", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2518,17 +2568,20 @@ "message": "סוג שם משתמש" }, "plusAddressedEmail": { - "message": "דואר אלקטרוני ממוען", + "message": "דוא\"ל ממוען בפלוס", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" }, "plusAddressedEmailDesc": { - "message": "השתמש בתכונת הכתובת החלופית של ספק הדוא\"ל שלך." + "message": "השתמש ביכולות מיעון משנה של ספק הדוא\"ל שלך." }, "catchallEmail": { - "message": "Catch-all הדוא\"ל" + "message": "דוא\"ל תופס־כל" }, "catchallEmailDesc": { - "message": "השתמש בתכונת ה-catch-all המוגדרת בדומיין שלך." + "message": "השתמש בתיבת דואר תפוס־כל המוגדרת בדומיין שלך." + }, + "useThisEmail": { + "message": "השתמש בדוא\"ל זה" }, "random": { "message": "אקראי" @@ -2546,27 +2599,27 @@ "message": "כל הכספות" }, "searchOrganization": { - "message": "חיפוש ארגון" + "message": "חפש בארגון" }, "searchMyVault": { "message": "חפש בכספת שלי" }, "forwardedEmail": { - "message": "כינוי דוא\"ל להעברה" + "message": "כינוי דוא\"ל מועבר" }, "forwardedEmailDesc": { - "message": "צור כינוי דוא\"ל באמצעות שירות שליחת דוא\"ל חיצוני." + "message": "צור כינוי דוא\"ל עם שירות העברה חיצוני." }, "forwarderDomainName": { - "message": "Email domain", + "message": "דומיין דוא\"ל", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Choose a domain that is supported by the selected service", + "message": "בחר דומיין שנתמך על ידי השירות הנבחר", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { - "message": "$SERVICENAME$ error: $ERRORMESSAGE$", + "message": "$SERVICENAME$ שגיאה: $ERRORMESSAGE$", "description": "Reports an error returned by a forwarding service to the user.", "placeholders": { "servicename": { @@ -2580,11 +2633,11 @@ } }, "forwarderGeneratedBy": { - "message": "Generated by Bitwarden.", + "message": "נוצר על ידי Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "אתר: $WEBSITE$. נוצר על ידי Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -2594,7 +2647,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "אסימון API של $SERVICENAME$ לא חוקי", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -2604,7 +2657,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "אסימון API של $SERVICENAME$ לא חוקי: $ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -2617,8 +2670,32 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ סירב לבקשה שלך. נא ליצור קשר עם נותן השירות שלך לקבלת סיוע.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ סירב לבקשה שלך: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { - "message": "Unable to obtain $SERVICENAME$ masked email account ID.", + "message": "לא ניתן להשיג מזהה חשבון דוא\"ל מסווה של $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -2628,7 +2705,7 @@ } }, "forwarderNoDomain": { - "message": "Invalid $SERVICENAME$ domain.", + "message": "דומיין $SERVICENAME$ לא חוקי.", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -2638,7 +2715,7 @@ } }, "forwarderNoUrl": { - "message": "Invalid $SERVICENAME$ url.", + "message": "כתובת url של $SERVICENAME$ לא חוקית.", "description": "Displayed when the url of the forwarding service wasn't supplied.", "placeholders": { "servicename": { @@ -2648,7 +2725,7 @@ } }, "forwarderUnknownError": { - "message": "Unknown $SERVICENAME$ error occurred.", + "message": "התרחשה שגיאת $SERVICENAME$ לא ידועה.", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -2658,7 +2735,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Unknown forwarder: '$SERVICENAME$'.", + "message": "משלח לא ידוע: '$SERVICENAME$'.", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -2672,43 +2749,37 @@ "description": "Part of a URL." }, "apiAccessToken": { - "message": "API מפתח גישה" + "message": "אסימון גישת API" }, "apiKey": { "message": "מפתח API" }, "premiumSubcriptionRequired": { - "message": "נדרש מנוי Premium" + "message": "נדרש מנוי פרימיום" }, "organizationIsDisabled": { - "message": "תכונת חשבון ארגוני מושבת." + "message": "ארגון מושעה" }, "disabledOrganizationFilterError": { - "message": "לא ניתן לגשת לפריטים של ארגונים מושבתים. פנה לבעל הארגון שלך לקבלת סיוע." + "message": "לא ניתן לגשת לפריטים בארגון מושעה. פנה אל בעל הארגון שלך עבור סיוע." }, "neverLockWarning": { - "message": "האם אתה בטוח שברצונך להשתמש באפשרות \"לעולם לא\"? על ידי הגדרת אפשרויות הנעילה ל\"לעולם לא\", מפתח ההצפנה של הכספת שלך יישמר במכשיר שלך. אם אתה משתמש באפשרות זו, ודא שאתה שומר על המכשיר שלך מוגן כראוי." + "message": "האם אתה בטוח שברצונך להשתמש באפשרות \"לעולם לא\"? הגדרת אפשרויות הנעילה שלך ל\"לעולם לא\" מאחסנת את מפתח ההצפנה של הכספת שלך במכשיר שלך. אם אתה משתמש באפשרות זו עליך לוודא שאתה שומר על המכשיר שלך מוגן כראוי." }, "vault": { "message": "כספת" }, "loginWithMasterPassword": { - "message": "היכנס עם סיסמת מאסטר" - }, - "loggingInAs": { - "message": "כניסה בתור" + "message": "כניסה עם סיסמה ראשית" }, "rememberEmail": { - "message": "זכור אימייל" - }, - "notYou": { - "message": "לא אתה?" + "message": "זכור דוא\"ל" }, "newAroundHere": { "message": "חדש כאן?" }, "loggingInTo": { - "message": "נכנס ל-$DOMAIN$", + "message": "נכנס אל $DOMAIN$", "placeholders": { "domain": { "content": "$1", @@ -2717,53 +2788,62 @@ } }, "logInWithAnotherDevice": { - "message": "התחבר עם מכשיר אחר" + "message": "כניסה עם מכשיר אחר" }, "loginInitiated": { - "message": "Login initiated" + "message": "הכניסה החלה" + }, + "logInRequestSent": { + "message": "בקשה נשלחה" }, "notificationSentDevice": { - "message": "התראה נשלחה למכשירך." + "message": "התראה נשלחה למכשיר שלך." }, "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "message": "התראה נשלחה למכשיר שלך" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "בטל נעילת Bitwarden במכשיר שלך או ב" + }, + "notificationSentDeviceAnchor": { + "message": "יישום רשת" + }, + "notificationSentDevicePart2": { + "message": "וודא שביטוי טביעת האצבע תואם את זה שלמטה לפני שתאשר." }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "צריך אפשרות אחרת?" }, "fingerprintMatchInfo": { - "message": "וודא שהכספת שלך לא נעולה ושם המזהה שלך מתאים למכשיר השני." + "message": "נא לוודא שהכספת שלך פתוחה ושביטוי טביעת האצבע תואם את המכשיר האחר." }, "fingerprintPhraseHeader": { - "message": "שם מזהה" + "message": "ביטוי טביעת אצבע" }, "youWillBeNotifiedOnceTheRequestIsApproved": { - "message": "You will be notified once the request is approved" + "message": "תקבל התראה כאשר הבקשה תאושר" }, "needAnotherOption": { - "message": "התחברות עם מכשיר צריכה להיות מוגדרת בהגדרות האפליקציה. זקוק/ה לאפשרות נוספת?" + "message": "כניסה עם מכשיר צריכה להיות מוגדרת בהגדרות של היישום Bitwarden. צריך אפשרות אחרת?" }, "viewAllLogInOptions": { - "message": "View all log in options" + "message": "הצג את כל אפשרויות הכניסה" }, "viewAllLoginOptions": { - "message": "צפייה בכל אפשרות ההתחברות" + "message": "הצג את כל אפשרויות הכניסה" }, "resendNotification": { - "message": "שליחת התראה מחדש" + "message": "שלח מחדש התראה" }, "toggleCharacterCount": { - "message": "החלפת מצב ספירת תווים", + "message": "החלף מצב מונה תווים", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "האם את/ה מנסה להתחבר? " + "areYouTryingToAccessYourAccount": { + "message": "האם אתה מנסה לגשת לחשבון שלך?" }, - "logInAttemptBy": { - "message": "ניסיון התחברות של $EMAIL$", + "accessAttemptBy": { + "message": "ניסיון גישה על ידי $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,14 +2860,14 @@ "time": { "message": "זמן" }, - "confirmLogIn": { - "message": "אישור ההתחברות" + "confirmAccess": { + "message": "אשר גישה" }, - "denyLogIn": { - "message": "ביטול התחברות" + "denyAccess": { + "message": "דחה גישה" }, "logInConfirmedForEmailOnDevice": { - "message": "התחברות אושרה עבור $EMAIL$ במכשיר $DEVICE$", + "message": "הכניסה אושרה עבור $EMAIL$ ב־$DEVICE$", "placeholders": { "email": { "content": "$1", @@ -2800,13 +2880,13 @@ } }, "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + "message": "דחית ניסיון כניסה ממכשיר אחר. אם זה באמת היית אתה, נסה להיכנס עם המכשיר שוב." }, "justNow": { - "message": "Just now" + "message": "זה עתה" }, "requestedXMinutesAgo": { - "message": "Requested $MINUTES$ minutes ago", + "message": "התבקשה לפני $MINUTES$ דקות", "placeholders": { "minutes": { "content": "$1", @@ -2815,13 +2895,13 @@ } }, "loginRequestHasAlreadyExpired": { - "message": "Login request has already expired." + "message": "כבר פג תוקפה של בקשת הכניסה." }, "thisRequestIsNoLongerValid": { - "message": "This request is no longer valid." + "message": "בקשה זו אינה תקפה עוד." }, - "confirmLoginAtemptForMail": { - "message": "Confirm login attempt for $EMAIL$", + "confirmAccessAttempt": { + "message": "אשר ניסיון גישה עבור $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2830,64 +2910,73 @@ } }, "logInRequested": { - "message": "Log in requested" + "message": "התבקשה כניסה" + }, + "accountAccessRequested": { + "message": "התבקשה גישה לחשבון" }, "creatingAccountOn": { - "message": "Creating account on" + "message": "יוצר חשבון ב־" }, "checkYourEmail": { - "message": "Check your email" + "message": "בדוק את הדוא\"ל שלך" }, "followTheLinkInTheEmailSentTo": { - "message": "Follow the link in the email sent to" + "message": "עקוב אחר הקישור בדוא\"ל הנשלח אל" }, "andContinueCreatingYourAccount": { - "message": "and continue creating your account." + "message": "והמשך ליצור את החשבון שלך." }, "noEmail": { - "message": "No email?" + "message": "אין דוא\"ל?" }, "goBack": { - "message": "Go back" + "message": "חזור" }, "toEditYourEmailAddress": { - "message": "to edit your email address." + "message": "כדי לערוך את כתובת הדוא\"ל שלך." }, "exposedMasterPassword": { - "message": "Exposed Master Password" + "message": "סיסמה ראשית חשופה" }, "exposedMasterPasswordDesc": { - "message": "Password found in a data breach. Use a unique password to protect your account. Are you sure you want to use an exposed password?" + "message": "הסיסמה נמצאה בפרצת נתונים. השתמש בסיסמה ייחודית כדי להגן על חשבונך. האם אתה בטוח שברצונך להשתמש בסיסמה חשופה?" }, "weakAndExposedMasterPassword": { - "message": "Weak and Exposed Master Password" + "message": "סיסמה ראשית חלשה וחשופה" }, "weakAndBreachedMasterPasswordDesc": { - "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" + "message": "סיסמה חלשה זוהתה ונמצאה בפרצת נתונים. השתמש בסיסמה חזקה וייחודית כדי להגן על חשבונך. האם אתה בטוח שאתה רוצה להשתמש בסיסמה זו?" + }, + "useThisPassword": { + "message": "השתמש בסיסמה זו" + }, + "useThisUsername": { + "message": "השתמש בשם משתמש זה" }, "checkForBreaches": { - "message": "Check known data breaches for this password" + "message": "בדוק פרצות נתונים ידועות עבור סיסמה זו" }, "loggedInExclamation": { - "message": "Logged in!" + "message": "נכנסת!" }, "important": { "message": "חשוב:" }, "accessing": { - "message": "Accessing" + "message": "ניגש אל" }, "accessTokenUnableToBeDecrypted": { - "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + "message": "נותקת בגלל שלא היה ניתן לפענח את אסימון הגישה שלך. נא להיכנס שוב כדי לפתור בעיה זו." }, "refreshTokenSecureStorageRetrievalFailure": { - "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + "message": "נותקת בגלל שלא היה ניתן לאחזר את אסימון הרענון שלך. נא להיכנס שוב כדי לפתור בעיה זו." }, "masterPasswordHint": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "הסיסמה הראשית שלך לא ניתנת לשחזור אם אתה שוכח אותה!" }, "characterMinimum": { - "message": "$LENGTH$ character minimum", + "message": "$LENGTH$ תווים לכל הפחות", "placeholders": { "length": { "content": "$1", @@ -2896,92 +2985,86 @@ } }, "windowsBiometricUpdateWarning": { - "message": "Bitwarden recommends updating your biometric settings to require your master password (or PIN) on the first unlock. Would you like to update your settings now?" + "message": "Bitwarden ממליץ לעדכן את הגדרות הזיהוי הביומטרי שלך כך שתדרש סיסמה ראשית (או PIN) בכניסה הראשונית. האם ברצונך לעדכן את ההגדרות שלך עכשיו?" }, "windowsBiometricUpdateWarningTitle": { - "message": "Recommended Settings Update" + "message": "עדכון הגדרות מומלץ" }, "rememberThisDeviceToMakeFutureLoginsSeamless": { - "message": "Remember this device to make future logins seamless" + "message": "זכור מכשיר זה כדי להפוך כניסות עתידיות לחלקות" }, "deviceApprovalRequired": { - "message": "Device approval required. Select an approval option below:" + "message": "נדרש אישור מכשיר. בחר אפשרות אישור למטה:" }, "deviceApprovalRequiredV2": { - "message": "Device approval required" + "message": "נדרש אישור מכשיר" }, "selectAnApprovalOptionBelow": { - "message": "Select an approval option below" + "message": "בחר אפשרות אישור למטה" }, "rememberThisDevice": { - "message": "Remember this device" + "message": "זכור מכשיר זה" }, "uncheckIfPublicDevice": { - "message": "Uncheck if using a public device" + "message": "בטל את הסימון אם אתה משתמש במכשיר ציבורי" }, "approveFromYourOtherDevice": { - "message": "Approve from your other device" + "message": "אשר מהמכשיר האחר שלך" }, "requestAdminApproval": { - "message": "Request admin approval" - }, - "approveWithMasterPassword": { - "message": "Approve with master password" + "message": "בקש אישור מנהל" }, "region": { - "message": "Region" + "message": "אזור" }, "ssoIdentifierRequired": { - "message": "Organization SSO identifier is required." + "message": "נדרש מזהה SSO של הארגון." }, "eu": { - "message": "EU", + "message": "האיחוד האירופי", "description": "European Union" }, "selfHostedServer": { - "message": "self-hosted" + "message": "אירוח עצמי" }, "accessDenied": { - "message": "Access denied. You do not have permission to view this page." + "message": "גישה נדחתה. אין לך הרשאות כדי לצפות בעמוד זה." }, "accountSuccessfullyCreated": { - "message": "Account successfully created!" + "message": "החשבון נוצר בהצלחה!" }, "adminApprovalRequested": { - "message": "Admin approval requested" + "message": "התבקש אישור מנהל" }, "adminApprovalRequestSentToAdmins": { - "message": "Your request has been sent to your admin." - }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." + "message": "הבקשה שלך נשלחה למנהל שלך." }, "troubleLoggingIn": { - "message": "Trouble logging in?" + "message": "בעיות בכניסה?" }, "loginApproved": { - "message": "Login approved" + "message": "כניסה אושרה" }, "userEmailMissing": { - "message": "User email missing" + "message": "חסר דוא\"ל משתמש" }, "activeUserEmailNotFoundLoggingYouOut": { - "message": "Active user email not found. Logging you out." + "message": "דוא\"ל משתמש פעיל לא נמצא. מוציא אותך." }, "deviceTrusted": { - "message": "Device trusted" + "message": "מכשיר מהימן" }, "inputRequired": { - "message": "Input is required." + "message": "נדרש קלט." }, "required": { - "message": "required" + "message": "נדרש" }, "search": { - "message": "Search" + "message": "חיפוש" }, "inputMinLength": { - "message": "Input must be at least $COUNT$ characters long.", + "message": "אורך הקלט חייב להיות לפחות $COUNT$ תווים.", "placeholders": { "count": { "content": "$1", @@ -2990,7 +3073,7 @@ } }, "inputMaxLength": { - "message": "Input must not exceed $COUNT$ characters in length.", + "message": "אורך הקלט לא יעלה על $COUNT$ תווים.", "placeholders": { "count": { "content": "$1", @@ -2999,7 +3082,7 @@ } }, "inputForbiddenCharacters": { - "message": "The following characters are not allowed: $CHARACTERS$", + "message": "התווים הבאים אינם מותרים: $CHARACTERS$", "placeholders": { "characters": { "content": "$1", @@ -3008,7 +3091,7 @@ } }, "inputMinValue": { - "message": "Input value must be at least $MIN$.", + "message": "ערך הקלט חייב להיות לפחות $MIN$.", "placeholders": { "min": { "content": "$1", @@ -3017,7 +3100,7 @@ } }, "inputMaxValue": { - "message": "Input value must not exceed $MAX$.", + "message": "ערך הקלט לא יעלה על $MAX$.", "placeholders": { "max": { "content": "$1", @@ -3026,17 +3109,17 @@ } }, "multipleInputEmails": { - "message": "1 or more emails are invalid" + "message": "כתובת דוא\"ל 1 או יותר אינה חוקית" }, "inputTrimValidator": { - "message": "Input must not contain only whitespace.", + "message": "אסור שקלט יכיל רק רווח לבן.", "description": "Notification to inform the user that a form's input can't contain only whitespace." }, "inputEmail": { - "message": "Input is not an email address." + "message": "קלט הוא לא כתובת דוא\"ל." }, "fieldsNeedAttention": { - "message": "$COUNT$ field(s) above need your attention.", + "message": "$COUNT$ שדות למעלה צריכים את תשומת לבך.", "placeholders": { "count": { "content": "$1", @@ -3045,22 +3128,22 @@ } }, "selectPlaceholder": { - "message": "-- Select --" + "message": "-- בחר --" }, "multiSelectPlaceholder": { - "message": "-- Type to filter --" + "message": "-- הקלד כדי לסנן --" }, "multiSelectLoading": { - "message": "Retrieving options..." + "message": "מאחזר אפשרויות..." }, "multiSelectNotFound": { - "message": "No items found" + "message": "לא נמצאו פריטים" }, "multiSelectClearAll": { - "message": "Clear all" + "message": "נקה הכל" }, "plusNMore": { - "message": "+ $QUANTITY$ more", + "message": "+ עוד $QUANTITY$", "placeholders": { "quantity": { "content": "$1", @@ -3069,47 +3152,47 @@ } }, "submenu": { - "message": "Submenu" + "message": "תפריט משנה" }, "toggleSideNavigation": { - "message": "Toggle side navigation" + "message": "החלף מצב ניווט צדדי" }, "skipToContent": { - "message": "Skip to content" + "message": "דלג לתוכן" }, "typePasskey": { - "message": "Passkey" + "message": "מפתח גישה" }, "passkeyNotCopied": { - "message": "Passkey will not be copied" + "message": "מפתח גישה לא יועתק" }, "passkeyNotCopiedAlert": { - "message": "The passkey will not be copied to the cloned item. Do you want to continue cloning this item?" + "message": "מפתח הגישה לא יועתק לפריט המשוכפל. האם ברצונך להמשיך לשכפל פריט זה?" }, "aliasDomain": { - "message": "Alias domain" + "message": "דומיין כינוי" }, "importData": { - "message": "Import data", + "message": "ייבא נתונים", "description": "Used for the desktop menu item and the header of the import dialog" }, "importError": { - "message": "Import error" + "message": "שגיאת ייבוא" }, "importErrorDesc": { - "message": "There was a problem with the data you tried to import. Please resolve the errors listed below in your source file and try again." + "message": "הייתה בעיה עם הנתונים שאתה מנסה לייבא. נא לפתור את השגיאות הרשומות למטה בקובץ המקור שלך ולנסות שוב." }, "resolveTheErrorsBelowAndTryAgain": { - "message": "Resolve the errors below and try again." + "message": "פתור את השגיאות למטה ונסה שוב." }, "description": { - "message": "Description" + "message": "תיאור" }, "importSuccess": { - "message": "Data successfully imported" + "message": "הנתונים יובאו בהצלחה" }, "importSuccessNumberOfItems": { - "message": "A total of $AMOUNT$ items were imported.", + "message": "סך הכל יובאו $AMOUNT$ פריטים.", "placeholders": { "amount": { "content": "$1", @@ -3118,10 +3201,10 @@ } }, "total": { - "message": "Total" + "message": "סך הכל" }, "importWarning": { - "message": "You are importing data to $ORGANIZATION$. Your data may be shared with members of this organization. Do you want to proceed?", + "message": "אתה מייבא נתונים אל $ORGANIZATION$. ייתכן שהנתונים שלך ישותפו עם חברים של הארגון הזה. האם אתה רוצה להמשיך?", "placeholders": { "organization": { "content": "$1", @@ -3130,43 +3213,49 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + "message": "שגיאה בהתחברות עם השירות Duo. השתמש בשיטת כניסה דו־שלבית אחרת או פנה אל Duo לסיוע." }, "launchDuoAndFollowStepsToFinishLoggingIn": { - "message": "Launch Duo and follow the steps to finish logging in." + "message": "פתח את Duo ועקוב אחר השלבים כדי לסיים להיכנס." }, "duoRequiredByOrgForAccount": { - "message": "Duo two-step login is required for your account." + "message": "נדרשת כניסה דו־שלבית של Duo עבור החשבון שלך." + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "נדרשת כניסה דו־שלבית של Duo עבור החשבון שלך. עקוב אחר השלבים למטה כדי לסיים להיכנס." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "עקוב אחר השלבים למטה כדי לסיים להיכנס." }, "launchDuo": { - "message": "Launch Duo in Browser" + "message": "פתח את Duo בדפדפן" }, "importFormatError": { - "message": "Data is not formatted correctly. Please check your import file and try again." + "message": "הנתונים אינם מעוצבים כראוי. נא לבדוק את קובץ הייבוא שלך ולנסות שוב." }, "importNothingError": { - "message": "Nothing was imported." + "message": "לא יובא דבר." }, "importEncKeyError": { - "message": "Error decrypting the exported file. Your encryption key does not match the encryption key used export the data." + "message": "שגיאה בפענוח הקובץ המיוצא. מפתח ההצפנה שלך אינו תואם את מפתח ההצפנה המשמש לייצוא הנתונים." }, "invalidFilePassword": { - "message": "Invalid file password, please use the password you entered when you created the export file." + "message": "סיסמת קובץ שגויה, נא להשתמש בסיסמה שהזנת כשיצרת את קובץ הייצוא." }, "destination": { - "message": "Destination" + "message": "יעד" }, "learnAboutImportOptions": { - "message": "Learn about your import options" + "message": "למד על אפשרויות הייבוא שלך" }, "selectImportFolder": { - "message": "Select a folder" + "message": "בחר תיקייה" }, "selectImportCollection": { - "message": "Select a collection" + "message": "בחר אוסף" }, "importTargetHint": { - "message": "Select this option if you want the imported file contents moved to a $DESTINATION$", + "message": "בחר באפשרות זו אם ברצונך להעביר את תוכן הקובץ המיובא אל $DESTINATION$", "description": "Located as a hint under the import target. Will be appended by either folder or collection, depending if the user is importing into an individual or an organizational vault.", "placeholders": { "destination": { @@ -3176,25 +3265,25 @@ } }, "importUnassignedItemsError": { - "message": "File contains unassigned items." + "message": "קובץ מכיל פריטים לא מוקצים." }, "selectFormat": { - "message": "Select the format of the import file" + "message": "בחר את הפורמט של קובץ הייבוא" }, "selectImportFile": { - "message": "Select the import file" + "message": "בחר את קובץ הייבוא" }, "chooseFile": { - "message": "Choose File" + "message": "בחר קובץ" }, "noFileChosen": { - "message": "No file chosen" + "message": "לא נבחר קובץ" }, "orCopyPasteFileContents": { - "message": "or copy/paste the import file contents" + "message": "או העתק/הדבק את תוכן קובץ הייבוא" }, "instructionsFor": { - "message": "$NAME$ Instructions", + "message": "הוראות עבור $NAME$", "description": "The title for the import tool instructions.", "placeholders": { "name": { @@ -3204,25 +3293,25 @@ } }, "confirmVaultImport": { - "message": "Confirm vault import" + "message": "אשר ייבוא כספת" }, "confirmVaultImportDesc": { - "message": "This file is password-protected. Please enter the file password to import data." + "message": "קובץ זה מוגן סיסמה. נא להזין את סיסמת הקובץ כדי לייבא נתונים." }, "confirmFilePassword": { - "message": "Confirm file password" + "message": "אשר סיסמת קובץ" }, "exportSuccess": { - "message": "Vault data exported" + "message": "נתוני הכספת יוצאו" }, "multifactorAuthenticationCancelled": { - "message": "Multifactor authentication cancelled" + "message": "אימות רב־גורמי בוטל" }, "noLastPassDataFound": { - "message": "No LastPass data found" + "message": "לא נמצאו נתוני LastPass" }, "incorrectUsernameOrPassword": { - "message": "Incorrect username or password" + "message": "שם משתמש או סיסמה שגויים" }, "incorrectPassword": { "message": "סיסמה שגויה" @@ -3231,93 +3320,93 @@ "message": "קוד שגוי" }, "incorrectPin": { - "message": "קוד אישי שגוי" + "message": "PIN שגוי" }, "multifactorAuthenticationFailed": { - "message": "Multifactor authentication failed" + "message": "אימות רב־גורמי נכשל" }, "includeSharedFolders": { - "message": "Include shared folders" + "message": "כלול תיקיות משותפות" }, "lastPassEmail": { - "message": "LastPass Email" + "message": "דוא\"ל LastPass" }, "importingYourAccount": { - "message": "Importing your account..." + "message": "מייבא את החשבון שלך..." }, "lastPassMFARequired": { - "message": "LastPass multifactor authentication required" + "message": "נדרש אימות רב־גורמי של LastPass" }, "lastPassMFADesc": { - "message": "Enter your one-time passcode from your authentication app" + "message": "הזן את קוד הגישה החד פעמי שלך מיישום האימות שלך" }, "lastPassOOBDesc": { - "message": "Approve the login request in your authentication app or enter a one-time passcode." + "message": "אשר את בקשת הכניסה ביישום האימות שלך או הזן קוד גישה חד פעמי." }, "passcode": { - "message": "Passcode" + "message": "קוד גישה" }, "lastPassMasterPassword": { - "message": "LastPass master password" + "message": "סיסמה ראשית של LastPass" }, "lastPassAuthRequired": { - "message": "LastPass authentication required" + "message": "נדרש אימות של LastPass" }, "awaitingSSO": { - "message": "Awaiting SSO authentication" + "message": "ממתין לאימות SSO" }, "awaitingSSODesc": { - "message": "Please continue to log in using your company credentials." + "message": "נא להמשיך להיכנס באמצעות אישורי החברה שלך." }, "seeDetailedInstructions": { - "message": "See detailed instructions on our help site at", + "message": "ראה הוראות מפורטות באתר העזרה שלנו ב־", "description": "This is followed a by a hyperlink to the help website." }, "importDirectlyFromLastPass": { - "message": "Import directly from LastPass" + "message": "ייבא ישירות מ־LastPass" }, "importFromCSV": { - "message": "Import from CSV" + "message": "ייבא מ־CSV" }, "lastPassTryAgainCheckEmail": { - "message": "Try again or look for an email from LastPass to verify it's you." + "message": "נסה שוב או חפש דוא\"ל מ־LastPass כדי לאמת שזה אתה." }, "collection": { - "message": "Collection" + "message": "אוסף" }, "lastPassYubikeyDesc": { - "message": "Insert the YubiKey associated with your LastPass account into your computer's USB port, then touch its button." + "message": "הכנס את ה־YubiKey המשויך עם חשבון ה־LastPass שלך אל תוך יציאת USB של המחשב שלך, לאחר מכן גע בכפתור שלו." }, "commonImportFormats": { - "message": "תסדירים נפוצים", + "message": "פורמטים נפוצים", "description": "Label indicating the most common import formats" }, "success": { - "message": "Success" + "message": "הצלחה" }, "troubleshooting": { - "message": "Troubleshooting" + "message": "פתרון בעיות" }, "disableHardwareAccelerationRestart": { - "message": "Disable hardware acceleration and restart" + "message": "בטל האצת חומרה והתחל מחדש" }, "enableHardwareAccelerationRestart": { - "message": "Enable hardware acceleration and restart" + "message": "אפשר האצת חומרה והתחל מחדש" }, "removePasskey": { - "message": "Remove passkey" + "message": "הסר מפתח גישה" }, "passkeyRemoved": { - "message": "Passkey removed" + "message": "מפתח גישה הוסר" }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "שגיאה בהקצאת אוסף יעד." }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "שגיאה בהקצאת תיקיית יעד." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "הצג פריטים ב־$NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -3327,7 +3416,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "חזרה אל $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -3337,11 +3426,11 @@ } }, "back": { - "message": "Back", + "message": "הקודם", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "הסר $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -3351,67 +3440,106 @@ } }, "data": { - "message": "Data" + "message": "נתונים" }, "fileSends": { - "message": "File Sends" + "message": "סֵנְדים של קובץ" }, "textSends": { - "message": "Text Sends" + "message": "סֵנְדים של טקסט" }, "ssoError": { - "message": "No free ports could be found for the sso login." + "message": "לא נמצאו יציאות פנויות עבור כניסת ה־sso." + }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "ביטול נעילה ביומטרי אינו זמין בגלל שביטול נעילה עם PIN או סיסמה נדרש תחילה." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "ביטול נעילה ביומטרי אינו זמין כעת." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "ביטול נעילה ביומטרי אינו זמין בשל קבצי מערכת המוגדרים באופן שגוי." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "ביטול נעילה ביומטרי אינו זמין בשל קבצי מערכת המוגדרים באופן שגוי." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "ביטול נעילה ביומטרי אינו זמין בגלל שהוא לא מופעל עבור $EMAIL$ ביישום שולחן העבודה של Bitwarden.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "ביטול נעילה ביומטרי אינו זמין כעת מסיבה לא ידועה." }, "authorize": { - "message": "Authorize" + "message": "אשר" }, "deny": { - "message": "Deny" + "message": "דחה" }, "sshkeyApprovalTitle": { - "message": "Confirm SSH key usage" + "message": "אשר שימוש במפתח SSH" + }, + "agentForwardingWarningTitle": { + "message": "אזהרה: העברת סוכן" + }, + "agentForwardingWarningText": { + "message": "הבקשה הזאת באה ממכשיר מרוחק שאתה מחובר אליו" }, "sshkeyApprovalMessageInfix": { - "message": "is requesting access to" + "message": "מבקש גישה אל" + }, + "sshkeyApprovalMessageSuffix": { + "message": "על מנת" + }, + "sshActionLogin": { + "message": "בצע אימות לשרת" + }, + "sshActionSign": { + "message": "חתימה על הודעה" + }, + "sshActionGitSign": { + "message": "חתימה על התחייבות Git" }, "unknownApplication": { - "message": "An application" - }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" + "message": "יישום" }, "invalidSshKey": { - "message": "The SSH key is invalid" + "message": "מפתח ה־SSH אינו תקין" }, "sshKeyTypeUnsupported": { - "message": "The SSH key type is not supported" + "message": "סוג מפתח ה־SSH אינו נתמך" }, "importSshKeyFromClipboard": { - "message": "Import key from clipboard" + "message": "ייבא מפתח מלוח ההעתקה" }, - "sshKeyPasted": { - "message": "SSH key imported successfully" + "sshKeyImported": { + "message": "מפתח SSH יובא בהצלחה" }, "fileSavedToDevice": { - "message": "File saved to device. Manage from your device downloads." + "message": "הקובץ נשמר למכשיר. נהל מהורדות המכשיר שלך." }, "importantNotice": { - "message": "Important notice" + "message": "הודעה חשובה" }, "setupTwoStepLogin": { - "message": "Set up two-step login" + "message": "הגדר כניסה דו־שלבית" }, "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + "message": "Bitwarden ישלח קוד לדוא\"ל החשבון שלך כדי לאמת כניסות ממכשירים חדשים החל מפברואר 2025." }, "newDeviceVerificationNoticeContentPage2": { - "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + "message": "אתה יכול להגדיר כניסה דו־שלבית כדרך חלופית להגן על החשבון שלך או לשנות את הדוא\"ל שלך לאחד שאתה יכול לגשת אליו." }, "remindMeLater": { - "message": "Remind me later" + "message": "הזכר לי מאוחר יותר" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", + "message": "יש לך גישה מהימנה לדוא\"ל שלך, $EMAIL$?", "placeholders": { "email": { "content": "$1", @@ -3420,15 +3548,36 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "לא, אין לי" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" + "message": "כן, אני יכול לגשת לדוא\"ל שלי באופן מהימן" }, "turnOnTwoStepLogin": { - "message": "Turn on two-step login" + "message": "הפעל כניסה דו־שלבית" }, "changeAcctEmail": { - "message": "Change account email" + "message": "שנה את דוא\"ל החשבון" + }, + "allowScreenshots": { + "message": "אפשר לכידת מסך" + }, + "allowScreenshotsDesc": { + "message": "אפשר ליישום שולחן העבודה של Bitwarden להילכד בצילומי מסך ולהיות מוצג בהפעלות שולחן עבודה מרוחק. השבתה תמנע גישה בצגים חיצוניים מסוימים." + }, + "confirmWindowStillVisibleTitle": { + "message": "אשר שהחלון עדיין גלוי" + }, + "confirmWindowStillVisibleContent": { + "message": "נא לאשר שהחלון עדיין גלוי." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "נדרש עדכון הרחבה" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "הרחבת הדפדפן בה אתה משתמש היא מיושנת. נא לעדכן אותה או להשבית אימות טביעת אצבע של שילוב דפדפן בהגדרות יישום שולחן העבודה." + }, + "changeAtRiskPassword": { + "message": "שנה סיסמה בסיכון" } } diff --git a/apps/desktop/src/locales/hi/messages.json b/apps/desktop/src/locales/hi/messages.json index 13bb5edfa93..90fed49de76 100644 --- a/apps/desktop/src/locales/hi/messages.json +++ b/apps/desktop/src/locales/hi/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Number of words" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Master password hint" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -871,6 +906,15 @@ "message": "Verify with Duo Security for your organization using the Duo Mobile app, SMS, phone call, or U2F security key.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Self-hosted environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Custom environment" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "Server URL" }, @@ -956,6 +997,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Overwrite password" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Purchase Premium" }, - "premiumPurchaseAlert": { - "message": "You can purchase premium membership on the bitwarden.com web vault. Do you want to visit the website now?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Require password or PIN on app start" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Recommended for security." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Hide my email address from recipients." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, "rememberEmail": { "message": "Remember email" }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "Toggle character count", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Are you trying to log in?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Login attempt by $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Time" }, - "confirmLogIn": { - "message": "Confirm login" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Deny login" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Login confirmed for $EMAIL$ on $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "confirmLoginAtemptForMail": { - "message": "Confirm login attempt for $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Log in requested" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "region": { "message": "Region" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json index a87261a2027..cb66437bedd 100644 --- a/apps/desktop/src/locales/hr/messages.json +++ b/apps/desktop/src/locales/hr/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Pogreška" }, + "decryptionError": { + "message": "Pogreška pri dešifriranju" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nije mogao dešifrirati sljedeće stavke trezora." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktiraj službu za korisnike", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "kako bi izbjegli gubitak podataka.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "siječanj" }, @@ -529,10 +543,6 @@ "message": "Uključi posebne znakove", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "! @ # $ % ^ & *", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Broj riječi" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Prijavi se u Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Unesi kôd poslan e-poštom" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Unesi kôd iz svoje aplikacije za autentifikaciju" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Za autentifikaciju dodirni svoj YubiKey" + }, "logInWithPasskey": { "message": "Prijava pristupnim ključem" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Podsjetnik glavne lozinke" }, + "passwordStrengthScore": { + "message": "Ocjena jačine lozinke: $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Pridruži se organizaciji" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Unesi svoju adresu e-pošte računa i poslat ćemo ti tvoj podsjetnik" }, - "passwordHint": { - "message": "Podsjetnik za lozinku" - }, - "enterEmailToGetHint": { - "message": "Unesi adresu e-pošte svog računa za primitak podsjetnika glavne lozinke." - }, "getMasterPasswordHint": { "message": "Slanje podsjetnika glavne lozinke" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "Autentifikacija je otkazana ili je trajala predugo. Molimo pokušaj ponovno." }, + "openInNewTab": { + "message": "Otvori u novoj kartici" + }, "invalidVerificationCode": { "message": "Nevažeći kôd za provjeru" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Zapamti me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Ne pitaj na ovom uređaju idućih 30 dana" + }, "sendVerificationCodeEmailAgain": { "message": "Ponovno slanje kontrolnog koda e-poštom" }, "useAnotherTwoStepMethod": { "message": "Koristiti drugi način prijave dvostrukom autentifikacijom" }, + "selectAnotherMethod": { + "message": "Odaberi drugi način", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Koristi kôd za oporavak" + }, "insertYubiKey": { "message": "Umetni svoj YubiKey u USB priključak računala, a zatim dodirni njegovu tipku." }, @@ -871,6 +906,15 @@ "message": "Potvrdi s Duo Security za svoju organizaciju pomoću aplikacije Duo Mobile, SMS-om, telefonskim pozivom ili U2F sigurnosnim ključem.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Potvrdi svoj identitet" + }, + "weDontRecognizeThisDevice": { + "message": "Ne prepoznajemo ovaj uređaj. Za potvrdu identiteta unesi kôd poslan e-poštom." + }, + "continueLoggingIn": { + "message": "Nastavi prijavu" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Mogućnosti prijave dvostrukom autentifikacijom" }, + "selectTwoStepLoginMethod": { + "message": "Odaberi način prijave dvostrukom autentifikacijom" + }, "selfHostedEnvironment": { "message": "Vlastito hosting okruženje" }, - "selfHostedEnvironmentFooter": { - "message": "Navedi osnovni URL svoje lokalno smještene Bitwarden instalacije." - }, "selfHostedBaseUrlHint": { "message": "Navedi osnovni URL svoje lokalne Bitwarden instalacije, npr.: https://bitwarden.tvrtka.hr" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Prilagođeno okruženje" }, - "customEnvironmentFooter": { - "message": "Za napredne korisnike. Samostalno možeš odrediti osnovni URL svake usluge." - }, "baseUrl": { "message": "URL poslužitelja" }, @@ -956,6 +997,9 @@ "no": { "message": "Ne" }, + "location": { + "message": "Lokacija" + }, "overwritePassword": { "message": "Prebriši lozinku" }, @@ -1104,7 +1148,7 @@ "message": "Neispravna glavna lozinka" }, "twoStepLoginConfirmation": { - "message": "Prijava dvostrukom autentifikacijom čini tvoj račun još sigurnijim tako što će zahtijevati potvrdu prijave putem drugog uređaja kao što su sigurnosni ključ, autentifikatorske aplikacije, SMS-om, pozivom ili e-poštom. Prijavu dvostrukom autentifikacijom možeš omogućiti na web trezoru. Želiš li sada posjetiti bitwarden.com?" + "message": "Prijava dvostrukom autentifikacijom čini tvoj račun još sigurnijim tako što će zahtijevati potvrdu prijave drugim uređajem kao što je sigurnosni ključ, autentifikatorska aplikacija, SMS, poziv ili e-pošta. Prijavu dvostrukom autentifikacijom možeš omogućiti na web trezoru. Želiš li sada posjetiti bitwarden.com?" }, "twoStepLogin": { "message": "Prijava dvostrukom autentifikacijom" @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Kupi premium članstvo" }, - "premiumPurchaseAlert": { - "message": "Možeš kupiti premium članstvo na web trezoru. Želiš li sada posjetiti bitwarden.com?" - }, "premiumPurchaseAlertV2": { "message": "Premium možeš kupiti u postavkama računa na Bitwarden web aplikaciji." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Zahtijevaj lozinku ili PIN pri pokretanju" }, + "requirePasswordWithoutPinOnStart": { + "message": "Zahtijevaj lozinku pri pokretanju" + }, "recommendedForSecurity": { "message": "Preporučeno za sigurnost." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Ovjeri WebAuthn" }, + "readSecurityKey": { + "message": "Pročitaj sigurnosni ključ" + }, + "awaitingSecurityKeyInteraction": { + "message": "Čekanje na interakciju sa sigurnosnim ključem..." + }, "hideEmail": { "message": "Sakrij moju adresu e-pošte od primatelja." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Koristi konfigurirani catch-all sandučić svoje domene." }, + "useThisEmail": { + "message": "Koristi ovu e-poštu" + }, "random": { "message": "Nasumično" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ je odbio tvoj zahtjev. Obrati se svom pružatelju usluga za pomoć.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ je odbio tvoj zahtjev: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Nije moguće dobiti $SERVICENAME$ maskirani ID računa e-pošte.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Prijava glavnom lozinkom" }, - "loggingInAs": { - "message": "Prijava kao" - }, "rememberEmail": { "message": "Zapamti adresu e-pošte" }, - "notYou": { - "message": "Nisi ti?" - }, "newAroundHere": { "message": "Novi korisnik?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Prijava pokrenuta" }, + "logInRequestSent": { + "message": "Zahtjev poslan" + }, "notificationSentDevice": { "message": "Obavijest je poslana na tvoj uređaj." }, "aNotificationWasSentToYourDevice": { "message": "Obavijest je poslana na tvoj uređaj" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Provjeri je li trezor otključan i slaže li se jedinstvena fraza s drugim uređajem" + "notificationSentDevicePart1": { + "message": "Otključaj Bitwarden na svojem uređaju ili na " + }, + "notificationSentDeviceAnchor": { + "message": "web trezoru" + }, + "notificationSentDevicePart2": { + "message": "Provjeri slaže li se jedinstvena fraza s ovdje prikazanom prije odobravanja." }, "needAnotherOptionV1": { "message": "Trebaš drugu opciju?" @@ -2759,11 +2839,11 @@ "message": "Prikaži/Sakrij broj znakova", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Pokušavaš li se prijaviti?" + "areYouTryingToAccessYourAccount": { + "message": "Pokušavaš li pristupiti svom računu?" }, - "logInAttemptBy": { - "message": "$EMAIL$ se pokušava prijaviti", + "accessAttemptBy": { + "message": "$EMAIL$ pokušava pristupiti", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Vrijeme" }, - "confirmLogIn": { - "message": "Potvrdi prijavu" + "confirmAccess": { + "message": "Potvrdi pristup" }, - "denyLogIn": { - "message": "Odbij prijavu" + "denyAccess": { + "message": "Odbij pristup" }, "logInConfirmedForEmailOnDevice": { "message": "Prijava za $EMAIL$ potvrđena na uređaju $DEVICE$", @@ -2820,7 +2900,7 @@ "thisRequestIsNoLongerValid": { "message": "Ovaj zahtjev više nije valjan." }, - "confirmLoginAtemptForMail": { + "confirmAccessAttempt": { "message": "Potvrdi pokušaj prijave za $EMAIL$", "placeholders": { "email": { @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Zatražena je prijava" }, + "accountAccessRequested": { + "message": "Zatražen je pristup računu" + }, "creatingAccountOn": { "message": "Stvaranje računa na" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Slaba lozinka je nađena među ukradenima tijekom krađa podataka. Za zaštitu svog računa koristi jaku i jedinstvenu lozinku. Želiš li svejedno korisiti slabu, ukradenu lozinku?" }, + "useThisPassword": { + "message": "Koristi ovu lozinku" + }, + "useThisUsername": { + "message": "Koristi ovo korisničko ime" + }, "checkForBreaches": { "message": "Provjeri je li lozinka ukradena prilikom krađe podataka" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Zatraži odobrenje administratora" }, - "approveWithMasterPassword": { - "message": "Odobri glavnom lozinkom" - }, "region": { "message": "Regija" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Tvoj zahtjev je poslan administratoru." }, - "youWillBeNotifiedOnceApproved": { - "message": "Dobiti ćeš obavijest kada bude odobreno." - }, "troubleLoggingIn": { "message": "Problem s prijavom?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Za tvoj račun je potrebna Duo dvostruka autentifikacija." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Za tvoj je račun potrebna Duo prijava u dva koraka. Za dovršetak prijave, slijedi daljnje korake." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Prati korake za dovršetak prijave." + }, "launchDuo": { "message": "Pokreni Duo u pregledniku" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "Nisu nađeni slobodni portovi za SSO prijavu." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrijsko otključavanje nije dostupno jer je prvo potrebno otključati PIN-om ili lozinkom." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrijsko otključavanje trenutno nije dostupno." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrijsko otključavanje nije dostupno zbog pogrešno konfiguriranih sistemskih datoteka." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrijsko otključavanje nije dostupno zbog pogrešno konfiguriranih sistemskih datoteka." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometrijsko otključavanje nije dostupno jer nije omogućeno za $EMAIL$ u Bitwarden desktop aplikaciji.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrijsko otključavanje trenutno nije dostupno iz nepoznatog razloga." + }, "authorize": { "message": "Autoriziraj" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Potvrdi korištenje SSH ključa" }, + "agentForwardingWarningTitle": { + "message": "Upozorenje: Agent proslijeđivanje" + }, + "agentForwardingWarningText": { + "message": "Ovaj je zahtjev došao s prijavljenog udaljenog uređaja" + }, "sshkeyApprovalMessageInfix": { "message": "traži pristup za" }, + "sshkeyApprovalMessageSuffix": { + "message": "za" + }, + "sshActionLogin": { + "message": "autentifikaciju poslužitelju" + }, + "sshActionSign": { + "message": "potpis poruke" + }, + "sshActionGitSign": { + "message": "potpis git commita" + }, "unknownApplication": { "message": "Aplikacija" }, - "sshKeyPasswordUnsupported": { - "message": "Uvoz SSH ključeva zaštićenih lozinkom još nije podržan" - }, "invalidSshKey": { "message": "SSH ključ nije valjan" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Uvezi ključ iz međuspremnika" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH ključ uspješno uvezen" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Promjeni e-poštu računa" + }, + "allowScreenshots": { + "message": "Dozvoli snimanje zaslona" + }, + "allowScreenshotsDesc": { + "message": "Dozvoli da Bitwarden desktop aplikacija bude snimljena u snimkama zaslona i prikazana u sesijama udaljene radne površine. Onemogućavanje ove opcije onemogućit će pristup nekim vanjskim zaslonima." + }, + "confirmWindowStillVisibleTitle": { + "message": "Potvrdi prozor je još uvijek vidljiv" + }, + "confirmWindowStillVisibleContent": { + "message": "Potvrdi da je prozor još uvijek vidljiv." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Potrebno je ažurirati proširenje" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "Proširenje preglednika koje koristitš zastarjelo. Ažuriraj ga ili onemogući provjeru otiska prsta u pregledniku u postavkama aplikacije za stolno računalo." + }, + "changeAtRiskPassword": { + "message": "Promijeni rizičnu lozinku" } } diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json index 11091cde860..b9573428136 100644 --- a/apps/desktop/src/locales/hu/messages.json +++ b/apps/desktop/src/locales/hu/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Hiba" }, + "decryptionError": { + "message": "Visszafejtési hiba" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "A Bitwarden nem tudta visszafejteni az alább felsorolt ​​széf elemeket." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Ügyfélszolgálat elérés siker", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "további adatvesztés elkerülése érdekében.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "január" }, @@ -529,10 +543,6 @@ "message": "Speciális karakterek bevonása", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Szavak száma" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Bejelentkezés a Bitwardenbe" }, + "enterTheCodeSentToYourEmail": { + "message": "Adjuk meg az email címre elküldött kódot." + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Adjuk meg a hitelesítő alkalmazása által generált kódot." + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Nyomjuk meg a YubiKey-t a hitelesítéshez." + }, "logInWithPasskey": { "message": "Bejelentkezés hozzáférési kulccsal" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Mesterjelszó emlékeztető" }, + "passwordStrengthScore": { + "message": "A jelszó erősségi pontszáma $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Csatlakozás szervezethez" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Adjuk meg fiók email címét és elküldésre kerül a jelszóra vonatkozó tipp." }, - "passwordHint": { - "message": "Jelszó emlékeztető" - }, - "enterEmailToGetHint": { - "message": "A fiók email címének megadása a mesterjelszó emlékeztető fogadásához." - }, "getMasterPasswordHint": { "message": "Mesterjelszó emlékeztető kérése" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "A hitelesítés megszakításra került vagy túl sokáig tartott. Próbáljuk újra." }, + "openInNewTab": { + "message": "Megnyitás új fülön" + }, "invalidVerificationCode": { "message": "Érvénytelen ellenőrző kód" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Adatok megjegyzése" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Ne kérdezzen újra ezen az eszközön 30 napig" + }, "sendVerificationCodeEmailAgain": { "message": "Megerősítő kód ismételt elküldése emailben" }, "useAnotherTwoStepMethod": { "message": "Másik kétlépcsős bejelentkezési mód használata" }, + "selectAnotherMethod": { + "message": "Másik módszer választás", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Helyreállító kód használata" + }, "insertYubiKey": { "message": "A YubiKey beillesztése a számítógép USB portjába és a rajta levő gomb megnyomása." }, @@ -871,6 +906,15 @@ "message": "Ellenőrzés szervezeti Duo Security segítségével a Duo Mobile alkalmazás, SMS, telefonhívás vagy U2F biztonsági kulcs használatával.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Személyazonosság ellenőrzése" + }, + "weDontRecognizeThisDevice": { + "message": "Nem ismerhető fel ez az eszköz. Írjuk be az email címünkre küldött kódot a személyazonosság igazolásához." + }, + "continueLoggingIn": { + "message": "A bejelentkezés folytatása" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Kétlépcsős bejelentkezés opciók" }, + "selectTwoStepLoginMethod": { + "message": "Kétlépcsős bejelentkezési mód használata" + }, "selfHostedEnvironment": { "message": "Saját üzemeltetésű környezet" }, - "selfHostedEnvironmentFooter": { - "message": "A helyileg üzemeltetett Bitwarden telepítés webcímének megadása." - }, "selfHostedBaseUrlHint": { "message": "Adjuk meg a helyileg tárolt Bitwarden telepítés alap webcímét. Példa: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Egyedi környezet" }, - "customEnvironmentFooter": { - "message": "Haladó felhasználóknak. Minden egyes szolgáltatásnak külön megadható az alap webcím." - }, "baseUrl": { "message": "Szerver webcím" }, @@ -956,6 +997,9 @@ "no": { "message": "Nem" }, + "location": { + "message": "Hely" + }, "overwritePassword": { "message": "Jelszó felülírása" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Prémium verzió megvásárlása" }, - "premiumPurchaseAlert": { - "message": "Prémium tagságot a bitwarden.com webes széfben lehet vásárolni. Szeretnénk most felkeresni a webhelyet?" - }, "premiumPurchaseAlertV2": { "message": "Prémium szolgáltatást vásárolhatunk a Bitwarden webalkalmazás fiókbeállításai között." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Jelszó vagy PIN kód szükséges az alkalmazás indításakor" }, + "requirePasswordWithoutPinOnStart": { + "message": "Jelszó szükséges az alkalmazás indításakor" + }, "recommendedForSecurity": { "message": "Biztonsági szempontból ajánlott." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "WebAutn hitelesítés" }, + "readSecurityKey": { + "message": "Biztonsági kulcs olvasása" + }, + "awaitingSecurityKeyInteraction": { + "message": "Várakozás a biztonsági kulcs interakciójára..." + }, "hideEmail": { "message": "Saját email cím elrejtése a címzettek elől." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Használjuk a tartomány konfigurált összes befogási bejövő postaládát." }, + "useThisEmail": { + "message": "Ezen email használata" + }, "random": { "message": "Véletlen" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ elutasította a kérést. Vegyük fel a kapcsolatot szolgáltatóval segítségért.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ elutasította a kérést: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Nem lehet beolvasni $SERVICENAME$ maszkolt email fiók azonosítót.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Bejelentkezés mesterjelszóval" }, - "loggingInAs": { - "message": "Bejelentkezve mint" - }, "rememberEmail": { "message": "Email megjegyzése" }, - "notYou": { - "message": "Ez tévedés?" - }, "newAroundHere": { "message": "Új felhasználó vagyunk?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "A bejelentkezés elindításra került." }, + "logInRequestSent": { + "message": "A kérés elküldésre került." + }, "notificationSentDevice": { "message": "Egy értesítés lett elküldve az eszközre." }, "aNotificationWasSentToYourDevice": { "message": "Egy értesítés lett elküldve az eszközre." }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Ellenőrizzük, hogy a széf feloldásra került és az ujjlenyomat kifejezés egyezik a másik eszközön levővel." + "notificationSentDevicePart1": { + "message": "A Bitwarden zárolás feloldása az eszközön vagy: " + }, + "notificationSentDeviceAnchor": { + "message": "webalkalmazás" + }, + "notificationSentDevicePart2": { + "message": "Jóváhagyás előtt győződjünk meg arról, hogy az ujjlenyomat kifejezés megegyezik az alábbi kifejezéssel." }, "needAnotherOptionV1": { "message": "Másik opció szükséges?" @@ -2759,10 +2839,10 @@ "message": "Karakterszámláló váltás", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Megpróbálunk bejelentkezni?" + "areYouTryingToAccessYourAccount": { + "message": "A fiókhoz próbálunk hozzáférni?" }, - "logInAttemptBy": { + "accessAttemptBy": { "message": "Bejelentkezési kísérlet $EMAIL$ segítségével", "placeholders": { "email": { @@ -2780,11 +2860,11 @@ "time": { "message": "Időpont" }, - "confirmLogIn": { - "message": "Bejelentkezés megerősítése" + "confirmAccess": { + "message": "Hozzáférés megerősítése" }, - "denyLogIn": { - "message": "Bejelentkezés megtagadása" + "denyAccess": { + "message": "Hozzáférés megtagadása" }, "logInConfirmedForEmailOnDevice": { "message": "A bejelelentketés $EMAIL$ email címmel megerősítésre került $DEVICE$ eszközön.", @@ -2820,7 +2900,7 @@ "thisRequestIsNoLongerValid": { "message": "A kérés a továbbiakban már nem érvényes." }, - "confirmLoginAtemptForMail": { + "confirmAccessAttempt": { "message": "Bejelentkezési kísérlet megerősítése $EMAIL$ email címmel", "placeholders": { "email": { @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Bejelentkezés megkérve" }, + "accountAccessRequested": { + "message": "Fiók hozzáférés kérés történt." + }, "creatingAccountOn": { "message": "Fiók létrehozása:" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Gyenge jelszó lett azonosítva és megtalálva egy adatvédelmi incidens során. A fók védelme érdekében használjunk erős és egyedi jelszót. Biztosan használni szeretnénk ezt a jelszót?" }, + "useThisPassword": { + "message": "Jelszó használata" + }, + "useThisUsername": { + "message": "Felhasználónév használata" + }, "checkForBreaches": { "message": "Az ehhez a jelszóhoz tartozó ismert adatvédelmi incidensek ellenőrzése" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Adminisztrátori jóváhagyás kérés" }, - "approveWithMasterPassword": { - "message": "Jóváhagyás mesterjelszóval" - }, "region": { "message": "Régió" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "A kérés elküldésre került az adminisztrátornak." }, - "youWillBeNotifiedOnceApproved": { - "message": "A jóváhagyás után értesítés érkezik." - }, "troubleLoggingIn": { "message": "Probléma van a bejelentkezéssel?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "A fiókhoz kétlépcsős DUO bejelentkezés szükséges." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo kétlépcsős bejelentkezés szükséges a fiókhoz. Kövessük az alábbi lépéseket a bejelentkezés befejezéséhez." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Kövessük az alábbi lépéseket a bejelentkezés befejezéséhez." + }, "launchDuo": { "message": "A Duo elindítása a böngészőben" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "Nem található szabad port az sso bejelentkezéshez." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "A biometrikus feloldás nem érhető el, mert először PIN kóddal vagy jelszóval kell feloldani." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "A biometrikus feloldás jelenleg nem érhető el." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "A biometrikus feloldás nem érhető el a rosszul konfigurált rendszerfájlok miatt." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "A biometrikus feloldás nem érhető el a rosszul konfigurált rendszerfájlok miatt." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "A biometrikus feloldás nem érhető el, mert nincs engedélyezve $EMAIL$ számára a Bitwarden asztali alkalmazásban.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "A biometrikus feloldás jelenleg ismeretlen okból nem érhető el." + }, "authorize": { "message": "Hitelesítés" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "SSH kulcs használat megerősítése" }, + "agentForwardingWarningTitle": { + "message": "Figyelmeztetés: Böngésző továbbítás" + }, + "agentForwardingWarningText": { + "message": "Ez a kérelem egy távoli eszközről érkezik, amelyre bejelentkeztünk" + }, "sshkeyApprovalMessageInfix": { "message": "hozzáférést kér:" }, + "sshkeyApprovalMessageSuffix": { + "message": "azért, hogy" + }, + "sshActionLogin": { + "message": "hitelesítsük magunkat a szerveren" + }, + "sshActionSign": { + "message": "aláírjunk egy üzenetet" + }, + "sshActionGitSign": { + "message": "aléírjunk egy git commit elemet" + }, "unknownApplication": { "message": "Egy alkalmazás" }, - "sshKeyPasswordUnsupported": { - "message": "A jelszóval védett SSH kulcsok importálása még nem támogatott." - }, "invalidSshKey": { "message": "Az SSH kulcs érvénytelen." }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Kulcs importálása vágólapból" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "Az SSH kulcs sikeresen importálásra került." }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Fiók email cím megváltoztatása" + }, + "allowScreenshots": { + "message": "Képernyőrögzítés engedélyezése" + }, + "allowScreenshotsDesc": { + "message": "Engedélyezi a Bitwarden asztali alkalmazásnak a képernyőképek rögzítését és távoli asztali munkamenetekben megtekintését. Ennek letiltása megakadályozza a hozzáférést bizonyos külső kijelzőkhöz." + }, + "confirmWindowStillVisibleTitle": { + "message": "Erősítsük meg, hogy az ablak továbbra is látható." + }, + "confirmWindowStillVisibleContent": { + "message": "Ellenőrizzük, hogy az ablak továbbra is látható-e." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Kiterjesztés frissítés szükséges" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "Al használt böngésző bővítmény elavult. Frissítsük vagy tiltsuk le a böngésző integráció ujjlenyomat ellenőrzését az asztali alkalmazás beállításainál." + }, + "changeAtRiskPassword": { + "message": "Kockázatos jelszó megváltoztatása" } } diff --git a/apps/desktop/src/locales/id/messages.json b/apps/desktop/src/locales/id/messages.json index b9af1c00045..df9b6484778 100644 --- a/apps/desktop/src/locales/id/messages.json +++ b/apps/desktop/src/locales/id/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Galat" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januari" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Jumlah kata" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Master password hint" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Petunjuk Sandi" - }, - "enterEmailToGetHint": { - "message": "Masukkan email akun Anda untuk menerima pentunjuk sandi utama Anda." - }, "getMasterPasswordHint": { "message": "Dapatkan petunjuk sandi utama" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Kode verifikasi tidak valid" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Ingat saya" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Kirim ulang email kode verifikasi" }, "useAnotherTwoStepMethod": { "message": "Gunakan metode masuk dua langkah lainnya" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Masukkan YubiKey Anda ke port USB komputer Anda, lalu sentuh tombol nya." }, @@ -871,6 +906,15 @@ "message": "Verifikasi dengan Duo Security untuk organisasi anda dengan menggunakan Aplikasi Duo Mobile, SMS, panggilan telepon atau kunci keamanan U2F.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Pilihan Info Masuk Dua Langkah" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Lingkungan Hos-mandiri" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premise hosted bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Lingkungan Kustom" }, - "customEnvironmentFooter": { - "message": "Untuk pengguna lanjutan. Anda dapat menentukan basis dari URL untuk setiap layanan mandiri." - }, "baseUrl": { "message": "URL Server" }, @@ -956,6 +997,9 @@ "no": { "message": "Tidak" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Timpa Sandi" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Beli Premium" }, - "premiumPurchaseAlert": { - "message": "Kamu bisa membeli keanggotaan Premium di Brankas Web bitwarden.com. Apakah kamu ingin mengunjungi situs web itu sekarang?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Require password or PIN on app start" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Direkomendasikan untuk keamanan." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Autentikasi dengan WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Sembunyikan alamat surel saya dari penerima." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Acak" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, "rememberEmail": { "message": "Remember email" }, - "notYou": { - "message": "Bukan Anda?" - }, "newAroundHere": { "message": "Pengguna baru?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "Toggle character count", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Are you trying to log in?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Login attempt by $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Time" }, - "confirmLogIn": { - "message": "Confirm login" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Deny login" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Login confirmed for $EMAIL$ on $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "confirmLoginAtemptForMail": { - "message": "Confirm login attempt for $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Log in requested" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Periksa pelanggaran data yang diketahui untuk kata sandi ini" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "region": { "message": "Region" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json index e035a13606d..ef5fa5ce381 100644 --- a/apps/desktop/src/locales/it/messages.json +++ b/apps/desktop/src/locales/it/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Errore" }, + "decryptionError": { + "message": "Errore di decifrazione" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden non può decifrare gli elementi elencati di seguito." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contatta il cliente correttamente", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "per evitare ulteriori perdite di dati.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Gennaio" }, @@ -529,10 +543,6 @@ "message": "Includi caratteri speciali", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Numero di parole" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Accedi a Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Inserisci il codice inviato alla tua e-mail" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Inserisci il codice dalla tua app di autenticazione" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Premi la tua chiave Yubi per autenticare" + }, "logInWithPasskey": { "message": "Accedi con passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Suggerimento per la password principale" }, + "passwordStrengthScore": { + "message": "Valutazione complessità parola d'accesso $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Unisciti all'organizzazione" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Inserisci l'indirizzo email dell'account e ti invieremo il tuo suggerimento per la password" }, - "passwordHint": { - "message": "Suggerimento per la password" - }, - "enterEmailToGetHint": { - "message": "Inserisci l'indirizzo email del tuo account per ricevere il suggerimento per la password principale." - }, "getMasterPasswordHint": { "message": "Ottieni suggerimento della password principale" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "L'autenticazione è stata annullata o ha richiesto troppo tempo. Per favore riprova." }, + "openInNewTab": { + "message": "Apri in una nuova scheda" + }, "invalidVerificationCode": { "message": "Codice di verifica non valido" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Ricordami" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Non chiedere più su questo dispositivo per 30 giorni" + }, "sendVerificationCodeEmailAgain": { "message": "Invia email con codice di verifica di nuovo" }, "useAnotherTwoStepMethod": { "message": "Usa un altro metodo di verifica in due passaggi" }, + "selectAnotherMethod": { + "message": "Seleziona un altro metodo", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Usa il tuo codice di recupero" + }, "insertYubiKey": { "message": "Inserisci la tua YubiKey nella porta USB del computer e premi il suo pulsante." }, @@ -871,6 +906,15 @@ "message": "Verifica con Duo Security per la tua organizzazione usando l'app Duo Mobile, SMS, chiamata telefonica, o chiave di sicurezza U2F.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verifica la tua identità" + }, + "weDontRecognizeThisDevice": { + "message": "Non riconosciamo questo dispositivo. Inserisci il codice inviato alla tua e-mail per verificare la tua identità." + }, + "continueLoggingIn": { + "message": "Continua l'accesso" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Opzioni di verifica in due passaggi" }, + "selectTwoStepLoginMethod": { + "message": "Seleziona metodo di accesso in due passaggi" + }, "selfHostedEnvironment": { "message": "Ambiente self-hosted" }, - "selfHostedEnvironmentFooter": { - "message": "Specifica l'URL principale della tua installazione self-hosted di Bitwarden." - }, "selfHostedBaseUrlHint": { "message": "Specifica lo URL principale della tua installazione self-hosted di Bitwarden. Esempio: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Ambiente personalizzato" }, - "customEnvironmentFooter": { - "message": "Per utenti avanzati. Puoi specificare l'URL principale di ogni servizio indipendentemente." - }, "baseUrl": { "message": "URL del server" }, @@ -956,6 +997,9 @@ "no": { "message": "No" }, + "location": { + "message": "Luogo" + }, "overwritePassword": { "message": "Sovrascrivi password" }, @@ -1320,7 +1364,7 @@ "description": "Copy credit card number" }, "copyEmail": { - "message": "Copia e-mail" + "message": "Copia email" }, "copySecurityCode": { "message": "Copia codice di sicurezza", @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Passa a Premium" }, - "premiumPurchaseAlert": { - "message": "Puoi acquistare il un abbonamento Premium dalla cassaforte web su bitwarden.com. Vuoi visitare il sito?" - }, "premiumPurchaseAlertV2": { "message": "Puoi acquistare Premium dalle impostazioni del tuo account sull'app web Bitwarden." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Richiedi password o PIN all'avvio dell'app" }, + "requirePasswordWithoutPinOnStart": { + "message": "Richiedi parola d'accesso all'avvio dell'app" + }, "recommendedForSecurity": { "message": "Consigliato per la sicurezza." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Autenticazione WebAuthn" }, + "readSecurityKey": { + "message": "Leggi chiave di sicurezza" + }, + "awaitingSecurityKeyInteraction": { + "message": "In attesa di interazione con la chiave di sicurezza..." + }, "hideEmail": { "message": "Nascondi il mio indirizzo email dai destinatari." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Usa la casella di posta catch-all di dominio." }, + "useThisEmail": { + "message": "Usa questa e-mail" + }, "random": { "message": "Casuale" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ ha rifiutato la tua richiesta. Contatta il tuo fornitore di servizi per assistenza.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ ha respinto la tua richiesta: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Impossibile ottenere l'ID dell'account email mascherato di $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Accedi con password principale" }, - "loggingInAs": { - "message": "Accedendo come" - }, "rememberEmail": { "message": "Ricorda email" }, - "notYou": { - "message": "Non sei tu?" - }, "newAroundHere": { "message": "Nuovo da queste parti?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Accesso avviato" }, + "logInRequestSent": { + "message": "Richiesta inviata" + }, "notificationSentDevice": { "message": "Una notifica è stata inviata al tuo dispositivo." }, "aNotificationWasSentToYourDevice": { "message": "Una notifica è stata inviata al tuo dispositivo" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Assicurati che il tuo account sia sbloccato e che la frase dell'impronta digitale corrisponda nell'altro dispositivo" + "notificationSentDevicePart1": { + "message": "Sblocca Bitwarden sul tuo dispositivo o su " + }, + "notificationSentDeviceAnchor": { + "message": "app web" + }, + "notificationSentDevicePart2": { + "message": "Assicurarsi che la frase di impronta digitale corrisponda a quella sottostante prima dell'approvazione." }, "needAnotherOptionV1": { "message": "Bisogno di un'altra opzione?" @@ -2759,10 +2839,10 @@ "message": "Attiva/disattiva conteggio caratteri", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Stai cercando di accedere?" + "areYouTryingToAccessYourAccount": { + "message": "Stai cercando di accedere al tuo account?" }, - "logInAttemptBy": { + "accessAttemptBy": { "message": "Tentativo di accesso di $EMAIL$", "placeholders": { "email": { @@ -2780,10 +2860,10 @@ "time": { "message": "Ora" }, - "confirmLogIn": { - "message": "Consenti accesso" + "confirmAccess": { + "message": "Conferma accesso" }, - "denyLogIn": { + "denyAccess": { "message": "Nega accesso" }, "logInConfirmedForEmailOnDevice": { @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "La richiesta non è più valida." }, - "confirmLoginAtemptForMail": { - "message": "Conferma il tentativo di accesso di $EMAIL$", + "confirmAccessAttempt": { + "message": "Conferma il tentativo di accesso per $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Accesso richiesto" }, + "accountAccessRequested": { + "message": "Accesso all'account richiesto" + }, "creatingAccountOn": { "message": "Creazione account su" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Password debole e trovata in una violazione dei dati. Usa una password forte e unica per proteggere il tuo account. Sei sicuro di voler usare questa password?" }, + "useThisPassword": { + "message": "Usa questa parola d'accesso" + }, + "useThisUsername": { + "message": "Usa questo nome utente" + }, "checkForBreaches": { "message": "Controlla se la tua password è presente in una violazione dei dati" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Richiedi approvazione dell'amministratore" }, - "approveWithMasterPassword": { - "message": "Approva con password principale" - }, "region": { "message": "Regione" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "La tua richiesta è stata inviata al tuo amministratore." }, - "youWillBeNotifiedOnceApproved": { - "message": "Riceverai una notifica una volta approvato." - }, "troubleLoggingIn": { "message": "Problemi ad accedere?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Per il tuo account è richiesta la verifica in due passaggi Duo." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Il login Duo in due passaggi è richiesto per il tuo account. Segui i passaggi qui sotto per completare l'accesso." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Segui i passaggi qui sotto per completare l'accesso." + }, "launchDuo": { "message": "Avvia Duo nel browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "Non è stato possibile trovare nessuna porta libera per il login Sso." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Lo sblocco biometrico non è disponibile perché è necessario prima sbloccare con PIN o parola d'accesso." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Lo sblocco biometrico non è attualmente disponibile." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Lo sblocco biometrico non è disponibile a causa di file di sistema mal configurati." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Lo sblocco biometrico non è disponibile a causa di file di sistema mal configurati." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Lo sblocco biometrico non è disponibile perché non è abilitato per $EMAIL$ nell'app desktop Bitwarden.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Lo sblocco biometrico non è attualmente disponibile per un motivo sconosciuto." + }, "authorize": { "message": "Autorizza" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Conferma l'uso della chiave SSH" }, + "agentForwardingWarningTitle": { + "message": "Attenzione: inoltro agente" + }, + "agentForwardingWarningText": { + "message": "Questa richiesta proviene da un dispositivo remoto al quale si è connessi" + }, "sshkeyApprovalMessageInfix": { "message": "richiede l'accesso a" }, + "sshkeyApprovalMessageSuffix": { + "message": "al fine di" + }, + "sshActionLogin": { + "message": "autenticazione su un server" + }, + "sshActionSign": { + "message": "firmare un messaggio" + }, + "sshActionGitSign": { + "message": "firmare un commit git" + }, "unknownApplication": { "message": "Un'applicazione" }, - "sshKeyPasswordUnsupported": { - "message": "L'importazione di chiavi SSH protette da password non è ancora supportata" - }, "invalidSshKey": { "message": "La chiave SSH non è valida" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Importa chiave dagli Appunti" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "Chiave SSH importata correttamente" }, "fileSavedToDevice": { @@ -3423,12 +3551,33 @@ "message": "No, non ce l'ho" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Sì, posso accedere in modo affidabile alla mia e-mail" + "message": "Sì, riesco ad accedere all'email" }, "turnOnTwoStepLogin": { "message": "Attiva accesso in due passaggi" }, "changeAcctEmail": { "message": "Cambia l'e-mail dell'account" + }, + "allowScreenshots": { + "message": "Permetti cattura dello schermo" + }, + "allowScreenshotsDesc": { + "message": "Consenti all' applicazione desktop Bitwarden di essere catturata in schermate e visualizzata in sessioni desktop remote. Disattivando questa opzione si impedirà l'accesso in alcuni schermi esterni." + }, + "confirmWindowStillVisibleTitle": { + "message": "Conferma la finestra ancora visibile" + }, + "confirmWindowStillVisibleContent": { + "message": "Si prega di confermare che la finestra è ancora visibile." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Aggiornamento estensione richiesto" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "L'estensione del browser che stai usando non è aggiornata. Aggiornala o disabilita la convalida dell'impronta digitale per l'integrazione del browser nelle impostazioni dell'app desktop." + }, + "changeAtRiskPassword": { + "message": "Modifica la password non sicura o esposta" } } diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json index 7315d74a70f..e8ba0e8509b 100644 --- a/apps/desktop/src/locales/ja/messages.json +++ b/apps/desktop/src/locales/ja/messages.json @@ -249,6 +249,20 @@ "error": { "message": "エラー" }, + "decryptionError": { + "message": "復号エラー" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden は以下の保管庫のアイテムを復号できませんでした。" + }, + "contactCSToAvoidDataLossPart1": { + "message": "カスタマーサクセスに問い合わせて、", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "さらなるデータ損失を回避してください。", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "1月" }, @@ -529,10 +543,6 @@ "message": "特殊記号を含める", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "単語数" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Bitwarden にログイン" }, + "enterTheCodeSentToYourEmail": { + "message": "メールアドレスに送信されたコードを入力してください" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "認証アプリに表示されているコードを入力してください" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "YubiKey を押して認証してください" + }, "logInWithPasskey": { "message": "パスキーでログイン" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "マスターパスワードのヒント" }, + "passwordStrengthScore": { + "message": "パスワードの強度スコア $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "組織に参加" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "アカウントのメールアドレスを入力すると、パスワードのヒントが送信されます" }, - "passwordHint": { - "message": "パスワードのヒント" - }, - "enterEmailToGetHint": { - "message": "マスターパスワードのヒントを受信するアカウントのメールアドレスを入力してください。" - }, "getMasterPasswordHint": { "message": "マスターパスワードのヒントを取得する" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "認証がキャンセルされたか、時間がかかりすぎました。もう一度やり直してください。" }, + "openInNewTab": { + "message": "新しいタブで開く" + }, "invalidVerificationCode": { "message": "認証コードが間違っています" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "情報を保存する" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "このデバイスで30日間再表示しない" + }, "sendVerificationCodeEmailAgain": { "message": "確認コードをメールで再送" }, "useAnotherTwoStepMethod": { "message": "他の2段階認証方法を使用" }, + "selectAnotherMethod": { + "message": "別の方法を選択", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "リカバリーコードを使用する" + }, "insertYubiKey": { "message": "YubiKey を USB ポートに挿入し、ボタンをタッチしてください。" }, @@ -871,6 +906,15 @@ "message": "組織の Duo Security を Duo Mobile アプリや SMS、電話、U2F セキュリティーキーを使用して認証します。", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "本人確認" + }, + "weDontRecognizeThisDevice": { + "message": "このデバイスは未確認です。本人確認のため、メールアドレスに送信されたコードを入力してください。" + }, + "continueLoggingIn": { + "message": "ログインを続ける" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "2段階認証オプション" }, + "selectTwoStepLoginMethod": { + "message": "2段階認証の方法を選択" + }, "selfHostedEnvironment": { "message": "セルフホスティング環境" }, - "selfHostedEnvironmentFooter": { - "message": "セルフホスティングしている Bitwarden のベース URL を指定してください。" - }, "selfHostedBaseUrlHint": { "message": "オンプレミスホストした Bitwarden のベース URL を指定してください。例: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "カスタム環境" }, - "customEnvironmentFooter": { - "message": "上級者向けです。各サービスのベース URL を個別に指定できます。" - }, "baseUrl": { "message": "サーバー URL" }, @@ -956,6 +997,9 @@ "no": { "message": "いいえ" }, + "location": { + "message": "場所" + }, "overwritePassword": { "message": "パスワードを上書き" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "プレミアム会員に加入" }, - "premiumPurchaseAlert": { - "message": "プレミアム会員権は bitwarden.com ウェブ保管庫で購入できます。ウェブサイトを開きますか?" - }, "premiumPurchaseAlertV2": { "message": "Bitwarden ウェブアプリでアカウント設定からプレミアムを購入できます。" }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "アプリ起動時にパスワードまたは PIN を要求" }, + "requirePasswordWithoutPinOnStart": { + "message": "アプリ起動時にパスワードを要求" + }, "recommendedForSecurity": { "message": "セキュリティ向上のためおすすめします。" }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "WebAuthn の認証" }, + "readSecurityKey": { + "message": "セキュリティキーの読み取り" + }, + "awaitingSecurityKeyInteraction": { + "message": "セキュリティキーとの通信を待ち受け中…" + }, "hideEmail": { "message": "メールアドレスを受信者に表示しない" }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "ドメインに設定されたキャッチオール受信トレイを使用します。" }, + "useThisEmail": { + "message": "このメールアドレスを使う" + }, "random": { "message": "ランダム" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ はリクエストを拒否しました。サービスプロバイダーにお問い合わせください。", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ があなたのリクエストを拒否しました: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "$SERVICENAME$ マスク済みメールアカウント ID を取得できませんでした。", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "マスターパスワードでログイン" }, - "loggingInAs": { - "message": "ログイン中:" - }, "rememberEmail": { "message": "メールアドレスを保存" }, - "notYou": { - "message": "あなたではないですか?" - }, "newAroundHere": { "message": "初めてですか?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "ログイン開始" }, + "logInRequestSent": { + "message": "リクエストが送信されました" + }, "notificationSentDevice": { "message": "デバイスに通知を送信しました。" }, "aNotificationWasSentToYourDevice": { "message": "お使いのデバイスに通知が送信されました" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "アカウントがロック解除されていることと、フィンガープリントフレーズが他の端末と一致していることを確認してください" + "notificationSentDevicePart1": { + "message": "デバイスまたは" + }, + "notificationSentDeviceAnchor": { + "message": "ウェブアプリ" + }, + "notificationSentDevicePart2": { + "message": "上で、Bitwarden をロック解除してください。承認する前に、フィンガープリントフレーズが以下と一致していることを確認してください。" }, "needAnotherOptionV1": { "message": "別の選択肢が必要ですか?" @@ -2759,10 +2839,10 @@ "message": "文字カウントを切り替える", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "ログインしようとしていますか?" + "areYouTryingToAccessYourAccount": { + "message": "アカウントにアクセスしようとしていますか?" }, - "logInAttemptBy": { + "accessAttemptBy": { "message": "$EMAIL$ によるログインの試行", "placeholders": { "email": { @@ -2780,11 +2860,11 @@ "time": { "message": "時間" }, - "confirmLogIn": { - "message": "ログインを承認" + "confirmAccess": { + "message": "アクセスの確認" }, - "denyLogIn": { - "message": "ログインを拒否" + "denyAccess": { + "message": "アクセスを拒否" }, "logInConfirmedForEmailOnDevice": { "message": "$EMAIL$ に $DEVICE$ でのログインを承認しました", @@ -2820,7 +2900,7 @@ "thisRequestIsNoLongerValid": { "message": "このリクエストは無効になりました。" }, - "confirmLoginAtemptForMail": { + "confirmAccessAttempt": { "message": "$EMAIL$ のログイン試行を確認", "placeholders": { "email": { @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "ログインリクエスト済み" }, + "accountAccessRequested": { + "message": "アカウントへのアクセスが要求されました" + }, "creatingAccountOn": { "message": "アカウント作成:" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "入力されたパスワードは脆弱かつすでに流出済みです。アカウントを守るためより強力で一意なパスワードを使用してください。本当にこの脆弱なパスワードを使用しますか?" }, + "useThisPassword": { + "message": "このパスワードを使用する" + }, + "useThisUsername": { + "message": "このユーザー名を使用する" + }, "checkForBreaches": { "message": "このパスワードの既知のデータ流出を確認" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "管理者の承認を要求する" }, - "approveWithMasterPassword": { - "message": "マスターパスワードで承認する" - }, "region": { "message": "リージョン" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "要求を管理者に送信しました。" }, - "youWillBeNotifiedOnceApproved": { - "message": "承認されると通知されます。 " - }, "troubleLoggingIn": { "message": "ログインできない場合" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "アカウントには Duo 二段階認証が必要です。" }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "このアカウントでは Duo 二段階認証を行う必要があります。以下の手順に従ってログインを完了してください。" + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "以下の手順に従ってログインを完了してください。" + }, "launchDuo": { "message": "ブラウザで Duo を起動" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "SSO ログインのための空きポートが見つかりませんでした。" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "PINまたはパスワードによるロック解除が最初に必要なため、生体認証によるロック解除は利用できません。" + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "生体認証によるロック解除は現在利用できません。" + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "システムファイルの設定が誤っているため、生体認証によるロック解除は利用できません。" + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "システムファイルの設定が誤っているため、生体認証によるロック解除は利用できません。" + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "生体認証による $EMAIL$ のロック解除は、 Bitwarden デスクトップアプリ上で有効になっていないため、利用できません。", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "生体認証によるロック解除は、不明な理由により現在利用できません。" + }, "authorize": { "message": "認可" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "SSH 鍵の使用を確認します" }, + "agentForwardingWarningTitle": { + "message": "警告: エージェント転送" + }, + "agentForwardingWarningText": { + "message": "このリクエストは、あなたがログインしているリモートデバイスから送信されています" + }, "sshkeyApprovalMessageInfix": { - "message": "がアクセスを要求しています: " + "message": "が" + }, + "sshkeyApprovalMessageSuffix": { + "message": "へのアクセスを要求しています。目的:" + }, + "sshActionLogin": { + "message": "サーバーへの認証" + }, + "sshActionSign": { + "message": "メッセージへの署名" + }, + "sshActionGitSign": { + "message": "git コミットへの署名" }, "unknownApplication": { "message": "アプリ" }, - "sshKeyPasswordUnsupported": { - "message": "パスワードで保護された SSH キーのインポートはまだサポートされていません" - }, "invalidSshKey": { "message": "SSH キーが無効です" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "クリップボードからキーをインポート" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH キーのインポートに成功しました" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "アカウントのメールアドレスを変更する" + }, + "allowScreenshots": { + "message": "スクリーンショットを許可" + }, + "allowScreenshotsDesc": { + "message": "Bitwarden デスクトップアプリをスクリーンショットでキャプチャしたり、リモートデスクトップセッションで表示したりすることを許可します。これを無効にすると、一部の外部ディスプレイ上でアクセスできなくなります。" + }, + "confirmWindowStillVisibleTitle": { + "message": "ウィンドウが表示されていることを確認" + }, + "confirmWindowStillVisibleContent": { + "message": "ウィンドウが現在も表示されていることを確認してください。" + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "拡張機能のアップデートが必要" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "お使いのブラウザ拡張機能が古くなっています。拡張機能を更新するか、デスクトップアプリの設定からブラウザ統合の指紋認証を無化してください。" + }, + "changeAtRiskPassword": { + "message": "危険なパスワードの変更" } } diff --git a/apps/desktop/src/locales/ka/messages.json b/apps/desktop/src/locales/ka/messages.json index 6c554a82700..96962e14ff5 100644 --- a/apps/desktop/src/locales/ka/messages.json +++ b/apps/desktop/src/locales/ka/messages.json @@ -249,6 +249,20 @@ "error": { "message": "შეცდომა" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "იანვარი" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Number of words" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Master password hint" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "არასწორი გადამოწმების კოდი" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "დამიმახსოვრე" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -871,6 +906,15 @@ "message": "Verify with Duo Security for your organization using the Duo Mobile app, SMS, phone call, or U2F security key.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Self-hosted environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Custom environment" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "სერვერის URL" }, @@ -956,6 +997,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Overwrite password" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Purchase Premium" }, - "premiumPurchaseAlert": { - "message": "You can purchase premium membership on the bitwarden.com web vault. Do you want to visit the website now?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Require password or PIN on app start" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Recommended for security." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Hide my email address from recipients." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "შემთხვევითი" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, "rememberEmail": { "message": "Remember email" }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "Toggle character count", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Are you trying to log in?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Login attempt by $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "დრო" }, - "confirmLogIn": { - "message": "Confirm login" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Deny login" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Login confirmed for $EMAIL$ on $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "confirmLoginAtemptForMail": { - "message": "Confirm login attempt for $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Log in requested" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "region": { "message": "რეგიონი" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/km/messages.json b/apps/desktop/src/locales/km/messages.json index 323d0cd3f7b..f93db44aa69 100644 --- a/apps/desktop/src/locales/km/messages.json +++ b/apps/desktop/src/locales/km/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Number of words" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Master password hint" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -871,6 +906,15 @@ "message": "Verify with Duo Security for your organization using the Duo Mobile app, SMS, phone call, or U2F security key.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Self-hosted environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Custom environment" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "Server URL" }, @@ -956,6 +997,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Overwrite password" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Purchase Premium" }, - "premiumPurchaseAlert": { - "message": "You can purchase premium membership on the bitwarden.com web vault. Do you want to visit the website now?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Require password or PIN on app start" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Recommended for security." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Hide my email address from recipients." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, "rememberEmail": { "message": "Remember email" }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "Toggle character count", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Are you trying to log in?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Login attempt by $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Time" }, - "confirmLogIn": { - "message": "Confirm login" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Deny login" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Login confirmed for $EMAIL$ on $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "confirmLoginAtemptForMail": { - "message": "Confirm login attempt for $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Log in requested" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "region": { "message": "Region" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/kn/messages.json b/apps/desktop/src/locales/kn/messages.json index 6182bd3a33d..f9bb154b66e 100644 --- a/apps/desktop/src/locales/kn/messages.json +++ b/apps/desktop/src/locales/kn/messages.json @@ -249,6 +249,20 @@ "error": { "message": "ದೋಷ" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "ಜನವರಿ" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "ಪದಗಳ ಸಂಖ್ಯೆ" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Master password hint" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "ಪಾಸ್ವರ್ಡ್ ಸುಳಿವು" - }, - "enterEmailToGetHint": { - "message": "ವಿಸ್ತರಣೆಯನ್ನು ಪ್ರಾರಂಭಿಸಲು ಮೆನುವಿನಲ್ಲಿರುವ ಬಿಟ್‌ವಾರ್ಡೆನ್ ಐಕಾನ್ ಟ್ಯಾಪ್ ಮಾಡಿ." - }, "getMasterPasswordHint": { "message": "ಮಾಸ್ಟರ್ ಪಾಸ್ವರ್ಡ್ ಸುಳಿವನ್ನು ಪಡೆಯಿರಿ" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "ನನ್ನನ್ನು ನೆನಪಿನಲ್ಲಿ ಇಡು" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "ಪರಿಶೀಲನೆ ಕೋಡ್ ಇಮೇಲ್ ಅನ್ನು ಮತ್ತೆ ಕಳುಹಿಸಿ" }, "useAnotherTwoStepMethod": { "message": "ಮತ್ತೊಂದು ಎರಡು-ಹಂತದ ಲಾಗಿನ್ ವಿಧಾನವನ್ನು ಬಳಸಿ" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "ನಿಮ್ಮ ಯುಬಿಕಿಯನ್ನು ನಿಮ್ಮ ಕಂಪ್ಯೂಟರ್‌ನ ಯುಎಸ್‌ಬಿ ಪೋರ್ಟ್ಗೆ ಸೇರಿಸಿ, ನಂತರ ಅದರ ಗುಂಡಿಯನ್ನು ಸ್ಪರ್ಶಿಸಿ." }, @@ -871,6 +906,15 @@ "message": "ಡ್ಯುಯೊ ಮೊಬೈಲ್ ಅಪ್ಲಿಕೇಶನ್, ಎಸ್‌ಎಂಎಸ್, ಫೋನ್ ಕರೆ ಅಥವಾ ಯು 2 ಎಫ್ ಭದ್ರತಾ ಕೀಲಿಯನ್ನು ಬಳಸಿಕೊಂಡು ನಿಮ್ಮ ಸಂಸ್ಥೆಗಾಗಿ ಡ್ಯುಯೊ ಸೆಕ್ಯುರಿಟಿಯೊಂದಿಗೆ ಪರಿಶೀಲಿಸಿ.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "ಎರಡು ಹಂತದ ಲಾಗಿನ್ ಆಯ್ಕೆಗಳು" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "ಸ್ವಯಂ ಆತಿಥೇಯ ಪರಿಸರ" }, - "selfHostedEnvironmentFooter": { - "message": "ನಿಮ್ಮ ಆನ್-ಪ್ರಮೇಯ ಹೋಸ್ಟ್ ಮಾಡಿದ ಬಿಟ್‌ವಾರ್ಡೆನ್ ಸ್ಥಾಪನೆಯ ಮೂಲ URL ಅನ್ನು ನಿರ್ದಿಷ್ಟಪಡಿಸಿ." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "ಕಸ್ಟಮ್ ಪರಿಸರ" }, - "customEnvironmentFooter": { - "message": "ಸುಧಾರಿತ ಬಳಕೆದಾರರಿಗಾಗಿ. ನೀವು ಪ್ರತಿ ಸೇವೆಯ ಮೂಲ URL ಅನ್ನು ಸ್ವತಂತ್ರವಾಗಿ ನಿರ್ದಿಷ್ಟಪಡಿಸಬಹುದು." - }, "baseUrl": { "message": "ಸರ್ವರ್ URL" }, @@ -956,6 +997,9 @@ "no": { "message": "ಇಲ್ಲ" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "ಪಾಸ್ವರ್ಡ್ ಅನ್ನು ಬದಲಿಸಿ" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "ಪ್ರೀಮಿಯಂ ಖರೀದಿಸಿ" }, - "premiumPurchaseAlert": { - "message": "ನೀವು ಬಿಟ್ವಾರ್ಡೆನ್.ಕಾಮ್ ವೆಬ್ ವಾಲ್ಟ್ನಲ್ಲಿ ಪ್ರೀಮಿಯಂ ಸದಸ್ಯತ್ವವನ್ನು ಖರೀದಿಸಬಹುದು. ನೀವು ಈಗ ವೆಬ್‌ಸೈಟ್‌ಗೆ ಭೇಟಿ ನೀಡಲು ಬಯಸುವಿರಾ?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Require password or PIN on app start" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Recommended for security." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "WebAuthn ಅನ್ನು ಪ್ರಮಾಣಿಕರಿಸು" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "ಸ್ವೀಕರಿಸುವವರಿಂದ ನನ್ನ ಇಮೇಲ್ ವಿಳಾಸವನ್ನು ಮರೆಮಾಡಿ." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, "rememberEmail": { "message": "Remember email" }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "Toggle character count", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Are you trying to log in?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Login attempt by $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Time" }, - "confirmLogIn": { - "message": "Confirm login" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Deny login" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Login confirmed for $EMAIL$ on $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "confirmLoginAtemptForMail": { - "message": "Confirm login attempt for $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Log in requested" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "region": { "message": "Region" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json index 89c5430a5fb..44bc681f205 100644 --- a/apps/desktop/src/locales/ko/messages.json +++ b/apps/desktop/src/locales/ko/messages.json @@ -64,7 +64,7 @@ } }, "welcomeBack": { - "message": "Welcome back" + "message": "돌아온 것을 환영합니다" }, "moveToOrgDesc": { "message": "이 항목을 이동할 조직을 선택하십시오. 항목이 조직으로 이동되면 소유권이 조직으로 이전됩니다. 일단 이동되면, 더는 이동된 항목의 직접적인 소유자가 아니게 됩니다." @@ -181,16 +181,16 @@ "message": "주소" }, "sshPrivateKey": { - "message": "Private key" + "message": "비공개 키" }, "sshPublicKey": { - "message": "Public key" + "message": "공개 키" }, "sshFingerprint": { - "message": "Fingerprint" + "message": "지문" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "키 유형" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -205,31 +205,31 @@ "message": "RSA 4096-Bit" }, "sshKeyGenerated": { - "message": "A new SSH key was generated" + "message": "새로운 SSH 키가 생성되었습니다." }, "sshKeyWrongPassword": { - "message": "The password you entered is incorrect." + "message": "입력된 비밀번호가 올바르지 않습니다." }, "importSshKey": { - "message": "Import" + "message": "가져오기" }, "confirmSshKeyPassword": { - "message": "Confirm password" + "message": "비밀번호 확인" }, "enterSshKeyPasswordDesc": { - "message": "Enter the password for the SSH key." + "message": "SSH 키의 비밀번호 입력" }, "enterSshKeyPassword": { - "message": "Enter password" + "message": "비밀번호 입력" }, "sshAgentUnlockRequired": { - "message": "Please unlock your vault to approve the SSH key request." + "message": "SSH 키 요청을 승인하려면 보관함을 해제하세요." }, "sshAgentUnlockTimeout": { - "message": "SSH key request timed out." + "message": "SSH 키 요청시간 만료" }, "enableSshAgent": { - "message": "Enable SSH agent" + "message": "SSH 에이전트 활성화" }, "enableSshAgentDesc": { "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." @@ -249,6 +249,20 @@ "error": { "message": "오류" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "1월" }, @@ -461,10 +475,10 @@ "message": "비밀번호 복사" }, "regenerateSshKey": { - "message": "Regenerate SSH key" + "message": "SSH 키 재생성" }, "copySshPrivateKey": { - "message": "Copy SSH private key" + "message": "SSH 비공개 키 복사" }, "copyPassphrase": { "message": "Copy passphrase", @@ -498,11 +512,11 @@ "message": "특수 문자 (!@#$%^&*)" }, "include": { - "message": "Include", + "message": "포함", "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "대문자 포함", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -510,7 +524,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "소문자 포함", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -518,7 +532,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "숫자 포함", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -526,13 +540,9 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "특수 문자 포함", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "단어 수" }, @@ -565,7 +575,7 @@ "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { - "message": "Enterprise policy requirements have been applied to your generator options.", + "message": "기업 정책에 따른 요구사항이 생성기 옵션에 적용되어 있습니다.", "description": "Indicates that a policy limits the credential generator screen." }, "searchCollection": { @@ -603,7 +613,7 @@ "message": "최대 파일 크기는 500MB입니다." }, "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "message": "암호화 키 마이그레이션이 필요합니다. 웹 보관함에 로그인하여 암호화 키를 업데이트하세요." }, "editedFolder": { "message": "폴더 편집함" @@ -624,28 +634,37 @@ "message": "계정 만들기" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Bitwarden이 처음 이신가요?" }, "setAStrongPassword": { - "message": "Set a strong password" + "message": "강력한 비밀번호 설정" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "Finish creating your account by setting a password" + "message": "비밀번호를 설정하여 계정 생성을 완료하세요" }, "logIn": { "message": "로그인" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Bitwarden 로그인" + }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "패스키로 로그인" }, "loginWithDevice": { - "message": "Log in with device" + "message": "기기로 로그인" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "Single sign-on(SSO) 사용" }, "submit": { "message": "보내기" @@ -679,7 +698,7 @@ } }, "masterPassword": { - "message": "Master password" + "message": "마스터 비밀번호" }, "masterPassImportant": { "message": "Your master password cannot be recovered if you forget it!" @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Master password hint" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "비밀번호 힌트" - }, - "enterEmailToGetHint": { - "message": "마스터 비밀번호 힌트를 받으려면 계정의 이메일 주소를 입력하세요." - }, "getMasterPasswordHint": { "message": "마스터 비밀번호 힌트 얻기" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "유효하지 않은 확인 코드" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "기억하기" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "인증 코드 이메일 다시 보내기" }, "useAnotherTwoStepMethod": { "message": "다른 2단계 인증 사용" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "YubiKey를 컴퓨터의 USB 포트에 삽입하고 버튼을 누르세요." }, @@ -871,6 +906,15 @@ "message": "Duo Mobile 앱, SMS, 전화 통화를 사용한 조직용 Duo Security 또는 U2F 보안 키를 사용하여 인증하세요.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "2단계 인증 옵션" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "자체 호스팅 환경" }, - "selfHostedEnvironmentFooter": { - "message": "온-프레미스 Bitwarden이 호스팅되고 있는 서버의 기본 URL을 지정하세요." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "사용자 지정 환경" }, - "customEnvironmentFooter": { - "message": "고급 사용자 전용 설정입니다. 각 서비스의 기본 URL을 개별적으로 지정할 수 있습니다." - }, "baseUrl": { "message": "서버 URL" }, @@ -956,6 +997,9 @@ "no": { "message": "아니오" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "비밀번호 덮어쓰기" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "프리미엄 멤버십 구입" }, - "premiumPurchaseAlert": { - "message": "bitwarden.com 웹 보관함에서 프리미엄 멤버십을 구입할 수 있습니다. 지금 웹 사이트를 방문하시겠습니까?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Require password or PIN on app start" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Recommended for security." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "WebAuthn 인증" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "받는 사람으로부터 나의 이메일 주소 숨기기" }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "catch-all이 설정된 내 도메인의 메일함을 사용하세요." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "무작위" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "마스터 비밀번호로 로그인" }, - "loggingInAs": { - "message": "다음으로 로그인 중" - }, "rememberEmail": { "message": "이메일 기억하기" }, - "notYou": { - "message": "본인이 아닌가요?" - }, "newAroundHere": { "message": "새로 찾아오셨나요?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "기기에 알림이 전송되었습니다." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "글자 수 표시하기", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "로그인을 시도하셨나요?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "$EMAIL$(으)로의 로그인 시도", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "시각" }, - "confirmLogIn": { - "message": "로그인 확인" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "로그인 거부" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "$DEVICE$에서 $EMAIL$(으)로의 로그인이 확인되었습니다", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "더 이상 유효하지 않은 요청입니다." }, - "confirmLoginAtemptForMail": { - "message": "$EMAIL$(으)로의 로그인 시도 확인", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "로그인 요청됨" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "region": { "message": "Region" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/lt/messages.json b/apps/desktop/src/locales/lt/messages.json index 6e07f4ceee3..40436c7ccda 100644 --- a/apps/desktop/src/locales/lt/messages.json +++ b/apps/desktop/src/locales/lt/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Klaida" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Sausis" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Žodžių skaičius" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Master password hint" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Slaptažodžio užuomina" - }, - "enterEmailToGetHint": { - "message": "Įveskite savo paskyros el. pašto adresą, kad gautumėte pagrindinio slaptažodžio užuominą." - }, "getMasterPasswordHint": { "message": "Gauti pagrindinio slaptažodžio užuominą" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Neteisingas patvirtinimo kodas" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Prisiminti mane" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Pakartotinai atsiųsti patvirtinimo kodą el. paštu" }, "useAnotherTwoStepMethod": { "message": "Naudoti kitą dviejų žingsnių prisijungimo metodą" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Įkiškite YubiKey į savo kompiuterio USB prievadą, tada palieskite jo mygtuką." }, @@ -871,6 +906,15 @@ "message": "Patikrinkite su Duo Security savo organizacijai naudodami Duo Mobile programą, SMS žinutę, telefono skambutį arba U2F saugumo raktą.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Dviejų žingsnių prisijungimo parinktys" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Savarankiškai sukurta aplinka" }, - "selfHostedEnvironmentFooter": { - "message": "Nurodykite pagrindinį URL adresą savo patalpose esančio Bitwarden įdiegimo." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Individualizuota aplinka" }, - "customEnvironmentFooter": { - "message": "Pažengusiems naudotojams. Galite nurodyti kiekvienos paslaugos pagrindinį URL adresą atskirai." - }, "baseUrl": { "message": "Serverio nuoroda" }, @@ -956,6 +997,9 @@ "no": { "message": "Ne" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Perrašyti slaptažodį" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Įsigyti Premium" }, - "premiumPurchaseAlert": { - "message": "Galite įsigyti Premium narystę bitwarden.com interneto saugykloje. Ar norite aplankyti svetainę dabar?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Reikalauti slaptažodžio arba PIN paleidus programėlę" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Rekomenduojama dėl saugumo." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Autentifikuoti WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Slėpti mano el. pašto adresą nuo gavėjų." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Naudokite savo domeno sukonfigūruotą viską palaikančią pašto dežutę." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Atsitiktinis" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Prisijungti su pagrindiniu slaptažodžiu" }, - "loggingInAs": { - "message": "Prisijungimas kaip" - }, "rememberEmail": { "message": "Prisiminti el. paštą" }, - "notYou": { - "message": "Ne jūs?" - }, "newAroundHere": { "message": "Ar jūs naujas čia?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Pradėtas prisijungimas" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "Į jūsų įrenginį išsiųstas pranešimas." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "Perjungti simbolių skaičių", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Ar bandote prisijungti?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Prisijungimo bandymas iš $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Laikas" }, - "confirmLogIn": { - "message": "Patvirtinkite prisijungimą" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Neleisti prisijungti" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "$EMAIL$ prisijungimas patvirtinas $DEVICE$ įrenginyje", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "Ši užklausa nebegalioja." }, - "confirmLoginAtemptForMail": { - "message": "Patvirtinti bandymą prisijungti iš $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Prašoma prisijungti" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Silpnas slaptažodis nustatytas ir rastas per duomenų pažeidimą. Norėdami apsaugoti paskyrą, naudokite stiprų ir unikalų slaptažodį. Ar tikrai norite naudoti šį slaptažodį?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Patikrinti žinomus šio slaptažodžio duomenų pažeidimus" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Prašyti administratoriaus patvirtinimo" }, - "approveWithMasterPassword": { - "message": "Patvirtinti su pagrindiniu slaptažodžiu" - }, "region": { "message": "Regionas" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Jūsų prašymas išsiųstas administratoriui." }, - "youWillBeNotifiedOnceApproved": { - "message": "Jums bus pranešta, kai bus patvirtinta." - }, "troubleLoggingIn": { "message": "Nepavyksta prisijungti?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json index a3db76ac646..c519987fc72 100644 --- a/apps/desktop/src/locales/lv/messages.json +++ b/apps/desktop/src/locales/lv/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Kļūda" }, + "decryptionError": { + "message": "Atšifrēšanas kļūda" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nevarēja atšifrēt zemāk uzskaitītos glabātavas vienumus." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Sazināties ar klientu atbalstu", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "lai izvairītos no papildu datu zaudējumiem.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Janvāris" }, @@ -529,10 +543,6 @@ "message": "Iekļaut īpašās rakstzīmes", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Vārdu skaits" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Pieteikties Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Jāievada e-pastā nosūtītais kods" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Jāievada kods no savas autentificētājlietotnes" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Jāpiespiež sava YubiKey ierīce, lai autentificētu" + }, "logInWithPasskey": { "message": "Pieteikties ar piekļuves atslēgu" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Galvenās paroles norāde" }, + "passwordStrengthScore": { + "message": "Paroles stipruma novērtējums $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Pievienoties apvienībai" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Jāievada sava konta e-pasta adrese, un paroles norāde tiks nosūtīta" }, - "passwordHint": { - "message": "Paroles norāde" - }, - "enterEmailToGetHint": { - "message": "Ievadiet sava konta e-pasta adresi, lai saņemtu galvenās paroles norādi!" - }, "getMasterPasswordHint": { "message": "Saņemt galvenās paroles norādi" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "Autentifikācija tika atcelta vai tā aizņēma pārāk daudz laika. Lūgums mēģināt vēlreiz." }, + "openInNewTab": { + "message": "Atvērt jaunā cilnē" + }, "invalidVerificationCode": { "message": "Nederīgs apstiprinājuma kods" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Atcerēties mani" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Šajā ierīcē 30 dienas vairs nevaicāt" + }, "sendVerificationCodeEmailAgain": { "message": "Sūtīt apstiprinājuma koda e-pastu vēlreiz" }, "useAnotherTwoStepMethod": { "message": "Izmantot citu divpakāpju pieteikšanās veidu" }, + "selectAnotherMethod": { + "message": "Atlasīt citu veidu", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Izmantot savu atkopes kodu" + }, "insertYubiKey": { "message": "Ievieto savu YubiKey datora USB ligzdā un pieskaries tā pogai!" }, @@ -871,6 +906,15 @@ "message": "Apliecināšana ar savas apvienības Duo Security, izmantojot Duo Mobile lietotni, īsziņu, tālruņa zvanu vai U2F drošības atslēgu.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Apliecināt savu identitāti" + }, + "weDontRecognizeThisDevice": { + "message": "Mēs neatpazīstam šo ierīci. Jāievada kods, kas tika nosūtīts e-pastā, lai apliecinātu savu identitāti." + }, + "continueLoggingIn": { + "message": "Turpināt pieteikšanos" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Divpakāpju pieteikšanās iespējas" }, + "selectTwoStepLoginMethod": { + "message": "Atlasīt divpakāpju pieteikšanās veidu" + }, "selfHostedEnvironment": { "message": "Pašuzturēta vide" }, - "selfHostedEnvironmentFooter": { - "message": "Norādīt pašuzstādīta Bitwarden pamata URL." - }, "selfHostedBaseUrlHint": { "message": "Jānorāda sava pašizvietotā Bitward servera pamata URL. Piemērs: https://bitwarden.uznemums.lv" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Pielāgota vide" }, - "customEnvironmentFooter": { - "message": "Pieredzējušiem lietotājiem. Ir iespējams norādīt URL katram pakalpojumam atsevišķi." - }, "baseUrl": { "message": "Servera URL" }, @@ -956,6 +997,9 @@ "no": { "message": "Nē" }, + "location": { + "message": "Atrašanās vieta" + }, "overwritePassword": { "message": "Pārrakstīt paroli" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Iegādāties Premium" }, - "premiumPurchaseAlert": { - "message": "Premium dalību ir iespējams iegādāties bitwarden.com tīmekļa glabātavā. Vai tagad apmeklēt tīmekļvietni?" - }, "premiumPurchaseAlertV2": { "message": "Premium var iegādāties Bitwarden tīmekļa lietotnē sava konta iestatījumos." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Pieprasīt paroli vai PIN pēc lietotnes palaišanas" }, + "requirePasswordWithoutPinOnStart": { + "message": "Pieprasīt paroli pēc lietotnes palaišanas" + }, "recommendedForSecurity": { "message": "Ieteicams drošībai." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Autentificēt WebAuthn" }, + "readSecurityKey": { + "message": "Nolasīt drošības atslēgu" + }, + "awaitingSecurityKeyInteraction": { + "message": "Gaida mijiedarbību ar drošības atslēgu..." + }, "hideEmail": { "message": "Slēpt e-pasta adresi no saņēmējiem." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Izmantot uzstādīto domēna visu tverošo iesūtni." }, + "useThisEmail": { + "message": "Izmantot šo e-pasta adresi" + }, "random": { "message": "Nejauši" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ atteica pieprasījumu. Lūgums sazināties ar savu pakalpojma nodrošinātāju, lai iegūtu palīdzību.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ atteica pieprasījumu: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Neizdevās iegūt $SERVICENAME$ aizsegta e-pasta konta Id.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Pieteikties ar galveno paroli" }, - "loggingInAs": { - "message": "Piesakās kā" - }, "rememberEmail": { "message": "Atcerēties e-pasta adresi" }, - "notYou": { - "message": "Tas neesi Tu?" - }, "newAroundHere": { "message": "Jauns šeit?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Uzsākta pieteikšanās" }, + "logInRequestSent": { + "message": "Pieprasījums nosūtīts" + }, "notificationSentDevice": { "message": "Uz jūsu ierīci ir nosūtīts paziņojums." }, "aNotificationWasSentToYourDevice": { "message": "Uz ierīci tika nosūtīts paziņojums" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Lūgums pārliecināties, ka konts ir atslēgts un atpazīšanas vārdkopa ir tāda pati arī otrā ierīcē" + "notificationSentDevicePart1": { + "message": "Bitwarden jāatslēdz savā ierīcē vai " + }, + "notificationSentDeviceAnchor": { + "message": "tīmekļa lietotnē" + }, + "notificationSentDevicePart2": { + "message": "Pirms apstiprināšanas jāpārliecinās, ka pirkstu nospieduma vārdkopa atbilst zemāk esošajai." }, "needAnotherOptionV1": { "message": "Nepieciešama cita iespēja?" @@ -2759,11 +2839,11 @@ "message": "Pārslēgt rakstzīmju skaita attēlošanu", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Vai mēģini pieteikties?" + "areYouTryingToAccessYourAccount": { + "message": "Vai mēģini piekļūt savam kontam?" }, - "logInAttemptBy": { - "message": "$EMAIL$ pieteikšanās mēģinājums", + "accessAttemptBy": { + "message": "$EMAIL$ piekļuves mēģinājums", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Laiks" }, - "confirmLogIn": { - "message": "Apstiprināt pieteikšanos" + "confirmAccess": { + "message": "Apstiprināt piekļuvi" }, - "denyLogIn": { - "message": "Atteikt pieteikšanos" + "denyAccess": { + "message": "Noraidīt piekļuvi" }, "logInConfirmedForEmailOnDevice": { "message": "$EMAIL$ pieteikšanās apstiprināta ierīcē $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "Šis pieprasījums vairs nav derīgs." }, - "confirmLoginAtemptForMail": { - "message": "Apstiprināt $EMAIL$ pieteikšanās mēģinājumu", + "confirmAccessAttempt": { + "message": "Apstiprināt $EMAIL$ piekļuves mēģinājumu", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Pieprasīta pieteikšanās" }, + "accountAccessRequested": { + "message": "Pieprasīta piekļuve kontam" + }, "creatingAccountOn": { "message": "Tiek veidots konts" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Noteikta vāja parole, un tā ir atrasta datu noplūdē. Jāizmanto spēcīga un neatkārtojama parole, lai aizsargātu savu kontu. Vai tiešām izmantot šo paroli?" }, + "useThisPassword": { + "message": "Izmantot šo paroli" + }, + "useThisUsername": { + "message": "Izmantot šo lietotājvārdu" + }, "checkForBreaches": { "message": "Meklēt šo paroli zināmās datu noplūdēs" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Pieprasīt pārvaldītāja apstiprinājumu" }, - "approveWithMasterPassword": { - "message": "Apstiprināt ar galveno paroli" - }, "region": { "message": "Apgabals" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Pieprasījums tika nosūtīts pārvaldītājam." }, - "youWillBeNotifiedOnceApproved": { - "message": "Tiks saņemts paziņojums, tiklīdz būs apstiprināts." - }, "troubleLoggingIn": { "message": "Neizdodas pieteikties?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Kontam ir nepieciešama Duo divpakāpju pieteikšanās." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Ir nepieciešama Duo divpakāpju pieteikšanās, lai pieteiktos savā kontā. Jāseko zemāk esošajām norādēm, lai pabeigtu pieteikšanos." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Jāseko zemāk esošajām norādēm, lai pabeigtu pieteikšanos." + }, "launchDuo": { "message": "Palaist Duo pārlūkā" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "Netika atrasti brīvi vienotās (SSO) pieteikšanās porti." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Atslēgšana ar biometriju nav pieejama, jo vispirms ir nepieciešama atslēgšana ar PIN vai paroli." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Atslēgšana ar biometriju pašlaik nav pieejama." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Atslēgšana ar biometriju nav pieejama nepareizi konfigurētu sistēmas datņu dēļ." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Atslēgšana ar biometriju nav pieejama nepareizi konfigurētu sistēmas datņu dēļ." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Atslēgšana ar biometriju nav pieejama, jo tā nav iespējota $EMAIL$ Bitwarden darbvirsmas lietotnē.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Atslēgšana ar biometriju pašlaik nav pieejama nezināma iemesla dēļ." + }, "authorize": { "message": "Pilnvarot" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Apstiprināt SSH atslēgas lietojumu" }, + "agentForwardingWarningTitle": { + "message": "Brīdinājums: aģenta pārsūtīšana" + }, + "agentForwardingWarningText": { + "message": "Šis pieprasījums nāk no attālas ierīces, kurā esi pieteicies" + }, "sshkeyApprovalMessageInfix": { "message": "pieprasa piekļuvi" }, + "sshkeyApprovalMessageSuffix": { + "message": ", lai varētu" + }, + "sshActionLogin": { + "message": "autentificēt serverī" + }, + "sshActionSign": { + "message": "parakstīt ziņojumu" + }, + "sshActionGitSign": { + "message": "parakstīt git iesūtījumu" + }, "unknownApplication": { "message": "Lietotne" }, - "sshKeyPasswordUnsupported": { - "message": "Ar paroli aizsargātu SSH atslēgu ievietošana pagaidām netiek nodrošināta" - }, "invalidSshKey": { "message": "SSH atslēga ir nederīga" }, @@ -3389,8 +3517,8 @@ "importSshKeyFromClipboard": { "message": "Ievietot atslēgu no starpliktuves" }, - "sshKeyPasted": { - "message": "SSH atslēga tika veiksmīgi ievietota" + "sshKeyImported": { + "message": "SSH atslēga tika sekmīgi ievietota" }, "fileSavedToDevice": { "message": "Datne saglabāta ierīcē. Tā ir atrodama ierīces lejupielāžu mapē." @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Mainīt konta e-pasta adresi" + }, + "allowScreenshots": { + "message": "Atļaut ekrāna tveršanu" + }, + "allowScreenshotsDesc": { + "message": "Ļaut Bitwarden darbvirsmas lietotni tvert ekrānuzņēmumos un rādīt attālās darbvirsmas sesijās. Atspējošana liegs piekļuvu atsevišķos ārējos ekrānos." + }, + "confirmWindowStillVisibleTitle": { + "message": "Apstirpināt, ka logs joprojām ir redzams" + }, + "confirmWindowStillVisibleContent": { + "message": "Lūgums apstiprināt, ka logs joprojām ir redzams." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Nepieciešama paplašinājuma atjaunināšana" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "Izmantotais pārlūka paplašinājums ir novecojis. Lūgums atjaunināt to vai atspējot pārlūka sasaistīšanas pirkstu nospieduma pārbaudi darbvirsmas lietotnes iestatījumos." + }, + "changeAtRiskPassword": { + "message": "Mainīt riskam pakļautu paroli" } } diff --git a/apps/desktop/src/locales/me/messages.json b/apps/desktop/src/locales/me/messages.json index 49bf2c60f6c..ea8defdc07c 100644 --- a/apps/desktop/src/locales/me/messages.json +++ b/apps/desktop/src/locales/me/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Greška" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januar" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Broj riječi" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Master password hint" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Podsjetnik na lozinku" - }, - "enterEmailToGetHint": { - "message": "Unesi email svog naloga kako bi ste primili podsjetnik na glavnu lozinku." - }, "getMasterPasswordHint": { "message": "Podsjetnik na glavnu lozinku" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Zapamti me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Pošalji ponovo verifikacioni kod na email" }, "useAnotherTwoStepMethod": { "message": "Koristi drugi metod prijave u dva koraka" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Priključi svoj YubiKey u USB port na kompjuteru i onda takni njegovo dugme." }, @@ -871,6 +906,15 @@ "message": "Potvrdite sa Duo Security za svoju organizaciju pomoću aplikacije Duo Mobile, SMS-a, telefonskog poziva ili U2F sigurnosnog ključa.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Opcije prijave u dva koraka" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Okruženje lokalne instalacije (SELF HOST)" }, - "selfHostedEnvironmentFooter": { - "message": "Navedite osnovni URL vaše lokalne instalacije (HOST) Bitwardena." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Prilagođeno okruženje" }, - "customEnvironmentFooter": { - "message": "Za napredne korisnike. Možete odrediti osnovni URL svake usluge nezavisno." - }, "baseUrl": { "message": "Server URL" }, @@ -956,6 +997,9 @@ "no": { "message": "Ne" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Zamijeni lozinku" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Kupi Premijum članstvo" }, - "premiumPurchaseAlert": { - "message": "Premium članstvo možete kupiti u trezoru na internet strani bitwarden.com. Da li želite da posjetite internet lokaciju sada?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Require password or PIN on app start" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Recommended for security." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Hide my email address from recipients." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, "rememberEmail": { "message": "Remember email" }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "Toggle character count", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Are you trying to log in?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Login attempt by $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Time" }, - "confirmLogIn": { - "message": "Confirm login" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Deny login" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Login confirmed for $EMAIL$ on $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "confirmLoginAtemptForMail": { - "message": "Confirm login attempt for $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Log in requested" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "region": { "message": "Region" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/ml/messages.json b/apps/desktop/src/locales/ml/messages.json index d314ee44578..a4af9251191 100644 --- a/apps/desktop/src/locales/ml/messages.json +++ b/apps/desktop/src/locales/ml/messages.json @@ -249,6 +249,20 @@ "error": { "message": "പിശക്" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "ജനുവരി" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "വാക്കുകളുടെ എണ്ണം" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Master password hint" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "പാസ്സ്‌വേഡ് സൂചനാ" - }, - "enterEmailToGetHint": { - "message": "നിങ്ങളുടെ പ്രാഥമിക പാസ്‌വേഡ് സൂചന ലഭിക്കുന്നതിന് നിങ്ങളുടെ അക്കൗണ്ട് ഇമെയിൽ വിലാസം നൽകുക." - }, "getMasterPasswordHint": { "message": "പ്രാഥമിക പാസ്‌വേഡ് സൂചന നേടുക" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "എന്നെ ഓർക്കണം" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "സ്ഥിരീകരണ കോഡ് ഇമെയിൽ വഴി വീണ്ടും അയയ്ക്കുക" }, "useAnotherTwoStepMethod": { "message": "മറ്റൊരു രണ്ട് ഘട്ട പ്രവേശന രീതി ഉപയോഗിക്കുക" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "നിങ്ങളുടെ കമ്പ്യൂട്ടറിന്റെ യു‌എസ്‌ബി പോർട്ടിലേക്ക് YubiKey ഇടുക, തുടർന്ന് അതിന്റെ ബട്ടൺ അമർത്തുക." }, @@ -871,6 +906,15 @@ "message": "Duo Mobile, SMS, ഫോൺ കോൾ അല്ലെങ്കിൽ U2F സുരക്ഷാ കീ ഉപയോഗിച്ച് നിങ്ങളുടെ ഓർഗനൈസേഷനെ Duo Security ഉപയോഗിച്ച് പരിശോധിക്കുക.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "രണ്ട്-ഘട്ട പ്രവേശനം ഓപ്ഷനുകൾ" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "സ്വയം ഹോസ്റ്റുചെയ്‌ത എൻവിയോണ്മെന്റ്" }, - "selfHostedEnvironmentFooter": { - "message": "നിങ്ങളുടെ പരിസരത്ത് ചെയ്യുന്ന Bitwarden ഇൻസ്റ്റാളേഷന്റെ അടിസ്ഥാന URL വ്യക്തമാക്കുക." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "ഇഷ്‌ടാനുസൃത എൻവിയോണ്മെന്റ്" }, - "customEnvironmentFooter": { - "message": "വിപുലമായ ഉപയോക്താക്കൾക്കായി. ഓരോ സേവനത്തിന്റെയും അടിസ്ഥാന URL നിങ്ങൾക്ക് സ്വതന്ത്രമായി വ്യക്തമാക്കാൻ കഴിയും." - }, "baseUrl": { "message": "സെർവർ യു ർ ൽ" }, @@ -956,6 +997,9 @@ "no": { "message": "അല്ല" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "പാസ്‌വേഡ് പുനരാലേഖനം ചെയ്യുക" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "പ്രീമിയം വാങ്ങുക" }, - "premiumPurchaseAlert": { - "message": "നിങ്ങൾക്ക് bitwarden.com വെബ് വാൾട്ടിൽ പ്രീമിയം അംഗത്വം വാങ്ങാം. നിങ്ങൾക്ക് ഇപ്പോൾ വെബ്സൈറ്റ് സന്ദർശിക്കാൻ ആഗ്രഹമുണ്ടോ?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Require password or PIN on app start" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Recommended for security." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Hide my email address from recipients." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, "rememberEmail": { "message": "Remember email" }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "Toggle character count", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Are you trying to log in?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Login attempt by $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Time" }, - "confirmLogIn": { - "message": "Confirm login" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Deny login" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Login confirmed for $EMAIL$ on $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "confirmLoginAtemptForMail": { - "message": "Confirm login attempt for $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Log in requested" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "region": { "message": "Region" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/mr/messages.json b/apps/desktop/src/locales/mr/messages.json index 323d0cd3f7b..f93db44aa69 100644 --- a/apps/desktop/src/locales/mr/messages.json +++ b/apps/desktop/src/locales/mr/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Number of words" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Master password hint" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -871,6 +906,15 @@ "message": "Verify with Duo Security for your organization using the Duo Mobile app, SMS, phone call, or U2F security key.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Self-hosted environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Custom environment" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "Server URL" }, @@ -956,6 +997,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Overwrite password" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Purchase Premium" }, - "premiumPurchaseAlert": { - "message": "You can purchase premium membership on the bitwarden.com web vault. Do you want to visit the website now?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Require password or PIN on app start" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Recommended for security." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Hide my email address from recipients." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, "rememberEmail": { "message": "Remember email" }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "Toggle character count", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Are you trying to log in?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Login attempt by $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Time" }, - "confirmLogIn": { - "message": "Confirm login" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Deny login" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Login confirmed for $EMAIL$ on $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "confirmLoginAtemptForMail": { - "message": "Confirm login attempt for $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Log in requested" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "region": { "message": "Region" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/my/messages.json b/apps/desktop/src/locales/my/messages.json index c1713e0bb32..493d6fdb5b9 100644 --- a/apps/desktop/src/locales/my/messages.json +++ b/apps/desktop/src/locales/my/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Number of words" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Master password hint" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -871,6 +906,15 @@ "message": "Verify with Duo Security for your organization using the Duo Mobile app, SMS, phone call, or U2F security key.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Self-hosted environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Custom environment" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "Server URL" }, @@ -956,6 +997,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Overwrite password" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Purchase Premium" }, - "premiumPurchaseAlert": { - "message": "You can purchase premium membership on the bitwarden.com web vault. Do you want to visit the website now?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Require password or PIN on app start" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Recommended for security." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Hide my email address from recipients." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, "rememberEmail": { "message": "Remember email" }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "Toggle character count", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Are you trying to log in?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Login attempt by $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Time" }, - "confirmLogIn": { - "message": "Confirm login" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Deny login" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Login confirmed for $EMAIL$ on $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "confirmLoginAtemptForMail": { - "message": "Confirm login attempt for $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Log in requested" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "region": { "message": "Region" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/nb/messages.json b/apps/desktop/src/locales/nb/messages.json index f264bb5f092..caf4d3da64b 100644 --- a/apps/desktop/src/locales/nb/messages.json +++ b/apps/desktop/src/locales/nb/messages.json @@ -27,7 +27,7 @@ "message": "Sikker notis" }, "typeSshKey": { - "message": "SSH key" + "message": "SSH-nøkkel" }, "folders": { "message": "Mapper" @@ -64,7 +64,7 @@ } }, "welcomeBack": { - "message": "Welcome back" + "message": "Velkommen tilbake" }, "moveToOrgDesc": { "message": "Velg en organisasjon som du ønsker å flyttet elementet til. Ved å flytte til en organisasjon, overfører du eierskapet til elementet til den organisasjonen. Du vil ikke lenger være den direkte eieren av elementet etter at den har blitt flyttet." @@ -181,16 +181,16 @@ "message": "Adresse" }, "sshPrivateKey": { - "message": "Private key" + "message": "Privat nøkkel" }, "sshPublicKey": { - "message": "Public key" + "message": "Offentlig nøkkel" }, "sshFingerprint": { - "message": "Fingerprint" + "message": "Fingeravtrykk" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "Nøkkeltype" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -208,19 +208,19 @@ "message": "A new SSH key was generated" }, "sshKeyWrongPassword": { - "message": "The password you entered is incorrect." + "message": "Passordet du skrev inn er feil." }, "importSshKey": { - "message": "Import" + "message": "Importer" }, "confirmSshKeyPassword": { - "message": "Confirm password" + "message": "Bekreft passordet" }, "enterSshKeyPasswordDesc": { "message": "Enter the password for the SSH key." }, "enterSshKeyPassword": { - "message": "Enter password" + "message": "Skriv inn passord" }, "sshAgentUnlockRequired": { "message": "Please unlock your vault to approve the SSH key request." @@ -229,7 +229,7 @@ "message": "SSH key request timed out." }, "enableSshAgent": { - "message": "Enable SSH agent" + "message": "Skru på SSH-agent" }, "enableSshAgentDesc": { "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." @@ -249,6 +249,20 @@ "error": { "message": "Feil" }, + "decryptionError": { + "message": "Dekrypteringsfeil" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "for å unngå ytterligere datatap.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januar" }, @@ -323,7 +337,7 @@ "message": "Generer et passord" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "Generér passordfrase" }, "type": { "message": "Type" @@ -461,7 +475,7 @@ "message": "Kopier passordet" }, "regenerateSshKey": { - "message": "Regenerate SSH key" + "message": "Generér SSH-nøkkel på nytt" }, "copySshPrivateKey": { "message": "Copy SSH private key" @@ -498,19 +512,19 @@ "message": "Spesialtegn (!@#$%^&*)" }, "include": { - "message": "Include", + "message": "Inkluder", "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "Inkluder store bokstaver", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { - "message": "A-Z", + "message": "A-Å", "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "Inkluder små bokstaver", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -518,7 +532,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "Inkluder tall", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -526,13 +540,9 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "Inkluder spesialtegn", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Antall ord" }, @@ -561,7 +571,7 @@ "description": "deprecated. Use avoidAmbiguous instead." }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "Unngå forvekslingsbare tegn", "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { @@ -624,7 +634,7 @@ "message": "Opprett en konto" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Er du ny til Bitwarden?" }, "setAStrongPassword": { "message": "Velg et sterkt passord" @@ -636,13 +646,22 @@ "message": "Logg på" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Logg inn på Bitwarden" + }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Logg inn med passnøkkel" }, "loginWithDevice": { - "message": "Log in with device" + "message": "Logg på med enhet" }, "useSingleSignOn": { "message": "Use single sign-on" @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Hint til hovedpassord" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Bli med i organisasjon" }, @@ -709,23 +737,17 @@ "message": "Innstillinger" }, "accountEmail": { - "message": "Account email" + "message": "Kontoens E-postadresse" }, "requestHint": { "message": "Be om hint" }, "requestPasswordHint": { - "message": "Request password hint" + "message": "Be om passordhint" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Passordhint" - }, - "enterEmailToGetHint": { - "message": "Skriv inn din kontos E-postadresse for å motta hintet til ditt superpassord." - }, "getMasterPasswordHint": { "message": "Få et hint om superpassordet" }, @@ -752,7 +774,7 @@ } }, "youSuccessfullyLoggedIn": { - "message": "You successfully logged in" + "message": "Du har vellykket logget inn" }, "youMayCloseThisWindow": { "message": "Du kan lukke dette vinduet" @@ -764,10 +786,10 @@ "message": "Din nye konto har blitt opprettet! Du kan nå logge på." }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "Den nye kontoen din er opprettet!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "Du har blitt logget inn!" }, "masterPassSent": { "message": "Vi har sendt deg en E-post med hintet til superpassordet." @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "Autentiseringen ble avbrutt eller tok for lang tid. Prøv igjen." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Ugyldig verifiseringskode" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Husk på meg" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send E-posten med verifiseringskoden på nytt" }, "useAnotherTwoStepMethod": { "message": "Bruk en annen 2-trinnsinnloggingsmetode" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Sett inn din YubiKey i din datamaskins USB-uttak, og så trykk på dens knapp." }, @@ -871,6 +906,15 @@ "message": "Verifiser med Duo Security for din organisasjon gjennom Duo Mobile-appen, SMS, telefonsamtale, eller en U2F-sikkerhetsnøkkel.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Fortsett innloggingen" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -881,7 +925,7 @@ "message": "E-post" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "Skriv inn koden du har fått tilsendt på E-post." }, "loginUnavailable": { "message": "Innloggingen er utilgjengelig" @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Totrinns innloggingsalternativer" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Selvbetjent miljø" }, - "selfHostedEnvironmentFooter": { - "message": "Spesifiser grunn-nettadressen til din selvbetjente Bitwarden-installasjon." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Tilpasset miljø" }, - "customEnvironmentFooter": { - "message": "For avanserte brukere. Du kan bestemme grunn-nettadressen til hver tjeneste separat." - }, "baseUrl": { "message": "Tjener-nettadresse" }, @@ -956,6 +997,9 @@ "no": { "message": "Nei" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Overskriv passordet" }, @@ -969,7 +1013,7 @@ "message": "Logget av" }, "loggedOutDesc": { - "message": "You have been logged out of your account." + "message": "Du har blitt logget ut av kontoen din." }, "loginExpired": { "message": "Din innloggingsøkt har utløpt." @@ -984,7 +1028,7 @@ "message": "Please restart registration or try logging in." }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "Du har kanskje allerede en konto" }, "logOutConfirmation": { "message": "Er du sikker på at du vil logge av?" @@ -1041,10 +1085,10 @@ "message": "Endre hovedpassordet" }, "continueToWebApp": { - "message": "Continue to web app?" + "message": "Vil du fortsette til nettappen?" }, "changeMasterPasswordOnWebConfirmation": { - "message": "You can change your master password on the Bitwarden web app." + "message": "Du kan endre hovedpassordet ditt i Bitwardens nettapp." }, "fingerprintPhrase": { "message": "Fingeravtrykksfrase", @@ -1073,16 +1117,16 @@ "message": "Hvelvet ditt er låst. Bekreft hovedpassordet ditt for å fortsette." }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "Kontoen din er låst" }, "or": { - "message": "or" + "message": "eller" }, "unlockWithBiometrics": { - "message": "Unlock with biometrics" + "message": "Lås opp med biometri" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "Lås opp med hovedpassord" }, "unlock": { "message": "Lås opp" @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Kjøp Premium" }, - "premiumPurchaseAlert": { - "message": "Du kan kjøpe et Premium-medlemskap på bitwarden.net-netthvelvet. Vil du besøke det nettstedet nå?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1393,10 +1434,10 @@ "message": "Passordhistorikk" }, "generatorHistory": { - "message": "Generator history" + "message": "Generatorhistorikk" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "Tøm generatorhistorikk" }, "cleargGeneratorHistoryDescription": { "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" @@ -1409,13 +1450,13 @@ "message": "Det er ingen passord å liste opp." }, "clearHistory": { - "message": "Clear history" + "message": "Tøm historikk" }, "nothingToShow": { - "message": "Nothing to show" + "message": "Ingenting å vise" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "Du har ikke generert noe i det siste" }, "undo": { "message": "Angre" @@ -1492,7 +1533,7 @@ } }, "copySuccessful": { - "message": "Copy Successful" + "message": "Kopiering lyktes" }, "errorRefreshingAccessToken": { "message": "Access Token Refresh Error" @@ -1615,7 +1656,7 @@ "message": "Set a file password to encrypt the export and import it to any Bitwarden account using the password for decryption." }, "exportTypeHeading": { - "message": "Export type" + "message": "Eksporttype" }, "accountRestricted": { "message": "Konto begrenset" @@ -1729,7 +1770,7 @@ "message": "Ytterligere Windows Hello-innstillinger" }, "unlockWithPolkit": { - "message": "Unlock with system authentication" + "message": "Lås opp med systemautentisering" }, "windowsHelloConsentMessage": { "message": "Bekreft for Bitwarden." @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Krev passord eller PIN-kode når appen startes" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Anbefalt for sikkerhet." }, @@ -1887,7 +1931,7 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "av $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -1954,16 +1998,16 @@ "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { - "message": "Unsubscribe" + "message": "Avabonner" }, "atAnyTime": { - "message": "at any time." + "message": "når som helst." }, "byContinuingYouAgreeToThe": { - "message": "By continuing, you agree to the" + "message": "Ved å fortsette, samtykker du til" }, "and": { - "message": "and" + "message": "og" }, "acceptPolicies": { "message": "Ved å huke av i denne boksen sier du deg enig i følgende:" @@ -2008,7 +2052,7 @@ "message": "Aktiver et ekstra lag sikkerhet ved å kreve fingeravtrykksvalidering når du oppretter en kobling mellom skrivebordet og nettleseren. Når dette er aktivert kreves det brukerintervensjon og verifisering hver gang en tilkobling etableres." }, "enableHardwareAcceleration": { - "message": "Use hardware acceleration" + "message": "Bruk maskinvareakselerering" }, "enableHardwareAccelerationDesc": { "message": "By default this setting is ON. Turn OFF only if you experience graphical issues. Restart is required." @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Autentiser WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Skjul min e-postadresse fra mottakere." }, @@ -2420,7 +2470,7 @@ "message": "Bytt konto" }, "alreadyHaveAccount": { - "message": "Already have an account?" + "message": "Har du allerede en konto?" }, "options": { "message": "Alternativer" @@ -2456,7 +2506,7 @@ "message": "Låst" }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "Hvelvet ditt er låst" }, "unlocked": { "message": "Opplåst" @@ -2478,10 +2528,10 @@ "message": "Generer brukernavn" }, "generateEmail": { - "message": "Generate email" + "message": "Generér E-post" }, "spinboxBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$.", + "message": "Verdien må være mellom $MIN$ og $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2495,7 +2545,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "message": " Bruk minst $RECOMMENDED$ tegn for å generere et sterkt passord.", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Bruk domenets konfigurerte catch-all innboks." }, + "useThisEmail": { + "message": "Bruk denne E-postadressen" + }, "random": { "message": "Tilfeldig" }, @@ -2558,11 +2611,11 @@ "message": "Generer et e-postalias med en ekstern videresendingstjeneste." }, "forwarderDomainName": { - "message": "Email domain", + "message": "E-postdomene", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Choose a domain that is supported by the selected service", + "message": "Velg et domene som støttes av den valgte tjenesten", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { @@ -2580,11 +2633,11 @@ } }, "forwarderGeneratedBy": { - "message": "Generated by Bitwarden.", + "message": "Generert av Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "Nettsted: $WEBSITE$. Generert av Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -2594,7 +2647,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "Ugyldig $SERVICENAME$-API-sjetong", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -2604,7 +2657,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "Ugyldig $SERVICENAME$-API-sjetong: $ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2628,7 +2705,7 @@ } }, "forwarderNoDomain": { - "message": "Invalid $SERVICENAME$ domain.", + "message": "Ugyldig $SERVICENAME$-domene.", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -2648,7 +2725,7 @@ } }, "forwarderUnknownError": { - "message": "Unknown $SERVICENAME$ error occurred.", + "message": "Ukjent $SERVICENAME$-feil oppstod.", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Logg inn med hovedpassord" }, - "loggingInAs": { - "message": "Logger inn som" - }, "rememberEmail": { "message": "Husk på E-postadressen" }, - "notYou": { - "message": "Ikke du?" - }, "newAroundHere": { "message": "Ny rundt her?" }, @@ -2720,19 +2791,28 @@ "message": "Logg inn med en annen enhet" }, "loginInitiated": { - "message": "Login initiated" + "message": "Innlogging igangsatt" + }, + "logInRequestSent": { + "message": "Forespørsel sendt" }, "notificationSentDevice": { "message": "Et varsel har blitt sendt til enheten din." }, "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "message": "Et varsel ble sendt til enheten din" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "nett-app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "Trenger du et annet alternativ?" }, "fingerprintMatchInfo": { "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." @@ -2747,7 +2827,7 @@ "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, "viewAllLogInOptions": { - "message": "View all log in options" + "message": "Vis alle påloggingsalternativer" }, "viewAllLoginOptions": { "message": "Vis alle påloggingsalternativer" @@ -2759,11 +2839,11 @@ "message": "Toggle character count", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Prøver du å logge på?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Påloggingsforsøk av $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Tid" }, - "confirmLogIn": { - "message": "Bekreft innlogging" + "confirmAccess": { + "message": "Bekreft tilgang" }, - "denyLogIn": { - "message": "Avslå innlogging" + "denyAccess": { + "message": "Nekt tilgang" }, "logInConfirmedForEmailOnDevice": { "message": "Login confirmed for $EMAIL$ on $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "Denne forespørselen er ikke lenger gyldig." }, - "confirmLoginAtemptForMail": { - "message": "Bekreft påloggingsforsøket til $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,14 +2912,17 @@ "logInRequested": { "message": "Log in requested" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { - "message": "Creating account on" + "message": "Oppretter en konto på" }, "checkYourEmail": { - "message": "Check your email" + "message": "Sjekk E-postinnboksen din" }, "followTheLinkInTheEmailSentTo": { - "message": "Follow the link in the email sent to" + "message": "Følg lenken i E-postadressen som ble sendt til" }, "andContinueCreatingYourAccount": { "message": "and continue creating your account." @@ -2851,7 +2934,7 @@ "message": "Gå tilbake" }, "toEditYourEmailAddress": { - "message": "to edit your email address." + "message": "for å redigere E-postadressen din." }, "exposedMasterPassword": { "message": "Eksponert hovedpassord" @@ -2865,17 +2948,23 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Bruk dette passordet" + }, + "useThisUsername": { + "message": "Bruk dette brukernavnet" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, "loggedInExclamation": { - "message": "Logged in!" + "message": "Innlogget!" }, "important": { "message": "Viktig:" }, "accessing": { - "message": "Accessing" + "message": "Logger inn på" }, "accessTokenUnableToBeDecrypted": { "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." @@ -2908,7 +2997,7 @@ "message": "Device approval required. Select an approval option below:" }, "deviceApprovalRequiredV2": { - "message": "Device approval required" + "message": "Enhetsgodkjennelse kreves" }, "selectAnApprovalOptionBelow": { "message": "Select an approval option below" @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Be om administratorgodkjennelse" }, - "approveWithMasterPassword": { - "message": "Godkjenn med hovedpassord" - }, "region": { "message": "Region" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Forespørselen din har blitt sendt til administratoren din." }, - "youWillBeNotifiedOnceApproved": { - "message": "Du vil bli varslet når det er godkjent." - }, "troubleLoggingIn": { "message": "Problemer med å logge inn?" }, @@ -2969,7 +3052,7 @@ "message": "Active user email not found. Logging you out." }, "deviceTrusted": { - "message": "Device trusted" + "message": "Enheten er betrodd" }, "inputRequired": { "message": "Inndata er påkrevd." @@ -3078,10 +3161,10 @@ "message": "Hopp frem til innholdet" }, "typePasskey": { - "message": "Passkey" + "message": "Passnøkkel" }, "passkeyNotCopied": { - "message": "Passkey will not be copied" + "message": "Passkoden vil ikke bli kopiert" }, "passkeyNotCopiedAlert": { "message": "The passkey will not be copied to the cloned item. Do you want to continue cloning this item?" @@ -3094,7 +3177,7 @@ "description": "Used for the desktop menu item and the header of the import dialog" }, "importError": { - "message": "Import error" + "message": "Importeringsfeil" }, "importErrorDesc": { "message": "There was a problem with the data you tried to import. Please resolve the errors listed below in your source file and try again." @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3154,7 +3243,7 @@ "message": "Invalid file password, please use the password you entered when you created the export file." }, "destination": { - "message": "Destination" + "message": "Destinasjon" }, "learnAboutImportOptions": { "message": "Lær mer om importalternativene dine" @@ -3213,7 +3302,7 @@ "message": "Bekreft filpassord" }, "exportSuccess": { - "message": "Vault data exported" + "message": "Hvelvdataen ble eksportert" }, "multifactorAuthenticationCancelled": { "message": "Multifaktorautentisering ble avbrutt" @@ -3255,7 +3344,7 @@ "message": "Approve the login request in your authentication app or enter a one-time passcode." }, "passcode": { - "message": "Passcode" + "message": "Passkode" }, "lastPassMasterPassword": { "message": "LastPass-hovedpassord" @@ -3293,10 +3382,10 @@ "description": "Label indicating the most common import formats" }, "success": { - "message": "Success" + "message": "Suksess" }, "troubleshooting": { - "message": "Troubleshooting" + "message": "Feilsøking" }, "disableHardwareAccelerationRestart": { "message": "Disable hardware acceleration and restart" @@ -3305,7 +3394,7 @@ "message": "Enable hardware acceleration and restart" }, "removePasskey": { - "message": "Remove passkey" + "message": "Fjern passordnøkkel" }, "passkeyRemoved": { "message": "Passkey removed" @@ -3317,7 +3406,7 @@ "message": "Error assigning target folder." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Vis gjenstander i $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -3327,7 +3416,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Tilbake til $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -3337,11 +3426,11 @@ } }, "back": { - "message": "Back", + "message": "Tilbake", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Fjern $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -3362,26 +3451,65 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrisk opplåsing er utilgjengelig for øyeblikket." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { - "message": "Authorize" + "message": "Autoriser" }, "deny": { - "message": "Deny" + "message": "Avvis" }, "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, - "unknownApplication": { - "message": "An application" + "sshkeyApprovalMessageSuffix": { + "message": "in order to" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, + "unknownApplication": { + "message": "Et program" }, "invalidSshKey": { - "message": "The SSH key is invalid" + "message": "SSH-nøkkelen er ugyldig" }, "sshKeyTypeUnsupported": { "message": "The SSH key type is not supported" @@ -3389,17 +3517,17 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, "importantNotice": { - "message": "Important notice" + "message": "Viktig melding" }, "setupTwoStepLogin": { - "message": "Set up two-step login" + "message": "Sett opp 2-trinnspålogging" }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." @@ -3408,7 +3536,7 @@ "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." }, "remindMeLater": { - "message": "Remind me later" + "message": "Minn meg på det senere" }, "newDeviceVerificationNoticePageOneFormContent": { "message": "Do you have reliable access to your email, $EMAIL$?", @@ -3420,15 +3548,36 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "Nei, det gjør jeg ikke" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { "message": "Yes, I can reliably access my email" }, "turnOnTwoStepLogin": { - "message": "Turn on two-step login" + "message": "Slå på 2-trinnsinnlogging" }, "changeAcctEmail": { - "message": "Change account email" + "message": "Endre kontoens E-postadresse" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/ne/messages.json b/apps/desktop/src/locales/ne/messages.json index fb8d7ec7df5..297708953df 100644 --- a/apps/desktop/src/locales/ne/messages.json +++ b/apps/desktop/src/locales/ne/messages.json @@ -249,6 +249,20 @@ "error": { "message": "त्रुटि" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "जनवरी" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "शब्द संख्या" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Master password hint" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -871,6 +906,15 @@ "message": "Verify with Duo Security for your organization using the Duo Mobile app, SMS, phone call, or U2F security key.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Self-hosted environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Custom environment" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "Server URL" }, @@ -956,6 +997,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Overwrite password" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Purchase Premium" }, - "premiumPurchaseAlert": { - "message": "You can purchase premium membership on the bitwarden.com web vault. Do you want to visit the website now?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Require password or PIN on app start" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Recommended for security." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Hide my email address from recipients." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, "rememberEmail": { "message": "Remember email" }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "Toggle character count", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Are you trying to log in?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Login attempt by $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Time" }, - "confirmLogIn": { - "message": "Confirm login" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Deny login" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Login confirmed for $EMAIL$ on $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "confirmLoginAtemptForMail": { - "message": "Confirm login attempt for $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Log in requested" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "region": { "message": "Region" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json index a23ef820ab7..1cfd5ff5523 100644 --- a/apps/desktop/src/locales/nl/messages.json +++ b/apps/desktop/src/locales/nl/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Fout" }, + "decryptionError": { + "message": "Ontsleutelingsfout" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden kon de onderstaande kluisitem(s) niet ontsleutelen." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Neem contact op met de klantenservice", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "om extra dataverlies te voorkomen.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "januari" }, @@ -529,10 +543,6 @@ "message": "Speciale tekens toevoegen", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Aantal woorden" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Inloggen op Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Voer de code in die naar je e-mailadres is verstuurd" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Voer de code uit je authenticatie-app in" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Druk op je YubiKey om te verifiëren" + }, "logInWithPasskey": { "message": "Inloggen met passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Hoofdwachtwoordhint" }, + "passwordStrengthScore": { + "message": "Score wachtwoordsterkte $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organisatie" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Als je het e-mailadres van je account invult, versturen we je je wachtwoordhint" }, - "passwordHint": { - "message": "Wachtwoordhint" - }, - "enterEmailToGetHint": { - "message": "Voer het e-mailadres van je account in om je hoofdwachtwoordhint te ontvangen." - }, "getMasterPasswordHint": { "message": "Hoofdwachtwoordhint opvragen" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "De authenticatie werd geannuleerd of duurde te lang. Probeer het opnieuw." }, + "openInNewTab": { + "message": "Openen in nieuwe tab" + }, "invalidVerificationCode": { "message": "Ongeldige verificatiecode" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Mijn gegevens onthouden" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "30 dagen niet meer vragen op dit apparaat" + }, "sendVerificationCodeEmailAgain": { "message": "E-mail met verificatiecode opnieuw versturen" }, "useAnotherTwoStepMethod": { "message": "Gebruik een andere methode voor tweestapsaanmelding" }, + "selectAnotherMethod": { + "message": "Kies een andere methode", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Gebruik je herstelcode" + }, "insertYubiKey": { "message": "Plaats je YubiKey in de USB-poort van je computer en druk op de knop." }, @@ -871,6 +906,15 @@ "message": "Verificatie met Duo Security middels de Duo Mobile-app, sms, spraakoproep of een U2F-beveiligingssleutel.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verifieer je identiteit" + }, + "weDontRecognizeThisDevice": { + "message": "We herkennen dit apparaat niet. Voer de code in die naar je e-mail is verzonden om je identiteit te verifiëren." + }, + "continueLoggingIn": { + "message": "Doorgaan met inloggen" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Opties voor tweestapsaanmelding" }, + "selectTwoStepLoginMethod": { + "message": "Kies methode voor tweestapsaanmelding" + }, "selfHostedEnvironment": { "message": "Zelfgehoste omgeving" }, - "selfHostedEnvironmentFooter": { - "message": "Geef de basis-URL van jouw zelfgehoste Bitwarden-installatie." - }, "selfHostedBaseUrlHint": { "message": "Specificeer de basis-URL van je zelfgehoste Bitwarden-installatie. Bijvoorbeeld: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Aangepaste omgeving" }, - "customEnvironmentFooter": { - "message": "Voor gevorderde gebruikers. Je kunt de basis-URL van elke dienst afzonderlijk instellen." - }, "baseUrl": { "message": "Server-URL" }, @@ -956,6 +997,9 @@ "no": { "message": "Nee" }, + "location": { + "message": "Locatie" + }, "overwritePassword": { "message": "Wachtwoord overschrijven" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Premium aanschaffen" }, - "premiumPurchaseAlert": { - "message": "Je kunt een Premium-abonnement aanschaffen in de webkluis op bitwarden.com. Wil je de website nu bezoeken?" - }, "premiumPurchaseAlertV2": { "message": "Je kunt Premium via je accountinstellingen in de Bitwarden-webapp kopen." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Wachtwoord of pincode vereisen bij starten van de app" }, + "requirePasswordWithoutPinOnStart": { + "message": "Wachtwoord vereisen bij starten van de app" + }, "recommendedForSecurity": { "message": "Aanbevolen voor veiligheid." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Verifieer WebAuthn" }, + "readSecurityKey": { + "message": "Beveiligingssleutel lezen" + }, + "awaitingSecurityKeyInteraction": { + "message": "Wacht op interactie met beveiligingssleutel..." + }, "hideEmail": { "message": "Verberg mijn e-mailadres voor ontvangers." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Gebruik de catch-all inbox van je domein." }, + "useThisEmail": { + "message": "Dit e-mailadres gebruiken" + }, "random": { "message": "Willekeurig" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ heeft je aanvraag geweigerd. Neem contact op met je serviceprovider voor hulp.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ heeft je aanvraag geweigerd: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Kan $SERVICENAME$ gemaskerde e-mailaccount-ID niet verkrijgen.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Inloggen met je hoofdwachtwoord" }, - "loggingInAs": { - "message": "Inloggen als" - }, "rememberEmail": { "message": "E-mailadres onthouden" }, - "notYou": { - "message": "Ben jij dit niet?" - }, "newAroundHere": { "message": "Nieuw hier?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Inloggen gestart" }, + "logInRequestSent": { + "message": "Verzoek verzonden" + }, "notificationSentDevice": { "message": "Er is een melding naar je apparaat verzonden." }, "aNotificationWasSentToYourDevice": { "message": "Er is een melding naar je apparaat verzonden" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Zorg ervoor dat je account is ontgrendeld en dat de vingerafdrukzin overeenkomt met het andere apparaat" + "notificationSentDevicePart1": { + "message": "Ontgrendel Bitwarden op je apparaat of op de " + }, + "notificationSentDeviceAnchor": { + "message": "webapp" + }, + "notificationSentDevicePart2": { + "message": "Zorg ervoor dat de Vingerafdrukzin overeenkomt met de onderstaande voor je deze goedkeurt." }, "needAnotherOptionV1": { "message": "Nog een optie nodig?" @@ -2759,10 +2839,10 @@ "message": "Tekentelling in-/uitschakelen", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Probeer je in te loggen?" + "areYouTryingToAccessYourAccount": { + "message": "Probeer je toegang te krijgen tot je account?" }, - "logInAttemptBy": { + "accessAttemptBy": { "message": "Inlogpoging door $EMAIL$", "placeholders": { "email": { @@ -2780,11 +2860,11 @@ "time": { "message": "Tijd" }, - "confirmLogIn": { - "message": "Inloggen bevestigen" + "confirmAccess": { + "message": "Toegang bevestigen" }, - "denyLogIn": { - "message": "Inloggen afwijzen" + "denyAccess": { + "message": "Toegang weigeren" }, "logInConfirmedForEmailOnDevice": { "message": "Inloggen voor $EMAIL$ bevestigd op $DEVICE$", @@ -2820,7 +2900,7 @@ "thisRequestIsNoLongerValid": { "message": "Dit verzoek is niet langer geldig." }, - "confirmLoginAtemptForMail": { + "confirmAccessAttempt": { "message": "Inlogpoging voor $EMAIL$ bevestigen", "placeholders": { "email": { @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Inloggen verzocht" }, + "accountAccessRequested": { + "message": "Accounttoegang aangevraagd" + }, "creatingAccountOn": { "message": "Account maken bij" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Zwak wachtwoord geïdentificeerd en gevonden in een datalek. Gebruik een sterk en uniek wachtwoord om je account te beschermen. Weet je zeker dat je dit wachtwoord wilt gebruiken?" }, + "useThisPassword": { + "message": "Dit wachtwoord gebruiken" + }, + "useThisUsername": { + "message": "Deze gebruikersnaam gebruiken" + }, "checkForBreaches": { "message": "Bekende datalekken voor dit wachtwoord controleren" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Goedkeuring van beheerder vragen" }, - "approveWithMasterPassword": { - "message": "Goedkeuren met hoofdwachtwoord" - }, "region": { "message": "Regio" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Je verzoek is naar je beheerder verstuurd." }, - "youWillBeNotifiedOnceApproved": { - "message": "Je krijgt een melding zodra je bent goedgekeurd." - }, "troubleLoggingIn": { "message": "Problemen met inloggen?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Jouw account vereist Duo-tweestapsaanmelding." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Jouw account vereist Duo-tweestapsaanmelding. Volg de onderstaande stappen om het inloggen te voltooien." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Volg de onderstaande stappen om in te loggen." + }, "launchDuo": { "message": "Start Duo in browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "Er zijn geen vrije poorten gevonden voor de sso-login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrisch ontgrendelen is niet beschikbaar omdat pincode of wachtwoordontgrendeling eerst vereist is." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrisch ontgrendelen is momenteel niet beschikbaar." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrisch ontgrendelen is niet beschikbaar vanwege verkeerd geconfigureerde systeembestanden." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrisch ontgrendelen is niet beschikbaar vanwege verkeerd geconfigureerde systeembestanden." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometrisch ontgrendelen is niet beschikbaar omdat het niet is ingeschakeld voor $EMAIL$ in de Bitwarden-desktopapp.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrisch ontgrendelen is momenteel niet beschikbaar om een onbekende reden." + }, "authorize": { "message": "Autoriseren" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Gebruik SSH-sleutel bevestigen" }, + "agentForwardingWarningTitle": { + "message": "Waarschuwing: Agent doorsturen" + }, + "agentForwardingWarningText": { + "message": "Dit verzoek komt van een extern apparaat waarop je bent ingelogd" + }, "sshkeyApprovalMessageInfix": { "message": "vraagt toegang tot" }, + "sshkeyApprovalMessageSuffix": { + "message": "om te" + }, + "sshActionLogin": { + "message": "autenticeer naar een server" + }, + "sshActionSign": { + "message": "een bericht te ondertekenen" + }, + "sshActionGitSign": { + "message": "een git-commit te ondertekenen" + }, "unknownApplication": { "message": "Een applicatie" }, - "sshKeyPasswordUnsupported": { - "message": "Importeren van met wachtwoord beveiligde SSH-sleutels wordt nog niet ondersteund" - }, "invalidSshKey": { "message": "De SSH-sleutel is ongeldig" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Sleutel van klembord importeren" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH-sleutel succesvol geïmporteerd" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "E-mailadres van het account veranderen" + }, + "allowScreenshots": { + "message": "Schermopname toestaan" + }, + "allowScreenshotsDesc": { + "message": "Toestaan dat de Bitwarden desktopapplicatie wordt vastgelegd in schermafbeeldingen en bekeken in externe desktopsessies. Als je dit uitgeschakeld, wordt de toegang van sommige externe monitors geblokkeerd." + }, + "confirmWindowStillVisibleTitle": { + "message": "Venster is nog zichtbaar" + }, + "confirmWindowStillVisibleContent": { + "message": "Bevestig dat het venster nog zichtbaar is." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extensie update vereist" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "De browserextensie die je gebruikt is verouderd. Werk deze bij of schakel de browserintegratie vingerafdrukvalidatie uit in de desktop-app-instellingen." + }, + "changeAtRiskPassword": { + "message": "Risicovol wachtwoord wijzigen" } } diff --git a/apps/desktop/src/locales/nn/messages.json b/apps/desktop/src/locales/nn/messages.json index 543f20fc01d..9195311cc67 100644 --- a/apps/desktop/src/locales/nn/messages.json +++ b/apps/desktop/src/locales/nn/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Feil" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januar" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Antall ord" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Master password hint" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Passordhint" - }, - "enterEmailToGetHint": { - "message": "Skriv inn e-postadressa til kontoen din, for å få tilsendt hovudpassordhintet ditt på e-post." - }, "getMasterPasswordHint": { "message": "Få hovudpassordhint" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Ugyldig stadfestingskode" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Husk meg" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send e-post om stadfestingskode på nytt" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -871,6 +906,15 @@ "message": "Verify with Duo Security for your organization using the Duo Mobile app, SMS, phone call, or U2F security key.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Self-hosted environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Custom environment" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "Nettadresse for tenar" }, @@ -956,6 +997,9 @@ "no": { "message": "Nei" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Overskriv passord" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Kjøp Premium-medlemsskap" }, - "premiumPurchaseAlert": { - "message": "Du kan kjøpe eit Premium-medlemsskap i Bitwarden sin nettkvelv. Vil du gå til nettstaden no?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Require password or PIN on app start" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Recommended for security." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Hide my email address from recipients." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, "rememberEmail": { "message": "Remember email" }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "Toggle character count", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Are you trying to log in?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Login attempt by $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Time" }, - "confirmLogIn": { - "message": "Confirm login" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Deny login" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Login confirmed for $EMAIL$ on $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "confirmLoginAtemptForMail": { - "message": "Confirm login attempt for $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Log in requested" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "region": { "message": "Region" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/or/messages.json b/apps/desktop/src/locales/or/messages.json index 2723db4f882..6f15ed21991 100644 --- a/apps/desktop/src/locales/or/messages.json +++ b/apps/desktop/src/locales/or/messages.json @@ -249,6 +249,20 @@ "error": { "message": "ତ୍ରୁଟି" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "ଜାନୁଆରୀ" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Number of words" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Master password hint" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -871,6 +906,15 @@ "message": "Verify with Duo Security for your organization using the Duo Mobile app, SMS, phone call, or U2F security key.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Self-hosted environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Custom environment" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "Server URL" }, @@ -956,6 +997,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Overwrite password" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Purchase Premium" }, - "premiumPurchaseAlert": { - "message": "You can purchase premium membership on the bitwarden.com web vault. Do you want to visit the website now?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Require password or PIN on app start" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Recommended for security." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Hide my email address from recipients." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, "rememberEmail": { "message": "Remember email" }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "Toggle character count", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Are you trying to log in?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Login attempt by $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Time" }, - "confirmLogIn": { - "message": "Confirm login" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Deny login" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Login confirmed for $EMAIL$ on $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "confirmLoginAtemptForMail": { - "message": "Confirm login attempt for $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Log in requested" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "region": { "message": "Region" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json index 9cddf815463..ef8cdc6eb9b 100644 --- a/apps/desktop/src/locales/pl/messages.json +++ b/apps/desktop/src/locales/pl/messages.json @@ -27,7 +27,7 @@ "message": "Bezpieczna notatka" }, "typeSshKey": { - "message": "SSH key" + "message": "Klucz SSH" }, "folders": { "message": "Foldery" @@ -64,7 +64,7 @@ } }, "welcomeBack": { - "message": "Welcome back" + "message": "Witaj ponownie" }, "moveToOrgDesc": { "message": "Wybierz organizację, do której chcesz przenieść ten element. Ta czynność spowoduje utratę własności elementu i przenosi te uprawnienia do organizacji." @@ -181,61 +181,61 @@ "message": "Adres" }, "sshPrivateKey": { - "message": "Private key" + "message": "Klucz prywatny" }, "sshPublicKey": { - "message": "Public key" + "message": "Klucz publiczny" }, "sshFingerprint": { - "message": "Fingerprint" + "message": "Odcisk palca" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "Typ klucza" }, "sshKeyAlgorithmED25519": { "message": "ED25519" }, "sshKeyAlgorithmRSA2048": { - "message": "RSA 2048-Bit" + "message": "RSA 2048-bitowy" }, "sshKeyAlgorithmRSA3072": { - "message": "RSA 3072-Bit" + "message": "RSA 3072-bitowy" }, "sshKeyAlgorithmRSA4096": { - "message": "RSA 4096-Bit" + "message": "RSA 4096-bitowy" }, "sshKeyGenerated": { - "message": "A new SSH key was generated" + "message": "Nowy klucz SSH został wygenerowany" }, "sshKeyWrongPassword": { - "message": "The password you entered is incorrect." + "message": "Wprowadzone hasło jest nieprawidłowe." }, "importSshKey": { - "message": "Import" + "message": "Importuj" }, "confirmSshKeyPassword": { - "message": "Confirm password" + "message": "Potwierdź hasło" }, "enterSshKeyPasswordDesc": { - "message": "Enter the password for the SSH key." + "message": "Wprowadź hasło do klucza SSH." }, "enterSshKeyPassword": { - "message": "Enter password" + "message": "Wprowadź hasło" }, "sshAgentUnlockRequired": { - "message": "Please unlock your vault to approve the SSH key request." + "message": "Odblokuj swój sejf, aby zatwierdzić żądanie klucza SSH." }, "sshAgentUnlockTimeout": { - "message": "SSH key request timed out." + "message": "Przekroczono limit czasu żądania klucza SSH." }, "enableSshAgent": { - "message": "Enable SSH agent" + "message": "Włącz agenta SSH" }, "enableSshAgentDesc": { - "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + "message": "Włącz agenta SSH do podpisywania żądań SSH bezpośrednio z Twojego sejfu Bitwarden." }, "enableSshAgentHelp": { - "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + "message": "Agent SSH to usługa skierowana do programistów, która umożliwia podpisywanie żądań SSH bezpośrednio z Twojego sejfu Bitwarden." }, "premiumRequired": { "message": "Konto Premium jest wymagane" @@ -249,6 +249,20 @@ "error": { "message": "Błąd" }, + "decryptionError": { + "message": "Błąd odszyfrowywania" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nie mógł odszyfrować elementów sejfu wymienionych poniżej." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Skontaktuj się z działem obsługi klienta,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "aby uniknąć dalszej utraty danych.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Styczeń" }, @@ -323,7 +337,7 @@ "message": "Wygeneruj hasło" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "Wygeneruj hasło wyrazowe" }, "type": { "message": "Rodzaj" @@ -461,13 +475,13 @@ "message": "Kopiuj hasło" }, "regenerateSshKey": { - "message": "Regenerate SSH key" + "message": "Wygeneruj ponownie klucz SSH" }, "copySshPrivateKey": { - "message": "Copy SSH private key" + "message": "Skopiuj klucz prywatny SSH" }, "copyPassphrase": { - "message": "Copy passphrase", + "message": "Skopiuj hasło wyrazowe", "description": "Copy passphrase to clipboard" }, "copyUri": { @@ -498,11 +512,11 @@ "message": "Znaki specjalne (!@#$%^&*)" }, "include": { - "message": "Include", + "message": "Uwzględnij", "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "Uwzględnij wielkie litery", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -510,7 +524,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "Uwzględnij małe litery", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -518,7 +532,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "Uwzględnij cyfry", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -526,13 +540,9 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "Uwzględnij znaki specjalne", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Liczba słów" }, @@ -561,7 +571,7 @@ "description": "deprecated. Use avoidAmbiguous instead." }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "Unikaj niejednoznacznych znaków", "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { @@ -624,7 +634,7 @@ "message": "Utwórz konto" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Nowy na Bitwarden?" }, "setAStrongPassword": { "message": "Ustaw silne hasło" @@ -636,16 +646,25 @@ "message": "Zaloguj się" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Zaloguj do Bitwarden" + }, + "enterTheCodeSentToYourEmail": { + "message": "Wpisz kod wysłany na Twój adres e-mail" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Wpisz kod z aplikacji uwierzytelniającej" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Naciśnij YubiKey aby uwierzytelnić" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Zaloguj się używając klucza dostępu" }, "loginWithDevice": { - "message": "Log in with device" + "message": "Zaloguj się za pomocą urządzenia" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "Użyj jednokrotnego logowania" }, "submit": { "message": "Wyślij" @@ -690,11 +709,20 @@ "masterPassHintLabel": { "message": "Podpowiedź do hasła głównego" }, + "passwordStrengthScore": { + "message": "Siła hasła: $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Dołącz do organizacji" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "Dołącz do $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Wprowadź adres e-mail swojego konta, a podpowiedź hasła zostanie wysłana do Ciebie" }, - "passwordHint": { - "message": "Podpowiedź do hasła" - }, - "enterEmailToGetHint": { - "message": "Wpisz adres e-mail powiązany z kontem, aby otrzymać podpowiedź do hasła głównego." - }, "getMasterPasswordHint": { "message": "Uzyskaj podpowiedź do hasła głównego" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "Uwierzytelnianie zostało anulowane lub trwało zbyt długo. Spróbuj ponownie." }, + "openInNewTab": { + "message": "Otwórz w nowej karcie" + }, "invalidVerificationCode": { "message": "Kod weryfikacyjny jest nieprawidłowy" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Zapamiętaj mnie" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Nie pytaj ponownie na tym urządzeniu przez 30 dni" + }, "sendVerificationCodeEmailAgain": { "message": "Wyślij ponownie wiadomość z kodem weryfikacyjnym" }, "useAnotherTwoStepMethod": { "message": "Użyj innej metody logowania dwustopniowego" }, + "selectAnotherMethod": { + "message": "Wybierz inną metodę", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Użyj kodu odzyskiwania" + }, "insertYubiKey": { "message": "Włóż klucz YubiKey do portu USB komputera, a następnie dotknij jego przycisku." }, @@ -871,6 +906,15 @@ "message": "Weryfikacja dostępu do Twojej organizacji z użyciem Duo Security poprzez aplikację Duo Mobile, SMS, połączenie telefoniczne lub klucz bezpieczeństwa U2F.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Potwierdź swoją tożsamość" + }, + "weDontRecognizeThisDevice": { + "message": "Nie rozpoznajemy tego urządzenia. Wpisz kod wysłany na Twój e-mail, aby zweryfikować tożsamość." + }, + "continueLoggingIn": { + "message": "Kontynuuj logowanie" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Opcje logowania dwustopniowego" }, + "selectTwoStepLoginMethod": { + "message": "Wybierz metodę logowania dwustopniowego" + }, "selfHostedEnvironment": { "message": "Samodzielnie hostowane środowisko" }, - "selfHostedEnvironmentFooter": { - "message": "Wpisz podstawowy adres URL hostowanej instalacji Bitwarden." - }, "selfHostedBaseUrlHint": { "message": "Określ bazowy adres URL swojej instalacji Bitwarden. Przykład: https://bitwarden.company.com" }, @@ -913,20 +957,17 @@ "customEnvironment": { "message": "Niestandardowe środowisko" }, - "customEnvironmentFooter": { - "message": "Dla zaawansowanych użytkowników. Możesz wpisać podstawowy adres URL niezależnie dla każdej usługi." - }, "baseUrl": { "message": "Adres URL serwera" }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "Limit czasu uwierzytelniania" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "Upłynął limit czasu uwierzytelniania. Uruchom ponownie proces logowania." }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "URL samodzielnie hostowanego serwera", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -956,6 +997,9 @@ "no": { "message": "Nie" }, + "location": { + "message": "Lokalizacja" + }, "overwritePassword": { "message": "Zastąp hasło" }, @@ -1073,16 +1117,16 @@ "message": "Sejf jest zablokowany. Zweryfikuj swoją tożsamość, aby kontynuować." }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "Twoje konto jest zablokowane" }, "or": { - "message": "or" + "message": "lub" }, "unlockWithBiometrics": { - "message": "Unlock with biometrics" + "message": "Odblokuj za pomocą danych biometrycznych" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "Odblokuj przy użyciu hasła głównego" }, "unlock": { "message": "Odblokuj" @@ -1320,7 +1364,7 @@ "description": "Copy credit card number" }, "copyEmail": { - "message": "Copy email" + "message": "Skopiuj e-mail" }, "copySecurityCode": { "message": "Kopiuj kod zabezpieczający", @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Kup konto Premium" }, - "premiumPurchaseAlert": { - "message": "Konto Premium możesz zakupić na stronie sejfu bitwarden.com. Czy chcesz otworzyć tę stronę?" - }, "premiumPurchaseAlertV2": { "message": "Możesz kupić Premium w ustawieniach konta w aplikacji internetowej Bitwarden." }, @@ -1393,13 +1434,13 @@ "message": "Historia hasła" }, "generatorHistory": { - "message": "Generator history" + "message": "Historia generatora" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "Wyczyść historię generatora" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "Jeśli zatwierdzisz, wszystkie wygenerowane hasła zostaną usunięte z historii generatora. Czy chcesz kontynuować mimo to?" }, "clear": { "message": "Wyczyść", @@ -1409,13 +1450,13 @@ "message": "Brak haseł." }, "clearHistory": { - "message": "Clear history" + "message": "Wyczyść historię" }, "nothingToShow": { - "message": "Nothing to show" + "message": "Brak zawartości do pokazania" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "Nic nie zostało wygenerowane przez ciebie w ostatnim czasie" }, "undo": { "message": "Cofnij" @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Wymagaj hasła lub PIN przy starcie aplikacji" }, + "requirePasswordWithoutPinOnStart": { + "message": "Wymagaj hasła przy starcie aplikacji" + }, "recommendedForSecurity": { "message": "Zalecane dla bezpieczeństwa." }, @@ -1771,10 +1815,10 @@ "message": "Usunięcie konta jest nieodwracalne. Ta czynność nie może zostać cofnięta." }, "cannotDeleteAccount": { - "message": "Cannot delete account" + "message": "Nie można usunąć konta" }, "cannotDeleteAccountDesc": { - "message": "This action cannot be completed because your account is owned by an organization. Contact your organization administrator for additional details." + "message": "Tej akcji nie można zakończyć, ponieważ Twoje konto jest zarządzane przez organizację. Skontaktuj się z administratorem organizacji, aby uzyskać dodatkowe informacje." }, "accountDeleted": { "message": "Konto zostało usunięte" @@ -1887,7 +1931,7 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "z $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -1975,7 +2019,7 @@ "message": "Włącz połączenie z przeglądarką" }, "enableBrowserIntegrationDesc1": { - "message": "Used to allow biometric unlock in browsers that are not Safari." + "message": "Używane do odblokowania biometrycznego w przeglądarkach innych niż Safari." }, "enableDuckDuckGoBrowserIntegration": { "message": "Włącz połączenie z przeglądarką DuckDuckGo" @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Uwierzytelnianie WebAuthn" }, + "readSecurityKey": { + "message": "Odczytaj klucz bezpieczeństwa" + }, + "awaitingSecurityKeyInteraction": { + "message": "Oczekiwanie na interakcję z kluczem bezpieczeństwa..." + }, "hideEmail": { "message": "Ukryj mój adres e-mail przed odbiorcami." }, @@ -2456,7 +2506,7 @@ "message": "Zablokowany" }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "Twój sejf jest zablokowany" }, "unlocked": { "message": "Odblokowany" @@ -2478,10 +2528,10 @@ "message": "Wygeneruj nazwę użytkownika" }, "generateEmail": { - "message": "Generate email" + "message": "Wygeneruj e-mail" }, "spinboxBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$.", + "message": "Wartość musi być pomiędzy $MIN$ a $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2495,7 +2545,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "message": " Użyj $RECOMMENDED$ znaków lub więcej, aby wygenerować silne hasło.", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2505,7 +2555,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "message": " Użyj $RECOMMENDED$ słów lub więcej, aby wygenerować silne hasło.", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Użyj skonfigurowanej skrzynki catch-all w swojej domenie." }, + "useThisEmail": { + "message": "Użyj tego adresu e-mail" + }, "random": { "message": "Losowa" }, @@ -2558,11 +2611,11 @@ "message": "Wygeneruj alias adresu e-mail z zewnętrznej usługi przekazywania." }, "forwarderDomainName": { - "message": "Email domain", + "message": "Domena e-mail", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Choose a domain that is supported by the selected service", + "message": "Wybierz domenę, która jest obsługiwana przez wybraną usługę", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ odrzucił Twoje żądanie. Skontaktuj się z dostawcą usług w celu uzyskania pomocy.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ odrzucił Twoje żądanie: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Nie można uzyskać ID maskowanego konta e-mail dla $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Logowanie hasłem głównym" }, - "loggingInAs": { - "message": "Logowanie jako" - }, "rememberEmail": { "message": "Zapamiętaj adres e-mail" }, - "notYou": { - "message": "To nie Ty?" - }, "newAroundHere": { "message": "Nowy użytkownik?" }, @@ -2722,17 +2793,26 @@ "loginInitiated": { "message": "Logowanie rozpoczęte" }, + "logInRequestSent": { + "message": "Żądanie wysłane" + }, "notificationSentDevice": { "message": "Powiadomienie zostało wysłane na urządzenie." }, "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "message": "Powiadomienie zostało wysłane na twoje urządzenie" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Odblokuj Bitwarden na swoim urządzeniu lub w" + }, + "notificationSentDeviceAnchor": { + "message": "aplikacji internetowej" + }, + "notificationSentDevicePart2": { + "message": "Upewnij się, że fraza odcisku palca zgadza się z tą poniżej, zanim zatwierdzisz." }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "Potrzebujesz innego sposobu?" }, "fingerprintMatchInfo": { "message": "Upewnij się, że sejf jest odblokowany, a unikalny identyfikator konta pasuje do innego urządzenia." @@ -2741,13 +2821,13 @@ "message": "Unikalny identyfikator konta" }, "youWillBeNotifiedOnceTheRequestIsApproved": { - "message": "You will be notified once the request is approved" + "message": "Zostaniesz powiadomiony po zatwierdzeniu prośby" }, "needAnotherOption": { "message": "Logowanie za pomocą urządzenia musi być włączone w ustawieniach aplikacji Bitwarden. Potrzebujesz innej opcji?" }, "viewAllLogInOptions": { - "message": "View all log in options" + "message": "Zobacz wszystkie sposoby logowania" }, "viewAllLoginOptions": { "message": "Zobacz wszystkie sposoby logowania" @@ -2759,11 +2839,11 @@ "message": "Pokaż / Ukryj licznik znaków", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Próbujesz się zalogować?" + "areYouTryingToAccessYourAccount": { + "message": "Czy próbujesz uzyskać dostęp do swojego konta?" }, - "logInAttemptBy": { - "message": "Próba logowania przez $EMAIL$", + "accessAttemptBy": { + "message": "Próba dostępu przez $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Godzina" }, - "confirmLogIn": { - "message": "Potwierdź logowanie" + "confirmAccess": { + "message": "Potwierdź dostęp" }, - "denyLogIn": { - "message": "Odrzuć logowanie" + "denyAccess": { + "message": "Odmów dostępu" }, "logInConfirmedForEmailOnDevice": { "message": "Logowanie potwierdzone dla $EMAIL$ dnia $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "Ta prośba nie jest już ważna." }, - "confirmLoginAtemptForMail": { - "message": "Potwierdź próbę logowania dla $EMAIL$", + "confirmAccessAttempt": { + "message": "Potwierdź próbę dostępu dla $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Wysłano prośbę logowania" }, + "accountAccessRequested": { + "message": "Poproszono o dostęp do konta" + }, "creatingAccountOn": { "message": "Tworzenie konta na" }, @@ -2865,17 +2948,23 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Słabe hasło ujawnione w wyniku naruszenia ochrony danych. Użyj silnego i unikalnego hasła, aby chronić swoje konto. Czy na pewno chcesz użyć tego hasła?" }, + "useThisPassword": { + "message": "Użyj tego hasła" + }, + "useThisUsername": { + "message": "Użyj tej nazwy użytkownika" + }, "checkForBreaches": { "message": "Sprawdź znane naruszenia ochrony danych tego hasła" }, "loggedInExclamation": { - "message": "Logged in!" + "message": "Zalogowano!" }, "important": { "message": "Ważne:" }, "accessing": { - "message": "Accessing" + "message": "Uzyskiwanie dostępu" }, "accessTokenUnableToBeDecrypted": { "message": "Zostałeś wylogowany, ponieważ token dostępu nie mógł zostać odszyfrowany. Zaloguj się ponownie, aby rozwiązać ten problem." @@ -2902,16 +2991,16 @@ "message": "Aktualizacja ustawień zalecanych" }, "rememberThisDeviceToMakeFutureLoginsSeamless": { - "message": "Remember this device to make future logins seamless" + "message": "Zapamiętaj to urządzenie, aby przyszłe logowania były bezproblemowe" }, "deviceApprovalRequired": { "message": "Wymagane zatwierdzenie urządzenia. Wybierz opcję zatwierdzenia poniżej:" }, "deviceApprovalRequiredV2": { - "message": "Device approval required" + "message": "Wymagane zatwierdzenie urządzenia" }, "selectAnApprovalOptionBelow": { - "message": "Select an approval option below" + "message": "Wybierz opcję zatwierdzenia poniżej" }, "rememberThisDevice": { "message": "Zapamiętaj to urządzenie" @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Poproś administratora o zatwierdzenie" }, - "approveWithMasterPassword": { - "message": "Zatwierdź przy użyciu hasła głównego" - }, "region": { "message": "Region" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Twoja prośba została wysłana do Twojego administratora." }, - "youWillBeNotifiedOnceApproved": { - "message": "Zostaniesz powiadomiony po zatwierdzeniu." - }, "troubleLoggingIn": { "message": "Problem z zalogowaniem?" }, @@ -2966,7 +3049,7 @@ "message": "Brak adresu e-mail użytkownika" }, "activeUserEmailNotFoundLoggingYouOut": { - "message": "Active user email not found. Logging you out." + "message": "Nie znaleziono aktywnego adresu e-mail. Trwa wylogowanie." }, "deviceTrusted": { "message": "Zaufano urządzeniu" @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Dwustopniowe logowanie Duo jest wymagane dla Twojego konta." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Logowanie dwustopniowe Duo jest wymagane dla twojego konta. Wykonaj poniższe kroki, by dokończyć logowanie" + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Wykonaj poniższe kroki, by dokończyć logowanie" + }, "launchDuo": { "message": "Uruchom Duo w przeglądarce" }, @@ -3354,64 +3443,103 @@ "message": "Dane" }, "fileSends": { - "message": "File Sends" + "message": "Wysyłki plików" }, "textSends": { - "message": "Text Sends" + "message": "Wysyłki tekstów" }, "ssoError": { "message": "Nie znaleziono wolnych portów dla logowania SSO." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Odblokowanie odciskiem palca jest niedostępne, ponieważ najpierw wymagane jest odblokowanie kodem PIN lub hasłem." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Odblokowanie biometryczne jest obecnie niedostępne." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Odblokowanie biometryczne jest niedostępne z powodu nieprawidłowej konfiguracji plików systemowych." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Odblokowanie biometryczne jest niedostępne z powodu nieprawidłowej konfiguracji plików systemowych." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Odblokowanie biometryczne jest niedostępne, ponieważ nie jest włączone dla $EMAIL$ w aplikacji desktopowej Bitwarden.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Odblokowanie biometryczne jest obecnie niedostępne z nieznanego powodu." + }, "authorize": { - "message": "Authorize" + "message": "Autoryzuj" }, "deny": { - "message": "Deny" + "message": "Odmów" }, "sshkeyApprovalTitle": { - "message": "Confirm SSH key usage" + "message": "Potwierdź użycie klucza SSH" + }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "To żądanie pochodzi ze zdalnego urządzenia, do którego jesteś zalogowany" }, "sshkeyApprovalMessageInfix": { - "message": "is requesting access to" + "message": "wnioskuje o dostęp do" + }, + "sshkeyApprovalMessageSuffix": { + "message": "w celu" + }, + "sshActionLogin": { + "message": "autoryzacji na serwerze" + }, + "sshActionSign": { + "message": "podpisania wiadomości" + }, + "sshActionGitSign": { + "message": "podpisania commita w giciem" }, "unknownApplication": { - "message": "An application" - }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" + "message": "Aplikacja" }, "invalidSshKey": { - "message": "The SSH key is invalid" + "message": "Klucz SSH jest nieprawidłowy" }, "sshKeyTypeUnsupported": { - "message": "The SSH key type is not supported" + "message": "Typ klucza SSH nie jest obsługiwany" }, "importSshKeyFromClipboard": { - "message": "Import key from clipboard" + "message": "Importuj klucz ze schowka" }, - "sshKeyPasted": { - "message": "SSH key imported successfully" + "sshKeyImported": { + "message": "Klucz SSH zaimportowano pomyślnie" }, "fileSavedToDevice": { "message": "Plik zapisany na urządzeniu. Zarządzaj plikiem na swoim urządzeniu." }, "importantNotice": { - "message": "Important notice" + "message": "Ważna informacja" }, "setupTwoStepLogin": { - "message": "Set up two-step login" + "message": "Skonfiguruj dwustopniowe logowanie" }, "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + "message": "Bitwarden wyśle kod na Twój adres e-mail w celu zweryfikowania logowania z nowych urządzeń, począwszy od lutego 2025 r." }, "newDeviceVerificationNoticeContentPage2": { - "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + "message": "Możesz skonfigurować dwustopniowe logowanie jako alternatywny sposób ochrony konta lub zmienić swój adres e-mail, do którego masz dostęp." }, "remindMeLater": { - "message": "Remind me later" + "message": "Przypomnij mi później" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", + "message": "Czy masz pewny dostęp do swojego adresu e-mail, $EMAIL$?", "placeholders": { "email": { "content": "$1", @@ -3420,15 +3548,36 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "Nie, nie mam" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" + "message": "Tak, mam pewny dostęp do mojego adresu e-mail" }, "turnOnTwoStepLogin": { - "message": "Turn on two-step login" + "message": "Włącz dwustopniowe logowanie" }, "changeAcctEmail": { - "message": "Change account email" + "message": "Zmień adres e-mail konta" + }, + "allowScreenshots": { + "message": "Zezwól na przechwytywanie ekranu" + }, + "allowScreenshotsDesc": { + "message": "Zezwól aplikacji desktopowej Bitwarden na przechwytywanie w zrzutach ekranu i przeglądanie w sesjach zdalnego pulpitu. Wyłączenie tej opcji uniemożliwi dostęp na niektórych zewnętrznych wyświetlaczach." + }, + "confirmWindowStillVisibleTitle": { + "message": "Potwierdź widoczność okna" + }, + "confirmWindowStillVisibleContent": { + "message": "Potwierdź, że okno jest nadal widoczne." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Wymagana aktualizacja rozszerzenia" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "Rozszerzenie przeglądarki, którego używasz, jest nieaktualne. Zaktualizuj je lub wyłącz weryfikację odcisku palca integracji przeglądarki w ustawieniach aplikacji desktopowej." + }, + "changeAtRiskPassword": { + "message": "Zmień zagrożone hasło" } } diff --git a/apps/desktop/src/locales/pt_BR/messages.json b/apps/desktop/src/locales/pt_BR/messages.json index 3e1b8f54040..e09ee6e16d2 100644 --- a/apps/desktop/src/locales/pt_BR/messages.json +++ b/apps/desktop/src/locales/pt_BR/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Erro" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Janeiro" }, @@ -529,10 +543,6 @@ "message": "Incluir caracteres especiais", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Número de palavras" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Inicie a sessão no Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Iniciar sessão com a chave de acesso" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Dica da senha mestra" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Juntar-se à organização" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Digite o endereço de e-mail da sua conta e sua dica da senha será enviada para você" }, - "passwordHint": { - "message": "Dica da Senha" - }, - "enterEmailToGetHint": { - "message": "Insira o seu endereço de e-mail para receber a dica da sua senha mestra." - }, "getMasterPasswordHint": { "message": "Obter dica da senha mestra" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "A autenticação foi cancelada ou demorou muito. Por favor tente novamente." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Código de verificação inválido" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Lembrar de mim" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Enviar código de verificação para o e-mail novamente" }, "useAnotherTwoStepMethod": { "message": "Utilizar outro método de verificação em duas etapas" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Introduza a sua YubiKey na porta USB do seu computador, e depois toque no botão da mesma." }, @@ -871,6 +906,15 @@ "message": "Verifique com o Duo Security utilizando o aplicativo Duo Mobile, SMS, chamada telefônica, ou chave de segurança U2F.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "WebAuthn FIDO2" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Opções de Login em Duas Etapas" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Ambiente auto-hospedado" }, - "selfHostedEnvironmentFooter": { - "message": "Especifique a URL de base da sua instalação local do Bitwarden." - }, "selfHostedBaseUrlHint": { "message": "Especifique a URL de base da sua instalação local do Bitwarden. Exemplo: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Ambiente Personalizado" }, - "customEnvironmentFooter": { - "message": "Para usuários avançados. Você pode especificar a URL de base de cada serviço independentemente." - }, "baseUrl": { "message": "URL do Servidor" }, @@ -956,6 +997,9 @@ "no": { "message": "Não" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Substituir Senha" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Comprar Premium" }, - "premiumPurchaseAlert": { - "message": "Você pode comprar a assinatura premium no cofre web em bitwarden.com. Você deseja visitar o site agora?" - }, "premiumPurchaseAlertV2": { "message": "Você pode comprar Premium nas configurações de sua conta no aplicativo web do Bitwarden." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Exigir senha ou PIN ao iniciar o app" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Recomendado para segurança." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Autenticar WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Ocultar meu endereço de e-mail dos destinatários." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Use o catch-all configurado no seu domínio." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Aleatório" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Não foi possível obter a máscara do ID da conta de email $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "‘Login’ com senha ‘master’" }, - "loggingInAs": { - "message": "Logar como" - }, "rememberEmail": { "message": "Lembrar e-mail" }, - "notYou": { - "message": "Não é você?" - }, "newAroundHere": { "message": "Novo por aqui?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Login iniciado" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "Uma notificação foi enviada para seu dispositivo." }, "aNotificationWasSentToYourDevice": { "message": "Uma notificação foi enviada para seu dispositivo" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Certifique-se que sua conta esteja desbloqueada e que a frase de identificação corresponda à do outro dispositivo" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Precisa de outra opção?" @@ -2759,11 +2839,11 @@ "message": "Ativar/Desativar contagem de caracteres", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Você está tentando fazer login?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Tentativa de login por $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Horário" }, - "confirmLogIn": { - "message": "Confirmar acesso" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Negar acesso" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Acesso confirmado para $EMAIL$ em $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "Este pedido não é mais válido." }, - "confirmLoginAtemptForMail": { - "message": "Confirmar tentativa de acesso de $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Acesso solicitado" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Criando conta em" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Senha fraca identificada e encontrada em um vazamento de dados. Use uma senha forte e única para proteger a sua conta. Tem certeza de que deseja usar essa senha?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Verificar vazamentos de dados conhecidos para esta senha" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Solicitar aprovação do administrador" }, - "approveWithMasterPassword": { - "message": "Aprovar com senha mestra" - }, "region": { "message": "Região" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Seu pedido foi enviado para seu administrador." }, - "youWillBeNotifiedOnceApproved": { - "message": "Será notificado assim que for aprovado." - }, "troubleLoggingIn": { "message": "Problemas em efetuar login?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "A autenticação em duas etapas do Duo é necessária para sua conta." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Iniciar o Duo no navegador" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "Nenhuma porta livre foi encontrada para o cliente final." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Autorizar" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirmar uso da chave SSH" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "está solicitando acesso para" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "Uma aplicação" }, - "sshKeyPasswordUnsupported": { - "message": "Importar chaves SSH protegidas por senha ainda não é suportado" - }, "invalidSshKey": { "message": "A chave SSH é inválida" }, @@ -3389,8 +3517,8 @@ "importSshKeyFromClipboard": { "message": "Importar chave da área de transferência" }, - "sshKeyPasted": { - "message": "Chave SSH importada com sucesso" + "sshKeyImported": { + "message": "SSH key imported successfully" }, "fileSavedToDevice": { "message": "Arquivo salvo no dispositivo. Gerencie a partir das transferências do seu dispositivo." @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Alterar e-mail" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json index 55b031690a8..2261357ba08 100644 --- a/apps/desktop/src/locales/pt_PT/messages.json +++ b/apps/desktop/src/locales/pt_PT/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Erro" }, + "decryptionError": { + "message": "Erro de desencriptação" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "O Bitwarden não conseguiu desencriptar o(s) item(ns) do cofre listado(s) abaixo." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contacte o serviço de apoio ao cliente", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "para evitar perdas adicionais de dados.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Janeiro" }, @@ -495,14 +509,14 @@ "description": "deprecated. Use numbersLabel instead." }, "specialCharacters": { - "message": "Caracteres especiais (!@#$%^&*)" + "message": "Carateres especiais (!@#$%^&*)" }, "include": { "message": "Incluir", "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Incluir caracteres em maiúsculas", + "message": "Incluir carateres em maiúsculas", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -510,7 +524,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Incluir caracteres em minúsculas", + "message": "Incluir carateres em minúsculas", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -526,13 +540,9 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Incluir caracteres especiais", + "message": "Incluir carateres especiais", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Número de palavras" }, @@ -553,15 +563,15 @@ "message": "Mínimo de números" }, "minSpecial": { - "message": "Mínimo de caracteres especiais", + "message": "Mínimo de carateres especiais", "description": "Minimum Special Characters" }, "ambiguous": { - "message": "Evitar caracteres ambíguos", + "message": "Evitar carateres ambíguos", "description": "deprecated. Use avoidAmbiguous instead." }, "avoidAmbiguous": { - "message": "Evitar caracteres ambíguos", + "message": "Evitar carateres ambíguos", "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Iniciar sessão no Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Introduza o código enviado para o seu e-mail" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Introduza o código da sua app de autenticação" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Prima a sua YubiKey para se autenticar" + }, "logInWithPasskey": { "message": "Iniciar sessão com a chave de acesso" }, @@ -666,7 +685,7 @@ "message": "Dica da palavra-passe mestra (opcional)" }, "masterPassHintText": { - "message": "Se se esquecer da sua palavra-passe, a dica da palavra-passe pode ser enviada para o seu e-mail. Máximo de $CURRENT$/$MAXIMUM$ caracteres.", + "message": "Se se esquecer da sua palavra-passe, a dica da palavra-passe pode ser enviada para o seu e-mail. Máximo de $CURRENT$/$MAXIMUM$ carateres.", "placeholders": { "current": { "content": "$1", @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Dica da palavra-passe mestra" }, + "passwordStrengthScore": { + "message": "Pontuação da força da palavra-passe: $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Aderir à organização" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Introduza o endereço de e-mail da sua conta e ser-lhe-á enviada a sua dica da palavra-passe" }, - "passwordHint": { - "message": "Dica da palavra-passe" - }, - "enterEmailToGetHint": { - "message": "Introduza o endereço de e-mail da sua conta para receber a dica da sua palavra-passe mestra." - }, "getMasterPasswordHint": { "message": "Obter a dica da palavra-passe mestra" }, @@ -742,7 +764,7 @@ "message": "É necessário reescrever a palavra-passe mestra." }, "masterPasswordMinlength": { - "message": "A palavra-passe mestra deve ter pelo menos $VALUE$ caracteres.", + "message": "A palavra-passe mestra deve ter pelo menos $VALUE$ carateres.", "description": "The Master Password must be at least a specific number of characters long.", "placeholders": { "value": { @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "A autenticação foi cancelada ou demorou demasiado tempo. Por favor, tente novamente." }, + "openInNewTab": { + "message": "Abrir num novo separador" + }, "invalidVerificationCode": { "message": "Código de verificação inválido" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Memorizar" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Não voltar a perguntar neste dispositivo durante 30 dias" + }, "sendVerificationCodeEmailAgain": { "message": "Enviar e-mail com o código de verificação novamente" }, "useAnotherTwoStepMethod": { "message": "Utilizar outro método de verificação de dois passos" }, + "selectAnotherMethod": { + "message": "Selecionar outro método", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Utilize o seu código de recuperação" + }, "insertYubiKey": { "message": "Introduza a sua YubiKey na porta USB do seu computador, depois toque no botão da mesma." }, @@ -871,6 +906,15 @@ "message": "Proteja a sua organização com a Duo Security utilizando a aplicação Duo Mobile, SMS, chamada telefónica ou uma chave de segurança U2F.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verifique a sua identidade" + }, + "weDontRecognizeThisDevice": { + "message": "Não reconhecemos este dispositivo. Introduza o código enviado para o seu e-mail para verificar a sua identidade." + }, + "continueLoggingIn": { + "message": "Continuar a iniciar sessão" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Opções de verificação de dois passos" }, + "selectTwoStepLoginMethod": { + "message": "Selecionar método de verificação de dois passos" + }, "selfHostedEnvironment": { "message": "Ambiente auto-hospedado" }, - "selfHostedEnvironmentFooter": { - "message": "Especifique o URL de base da sua instalação Bitwarden hospedada no local." - }, "selfHostedBaseUrlHint": { "message": "Especifique o URL de base da sua instalação Bitwarden hospedada no local. Exemplo: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Ambiente personalizado" }, - "customEnvironmentFooter": { - "message": "Para utilizadores avançados. Pode especificar o URL de base de cada serviço de forma independente." - }, "baseUrl": { "message": "URL do servidor" }, @@ -956,6 +997,9 @@ "no": { "message": "Não" }, + "location": { + "message": "Localização" + }, "overwritePassword": { "message": "Substituir palavra-passe" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Adquirir Premium" }, - "premiumPurchaseAlert": { - "message": "Pode adquirir uma subscrição Premium no cofre web em bitwarden.com. Pretende visitar o site agora?" - }, "premiumPurchaseAlertV2": { "message": "Pode adquirir o Premium a partir das definições da sua conta na aplicação Web do Bitwarden." }, @@ -1744,16 +1785,19 @@ "message": "desbloquear o cofre" }, "autoPromptWindowsHello": { - "message": "Pedir o Windows Hello ao iniciar a aplicação" + "message": "Pedir o Windows Hello ao iniciar a app" }, "autoPromptPolkit": { "message": "Pedir a autenticação do sistema no arranque" }, "autoPromptTouchId": { - "message": "Pedir o Touch ID ao iniciar a aplicação" + "message": "Pedir o Touch ID ao iniciar a app" }, "requirePasswordOnStart": { - "message": "Exigir palavra-passe ou PIN ao iniciar a aplicação" + "message": "Exigir palavra-passe ou PIN ao iniciar a app" + }, + "requirePasswordWithoutPinOnStart": { + "message": "Exigir palavra-passe ao iniciar a app" }, "recommendedForSecurity": { "message": "Recomendado por segurança." @@ -1930,16 +1974,16 @@ } }, "policyInEffectUppercase": { - "message": "Contém um ou mais caracteres em maiúsculas" + "message": "Contém um ou mais carateres em maiúsculas" }, "policyInEffectLowercase": { - "message": "Contém um ou mais caracteres em minúsculas" + "message": "Contém um ou mais carateres em minúsculas" }, "policyInEffectNumbers": { "message": "Contém um ou mais números" }, "policyInEffectSpecial": { - "message": "Contém um ou mais dos seguintes caracteres especiais $CHARS$", + "message": "Contém um ou mais dos seguintes carateres especiais $CHARS$", "placeholders": { "chars": { "content": "$1", @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Autenticar o WebAuthn" }, + "readSecurityKey": { + "message": "Ler chave de segurança" + }, + "awaitingSecurityKeyInteraction": { + "message": "A aguardar interação da chave de segurança..." + }, "hideEmail": { "message": "Ocultar o meu endereço de e-mail dos destinatários." }, @@ -2495,7 +2545,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " Utilize $RECOMMENDED$ caracteres ou mais para gerar uma palavra-passe forte.", + "message": " Utilize $RECOMMENDED$ carateres ou mais para gerar uma palavra-passe forte.", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Utilize a caixa de entrada de captura geral configurada para o seu domínio." }, + "useThisEmail": { + "message": "Utilizar este e-mail" + }, "random": { "message": "Aleatório" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ recusou o seu pedido. Por favor, contacte o seu fornecedor de serviços para obter assistência.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ recusou o seu pedido: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Não foi possível obter o ID da conta de e-mail mascarada de $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Iniciar sessão com a palavra-passe mestra" }, - "loggingInAs": { - "message": "A iniciar sessão como" - }, "rememberEmail": { "message": "Memorizar e-mail" }, - "notYou": { - "message": "Utilizador incorreto?" - }, "newAroundHere": { "message": "É novo por cá?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "A preparar o início de sessão" }, + "logInRequestSent": { + "message": "Pedido enviado" + }, "notificationSentDevice": { "message": "Foi enviada uma notificação para o seu dispositivo." }, "aNotificationWasSentToYourDevice": { "message": "Foi enviada uma notificação para o seu dispositivo" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Certifique-se de que a sua conta está desbloqueada e que a frase de impressão digital corresponde à do outro dispositivo" + "notificationSentDevicePart1": { + "message": "Desbloqueie o Bitwarden no seu dispositivo ou no " + }, + "notificationSentDeviceAnchor": { + "message": "aplicação web" + }, + "notificationSentDevicePart2": { + "message": "Certifique-se de que a frase da impressão digital corresponde à frase abaixo indicada antes de a aprovar." }, "needAnotherOptionV1": { "message": "Precisa de outra opção?" @@ -2756,14 +2836,14 @@ "message": "Reenviar notificação" }, "toggleCharacterCount": { - "message": "Mostrar/ocultar contagem de caracteres", + "message": "Mostrar/ocultar contagem de carateres", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Está a tentar iniciar sessão?" + "areYouTryingToAccessYourAccount": { + "message": "Está a tentar aceder à sua conta?" }, - "logInAttemptBy": { - "message": "Tentativa de início de sessão por $EMAIL$", + "accessAttemptBy": { + "message": "Tentativa de acesso por $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,14 +2860,14 @@ "time": { "message": "Hora" }, - "confirmLogIn": { - "message": "Confirmar início de sessão" + "confirmAccess": { + "message": "Confirmar acesso" }, - "denyLogIn": { - "message": "Recusar início de sessão" + "denyAccess": { + "message": "Recusar acesso" }, "logInConfirmedForEmailOnDevice": { - "message": "Início de sessão confirmado para $EMAIL$ em $DEVICE$", + "message": "Início de sessão confirmado para $EMAIL$ no $DEVICE$", "placeholders": { "email": { "content": "$1", @@ -2800,7 +2880,7 @@ } }, "youDeniedALogInAttemptFromAnotherDevice": { - "message": "Negou uma tentativa de início de sessão de outro dispositivo. Se foi realmente o caso, tente iniciar sessão com o dispositivo novamente." + "message": "Recusou uma tentativa de início de sessão de outro dispositivo. Se foi realmente o caso, tente iniciar sessão com o dispositivo novamente." }, "justNow": { "message": "Agora mesmo" @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "Este pedido já não é válido." }, - "confirmLoginAtemptForMail": { - "message": "Confirmar tentativa de início de sessão de $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirmar tentativa de acesso para $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Pedido de início de sessão" }, + "accountAccessRequested": { + "message": "Acesso à conta solicitado" + }, "creatingAccountOn": { "message": "A criar uma conta em" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Palavra-passe fraca identificada e encontrada numa violação de dados. Utilize uma palavra-passe forte e única para proteger a sua conta. Tem a certeza de que pretende utilizar esta palavra-passe?" }, + "useThisPassword": { + "message": "Utilizar esta palavra-passe" + }, + "useThisUsername": { + "message": "Utilizar este nome de utilizador" + }, "checkForBreaches": { "message": "Verificar violações de dados conhecidas para esta palavra-passe" }, @@ -2887,7 +2976,7 @@ "message": "A sua palavra-passe mestra não pode ser recuperada se a esquecer!" }, "characterMinimum": { - "message": "$LENGTH$ caracteres no mínimo", + "message": "$LENGTH$ carateres no mínimo", "placeholders": { "length": { "content": "$1", @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Pedir aprovação do administrador" }, - "approveWithMasterPassword": { - "message": "Aprovar com a palavra-passe mestra" - }, "region": { "message": "Região" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "O seu pedido foi enviado ao seu administrador." }, - "youWillBeNotifiedOnceApproved": { - "message": "Será notificado quando for aprovado." - }, "troubleLoggingIn": { "message": "Problemas a iniciar sessão?" }, @@ -2981,7 +3064,7 @@ "message": "Procurar" }, "inputMinLength": { - "message": "O campo deve ter pelo menos $COUNT$ caracteres.", + "message": "O campo deve ter pelo menos $COUNT$ carateres.", "placeholders": { "count": { "content": "$1", @@ -2990,7 +3073,7 @@ } }, "inputMaxLength": { - "message": "O campo não pode exceder os $COUNT$ caracteres de comprimento.", + "message": "O campo não pode exceder os $COUNT$ carateres de comprimento.", "placeholders": { "count": { "content": "$1", @@ -2999,7 +3082,7 @@ } }, "inputForbiddenCharacters": { - "message": "Não são permitidos os seguintes caracteres: $CHARACTERS$", + "message": "Não são permitidos os seguintes carateres: $CHARACTERS$", "placeholders": { "characters": { "content": "$1", @@ -3008,7 +3091,7 @@ } }, "inputMinValue": { - "message": "O valor do campo tem de ser, pelo menos, $MIN$ caracteres.", + "message": "O valor do campo tem de ser, pelo menos, $MIN$ carateres.", "placeholders": { "min": { "content": "$1", @@ -3017,7 +3100,7 @@ } }, "inputMaxValue": { - "message": "O valor do campo não pode exceder os $MAX$ caracteres.", + "message": "O valor do campo não pode exceder os $MAX$ carateres.", "placeholders": { "max": { "content": "$1", @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "A verificação de dois passos Duo é necessária para a sua conta." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "A verificação de dois passos do Duo é necessária para a sua conta. Siga os passos abaixo para concluir o início de sessão." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Siga os passos abaixo para concluir o início de sessão." + }, "launchDuo": { "message": "Iniciar o Duo no navegador" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "Não foi possível encontrar portas livres para o início de sessão sso." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "O desbloqueio biométrico não está disponível porque o desbloqueio por PIN ou palavra-passe é necessário primeiro." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "O desbloqueio biométrico está atualmente indisponível." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "O desbloqueio biométrico não está disponível devido a ficheiros de sistema mal configurados." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "O desbloqueio biométrico não está disponível devido a ficheiros de sistema mal configurados." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "O desbloqueio biométrico não está disponível porque não está ativado para $EMAIL$ na app Bitwarden para computador.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "O desbloqueio biométrico está atualmente indisponível por um motivo desconhecido." + }, "authorize": { "message": "Autorizar" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirmar a utilização da chave SSH" }, + "agentForwardingWarningTitle": { + "message": "Aviso: Reencaminhamento de agentes" + }, + "agentForwardingWarningText": { + "message": "Este pedido provém de um dispositivo remoto no qual tem sessão iniciada" + }, "sshkeyApprovalMessageInfix": { "message": "está a pedir acesso a" }, + "sshkeyApprovalMessageSuffix": { + "message": "para" + }, + "sshActionLogin": { + "message": "se autenticar num servidor" + }, + "sshActionSign": { + "message": "assinar uma mensagem" + }, + "sshActionGitSign": { + "message": "assinar um commit no git" + }, "unknownApplication": { "message": "Uma aplicação" }, - "sshKeyPasswordUnsupported": { - "message": "A importação de chaves SSH protegidas por palavra-passe ainda não é suportada" - }, "invalidSshKey": { "message": "A chave SSH é inválida" }, @@ -3389,11 +3517,11 @@ "importSshKeyFromClipboard": { "message": "Importar chave da área de transferência" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "Chave SSH importada com sucesso" }, "fileSavedToDevice": { - "message": "Ficheiro guardado no dispositivo. Gira-o a partir das transferências do seu dispositivo." + "message": "Ficheiro guardado no dispositivo. Faça a gestão a partir das transferências do seu dispositivo." }, "importantNotice": { "message": "Aviso importante" @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Alterar o e-mail da conta" + }, + "allowScreenshots": { + "message": "Permitir a captura de ecrã" + }, + "allowScreenshotsDesc": { + "message": "Permitir capturas de ecrã à aplicação para computador do Bitwarden e que seja vista em sessões remotas no computador. Desativar esta opção impedirá o acesso de alguns monitores externos." + }, + "confirmWindowStillVisibleTitle": { + "message": "Janela de confirmação ainda visível" + }, + "confirmWindowStillVisibleContent": { + "message": "Por favor, confirme se a janela ainda está visível." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Atualização da extensão necessária" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "A extensão do navegador que está a utilizar está desatualizada. Atualize-a ou desative a validação de impressões digitais da integração do navegador nas definições da aplicação para computador." + }, + "changeAtRiskPassword": { + "message": "Alterar palavra-passe em risco" } } diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json index 0d26dbb2fc8..d148d90a4fd 100644 --- a/apps/desktop/src/locales/ro/messages.json +++ b/apps/desktop/src/locales/ro/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Eroare" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "ianuarie" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Număr de cuvinte" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Master password hint" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Indiciu parolă" - }, - "enterEmailToGetHint": { - "message": "Adresa de e-mail a contului pentru primirea indiciului parolei principale." - }, "getMasterPasswordHint": { "message": "Obținere indiciu parolă principală" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Cod de verificare nevalid" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Memorare autentificare" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Retrimitere e-mail cu codul de verificare" }, "useAnotherTwoStepMethod": { "message": "Utilizare de metodă diferită de autentificare în două etape" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Introduceți YubiKey în portul USB al calculatorului apoi atingeți butonul acestuia." }, @@ -871,6 +906,15 @@ "message": "Verificați cu Duo Security pentru organizația dvs. utilizând aplicația Duo Mobile, SMS, apel telefonic sau cheia de securitate U2F.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Opțiuni de autentificare în două etape" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Mediul găzduit local" }, - "selfHostedEnvironmentFooter": { - "message": "Specificați URL-ul de bază al implementări Bitwarden găzduită local." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Mediu personalizat" }, - "customEnvironmentFooter": { - "message": "Pentru utilizatorii avansați. Puteți specifica URL-ul de bază al fiecărui serviciu în mod independent." - }, "baseUrl": { "message": "URL server" }, @@ -956,6 +997,9 @@ "no": { "message": "Nu" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Suprascriere parolă" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Achiziționare abonament Premium" }, - "premiumPurchaseAlert": { - "message": "Puteți achiziționa un abonament premium pe saitul web bitwarden.com. Doriți să vizitați saitul acum?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Require password or PIN on app start" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Recommended for security." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Autentificare WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Ascundeți adresa mea de e-mail de la destinatari." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Utilizați inbox-ul catch-all configurat pentru domeniul dvs." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Aleatoriu" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Autentificați-vă cu parola principală" }, - "loggingInAs": { - "message": "Autentificare ca" - }, "rememberEmail": { "message": "Memorare e-mail" }, - "notYou": { - "message": "Nu sunteți dvs.?" - }, "newAroundHere": { "message": "Sunteți nou pe aici?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "Toggle character count", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Are you trying to log in?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Login attempt by $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Time" }, - "confirmLogIn": { - "message": "Confirm login" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Deny login" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Login confirmed for $EMAIL$ on $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "confirmLoginAtemptForMail": { - "message": "Confirm login attempt for $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Log in requested" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "region": { "message": "Region" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json index 056c9d32aca..ea5b8c6d526 100644 --- a/apps/desktop/src/locales/ru/messages.json +++ b/apps/desktop/src/locales/ru/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Ошибка" }, + "decryptionError": { + "message": "Ошибка расшифровки" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden не удалось расшифровать элемент(ы) хранилища, перечисленные ниже." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Обратитесь в службу поддержки,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "чтобы избежать дополнительной потери данных.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Январь" }, @@ -529,10 +543,6 @@ "message": "Включить специальные символы", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Количество слов" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Войти в Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Введите код, отправленный на ваш email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Введите код из приложения-аутентификатора" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Нажмите на YubiKey для аутентификации" + }, "logInWithPasskey": { "message": "Войти с passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Подсказка к мастер-паролю" }, + "passwordStrengthScore": { + "message": "Оценка надежности пароля $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Присоединиться к организации" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Введите email вашего аккаунта, и вам будет отправлена подсказка для пароля" }, - "passwordHint": { - "message": "Подсказка к паролю" - }, - "enterEmailToGetHint": { - "message": "Введите email аккаунта для получения подсказки к мастер-паролю." - }, "getMasterPasswordHint": { "message": "Получить подсказку к мастер-паролю" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "Аутентификация была отменена или заняла слишком много времени. Пожалуйста, попробуйте еще раз." }, + "openInNewTab": { + "message": "Открыть в новой вкладке" + }, "invalidVerificationCode": { "message": "Неверный код подтверждения" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Запомнить меня" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Не спрашивать на этом устройстве в течение 30 дней" + }, "sendVerificationCodeEmailAgain": { "message": "Отправить код подтверждения еще раз" }, "useAnotherTwoStepMethod": { "message": "Использовать другой метод двухэтапной аутентификации" }, + "selectAnotherMethod": { + "message": "Выбрать другой способ", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Использовать код восстановления" + }, "insertYubiKey": { "message": "Вставьте свой YubiKey в USB-порт компьютера и нажмите его кнопку." }, @@ -871,6 +906,15 @@ "message": "Подтвердите с помощью Duo Security для вашей организации, используя приложение Duo Mobile, SMS, телефонный звонок или ключ безопасности U2F.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Подтвердите вашу личность" + }, + "weDontRecognizeThisDevice": { + "message": "Мы не распознали это устройство. Введите код, отправленный на ваш email, чтобы подтвердить вашу личность." + }, + "continueLoggingIn": { + "message": "Продолжить вход" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Настройки двухэтапной аутентификации" }, + "selectTwoStepLoginMethod": { + "message": "Выбрать другой метод двухэтапной аутентификации" + }, "selfHostedEnvironment": { "message": "Окружение пользовательского хостинга" }, - "selfHostedEnvironmentFooter": { - "message": "Укажите URL Bitwarden на вашем сервере." - }, "selfHostedBaseUrlHint": { "message": "Укажите базовый URL вашего локального хостинга Bitwarden. Пример: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Пользовательское окружение" }, - "customEnvironmentFooter": { - "message": "Для опытных пользователей. Можно указать URL отдельно для каждой службы." - }, "baseUrl": { "message": "URL сервера" }, @@ -956,6 +997,9 @@ "no": { "message": "Нет" }, + "location": { + "message": "Местоположение" + }, "overwritePassword": { "message": "Перезаписать пароль" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Купить Премиум" }, - "premiumPurchaseAlert": { - "message": "Вы можете купить Премиум на bitwarden.com. Перейти на сайт сейчас?" - }, "premiumPurchaseAlertV2": { "message": "Премиум можно приобрести в настройках аккаунта в веб-версии Bitwarden." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Требовать пароль или PIN-код при запуске приложения" }, + "requirePasswordWithoutPinOnStart": { + "message": "Требовать пароль при запуске приложения" + }, "recommendedForSecurity": { "message": "Рекомендуется для обеспечения безопасности." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Аутентификация WebAutn" }, + "readSecurityKey": { + "message": "Считать ключ безопасности" + }, + "awaitingSecurityKeyInteraction": { + "message": "Ожидание взаимодействия с ключом безопасности..." + }, "hideEmail": { "message": "Скрыть мой адрес email от получателей." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Использовать общую почту домена." }, + "useThisEmail": { + "message": "Использовать этот email" + }, "random": { "message": "Случайно" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ отклонил(а) ваш запрос. Обратитесь за помощью к своему провайдеру.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ отклонил(а) ваш запрос: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Не удалось получить скрытый идентификатор email аккаунта $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Войти с мастер-паролем" }, - "loggingInAs": { - "message": "Войти как" - }, "rememberEmail": { "message": "Запомнить email" }, - "notYou": { - "message": "Не вы?" - }, "newAroundHere": { "message": "Вы здесь впервые?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Вход инициирован" }, + "logInRequestSent": { + "message": "Запрос отправлен" + }, "notificationSentDevice": { "message": "На ваше устройство отправлено уведомление." }, "aNotificationWasSentToYourDevice": { "message": "На ваше устройство было отправлено уведомление" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Убедитесь, что ваш аккаунт разблокирован и фраза отпечатка совпадает с фразой на другом устройстве" + "notificationSentDevicePart1": { + "message": "Разблокируйте Bitwarden на своем устройстве или " + }, + "notificationSentDeviceAnchor": { + "message": "веб-приложении" + }, + "notificationSentDevicePart2": { + "message": "Перед одобрением убедитесь, что фраза отпечатка совпадает с приведенной ниже." }, "needAnotherOptionV1": { "message": "Нужен другой вариант?" @@ -2759,11 +2839,11 @@ "message": "Показать количество символов", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Вы пытаетесь войти?" + "areYouTryingToAccessYourAccount": { + "message": "Вы пытаетесь получить доступ к своему аккаунту?" }, - "logInAttemptBy": { - "message": "Попытка авторизации через $EMAIL$", + "accessAttemptBy": { + "message": "Попытка доступа $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Время" }, - "confirmLogIn": { - "message": "Подтвердить вход" + "confirmAccess": { + "message": "Подтвердить доступ" }, - "denyLogIn": { - "message": "Запретить вход" + "denyAccess": { + "message": "Отказать в доступе" }, "logInConfirmedForEmailOnDevice": { "message": "Вход подтвержден для $EMAIL$ на $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "Этот запрос больше не действителен." }, - "confirmLoginAtemptForMail": { - "message": "Подтвердите попытку входа для $EMAIL$", + "confirmAccessAttempt": { + "message": "Подтвердить попытку доступа $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Запрошено разрешение на вход" }, + "accountAccessRequested": { + "message": "Запрошен доступ к аккаунту" + }, "creatingAccountOn": { "message": "Создание аккаунта" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Обнаружен слабый пароль, найденный в утечке данных. Используйте надежный и уникальный пароль для защиты вашего аккаунта. Вы уверены, что хотите использовать этот пароль?" }, + "useThisPassword": { + "message": "Использовать этот пароль" + }, + "useThisUsername": { + "message": "Использовать это имя пользователя" + }, "checkForBreaches": { "message": "Проверять известные случаи утечки данных для этого пароля" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Запросить одобрение администратора" }, - "approveWithMasterPassword": { - "message": "Одобрить с мастер-паролем" - }, "region": { "message": "Регион" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Ваш запрос был отправлен администратору." }, - "youWillBeNotifiedOnceApproved": { - "message": "Вас уведомят об одобрении." - }, "troubleLoggingIn": { "message": "Не удалось войти?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Для вашего аккаунта требуется двухэтапная аутентификация Duo." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Для вашего аккаунта требуется двухэтапная аутентификация Duo. Выполните следующие действия, чтобы завершить авторизацию." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Следуйте указаниям ниже, чтобы завершить авторизацию." + }, "launchDuo": { "message": "Запустить Duo в браузере" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "Не удалось найти свободные порты для авторизации SSO." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Биометрическая разблокировка недоступна, поскольку сначала требуется разблокировка с помощью PIN-кода или пароля." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Биометрическая разблокировка в настоящее время недоступна." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Биометрическая разблокировка недоступна из-за неправильно настроенных системных файлов." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Биометрическая разблокировка недоступна из-за неправильно настроенных системных файлов." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Биометрическая разблокировка недоступна, потому что она не включена для $EMAIL$ в приложении Bitwarden для компьютера.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Биометрическая разблокировка в настоящее время недоступна по неизвестной причине." + }, "authorize": { "message": "Разрешить" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Подтвердить использование ключа SSH" }, + "agentForwardingWarningTitle": { + "message": "Предупреждение: Переадресация агента" + }, + "agentForwardingWarningText": { + "message": "Этот запрос поступает с удаленного устройства, на котором вы авторизовались" + }, "sshkeyApprovalMessageInfix": { "message": "запрашивает доступ к" }, + "sshkeyApprovalMessageSuffix": { + "message": "чтобы" + }, + "sshActionLogin": { + "message": "авторизоваться на сервере" + }, + "sshActionSign": { + "message": "подписать сообщение" + }, + "sshActionGitSign": { + "message": "подписать git коммит" + }, "unknownApplication": { "message": "Приложение" }, - "sshKeyPasswordUnsupported": { - "message": "Импорт защищенных паролем ключей SSH пока не поддерживается" - }, "invalidSshKey": { "message": "Ключ SSH недействителен" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Импорт ключа из буфера обмена" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "Ключ SSH успешно импортирован" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Изменить email аккаунта" + }, + "allowScreenshots": { + "message": "Разрешить захват экрана" + }, + "allowScreenshotsDesc": { + "message": "Разрешить приложению Bitwarden захват экрана для скриншотов и просмотра в сеансах удаленного рабочего стола. Отключение параметра запретит доступ на некоторых внешних дисплеях." + }, + "confirmWindowStillVisibleTitle": { + "message": "Окно подтверждения остается видимым" + }, + "confirmWindowStillVisibleContent": { + "message": "Пожалуйста, подтвердите, что окно все еще видно." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Необходимо обновить расширение" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "Используемое вами расширение браузера устарело. Пожалуйста, обновите его или отключите проверку интеграции браузера с помощью отпечатка пальца в настройках приложения для компьютера." + }, + "changeAtRiskPassword": { + "message": "Изменить пароль, подверженный риску" } } diff --git a/apps/desktop/src/locales/si/messages.json b/apps/desktop/src/locales/si/messages.json index 51625639dda..cb837ea4f76 100644 --- a/apps/desktop/src/locales/si/messages.json +++ b/apps/desktop/src/locales/si/messages.json @@ -249,6 +249,20 @@ "error": { "message": "දෝෂය" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "දුරුතු" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Number of words" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Master password hint" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -871,6 +906,15 @@ "message": "Verify with Duo Security for your organization using the Duo Mobile app, SMS, phone call, or U2F security key.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Self-hosted environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Custom environment" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "Server URL" }, @@ -956,6 +997,9 @@ "no": { "message": "නැහැ" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Overwrite password" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Purchase Premium" }, - "premiumPurchaseAlert": { - "message": "You can purchase premium membership on the bitwarden.com web vault. Do you want to visit the website now?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Require password or PIN on app start" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Recommended for security." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Hide my email address from recipients." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, "rememberEmail": { "message": "Remember email" }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "Toggle character count", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Are you trying to log in?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Login attempt by $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Time" }, - "confirmLogIn": { - "message": "Confirm login" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Deny login" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Login confirmed for $EMAIL$ on $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "confirmLoginAtemptForMail": { - "message": "Confirm login attempt for $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Log in requested" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "region": { "message": "Region" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index cec06c3c028..7eaa1026c00 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Chyba" }, + "decryptionError": { + "message": "Chyba dešifrovania" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nedokázal dešifrovať nižšie uvedené položky trezoru." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktujte zákaznícku podporu,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "aby ste predišli ďalším stratám údajov.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Január" }, @@ -529,10 +543,6 @@ "message": "Zahrnúť špeciálne znaky", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Počet slov" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Prihlásenie do Bitwardenu" }, + "enterTheCodeSentToYourEmail": { + "message": "Zadajte kód zaslaný na váš e-mail" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Zadajte kód z overovacej aplikácie" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Stlačte YubiKey na overenie" + }, "logInWithPasskey": { "message": "Prihlásiť sa s prístupovým kľúčom" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Nápoveď k hlavnému heslu" }, + "passwordStrengthScore": { + "message": "Sila hesla $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Pripojte sa k organizácii" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Zadajte e-mailovú adresu účtu a zašleme vám nápoveď k heslu" }, - "passwordHint": { - "message": "Nápoveď k heslu" - }, - "enterEmailToGetHint": { - "message": "Zadajte e-mailovú adresu na zaslanie nápovede k vášmu hlavnému heslu." - }, "getMasterPasswordHint": { "message": "Získať nápoveď k hlavnému heslu" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "Overenie bolo zrušené alebo trvalo príliš dlho. Skúste to znova." }, + "openInNewTab": { + "message": "Otvoriť v novej karte" + }, "invalidVerificationCode": { "message": "Neplatný verifikačný kód" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Zapamätaj si ma" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Nepýtať sa znova na tomto zariadení 30 dní" + }, "sendVerificationCodeEmailAgain": { "message": "Znovu zaslať overovací kód e-mailom" }, "useAnotherTwoStepMethod": { "message": "Použiť inú dvojstupňovú metódu prihlásenia" }, + "selectAnotherMethod": { + "message": "Vyberte iný spôsob", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Použiť obnovovací kód" + }, "insertYubiKey": { "message": "Vložte váš YubiKey do USB portu počítača a stlačte jeho tlačidlo." }, @@ -871,6 +906,15 @@ "message": "Overiť sa prostredníctvom Duo Security vašej organizácie použitím Duo Mobile aplikácie, SMS, telefonátu alebo U2F bezpečnostným kľúčom.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Overte svoju totožnosť" + }, + "weDontRecognizeThisDevice": { + "message": "Toto zariadenie nepoznáme. Na overenie vašej totožnosti zadajte kód, ktorý bol zaslaný na váš e-mail." + }, + "continueLoggingIn": { + "message": "Pokračovať v prihlasovaní" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Možnosti dvojstupňového prihlásenia" }, + "selectTwoStepLoginMethod": { + "message": "Vyberte metódu dvojstupňového prihlásenia" + }, "selfHostedEnvironment": { "message": "Prevádzkované vo vlastnom prostredí" }, - "selfHostedEnvironmentFooter": { - "message": "Zadajte URL Bitwarden inštalácie, ktorú prevádzkujete vo vlastnom prostredí." - }, "selfHostedBaseUrlHint": { "message": "Zadajte základnú URL adresu lokálne hosťovanej inštalácie Bitwarden. Napríklad: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Vlastné prostredie" }, - "customEnvironmentFooter": { - "message": "Pre pokročilých používateľov. Môžete špecifikovať základnú URL pre každú službu nezávisle." - }, "baseUrl": { "message": "URL servera" }, @@ -956,6 +997,9 @@ "no": { "message": "Nie" }, + "location": { + "message": "Poloha" + }, "overwritePassword": { "message": "Prepísať heslo" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Zakúpiť Prémium" }, - "premiumPurchaseAlert": { - "message": "Svoje prémiové členstvo môžete zakúpiť vo webovom trezore bitwarden.com. Chcete navštíviť túto stránku teraz?" - }, "premiumPurchaseAlertV2": { "message": "Prémiové členstvo si môžete zakúpiť v nastaveniach svojho účtu vo webovej aplikácii Bitwarden." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Pri spustení aplikácie vyžadovať heslo alebo PIN" }, + "requirePasswordWithoutPinOnStart": { + "message": "Pri spustení aplikácie vyžadovať heslo" + }, "recommendedForSecurity": { "message": "Odporúča sa z hľadiska bezpečnosti." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Overiť cez WebAuthn" }, + "readSecurityKey": { + "message": "Prečítať bezpečnostný kľúč" + }, + "awaitingSecurityKeyInteraction": { + "message": "Čaká sa na interakciu s bezpečnostným kľúčom..." + }, "hideEmail": { "message": "Skryť moju e-mailovú adresu pred príjemcami." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Použiť doručenú poštu typu catch-all nastavenú na doméne." }, + "useThisEmail": { + "message": "Použiť tento e-mail" + }, "random": { "message": "Náhodné" }, @@ -2552,10 +2605,10 @@ "message": "Prehľadávať môj trezor" }, "forwardedEmail": { - "message": "Alias preposlaného e-mailu" + "message": "Alias presmerovaného e-mailu" }, "forwardedEmailDesc": { - "message": "Vytvoriť e-mailový alias pomocou externej služby preposielania." + "message": "Vytvoriť e-mailový alias pomocou externej služby presmerovania." }, "forwarderDomainName": { "message": "E-mailová doména", @@ -2580,11 +2633,11 @@ } }, "forwarderGeneratedBy": { - "message": "Vygenerované s Bitwarden.", + "message": "Vygenerované Bitwardenom.", "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Webstránka: $WEBSITE$. Vygenerované s Bitwarden.", + "message": "Webstránka: $WEBSITE$. Vygenerované Bitwardenon.", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "Služba $SERVICENAME$ odmietla vašu žiadosť. Obráťte sa na svojho poskytovateľa služieb a požiadajte o pomoc.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "Služba $SERVICENAME$ odmietla vašu žiadosť: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Nepodarilo sa získať ID maskovaného e-mailového účtu $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Prihlásenie pomocou hlavného hesla" }, - "loggingInAs": { - "message": "Prihlasujete sa ako" - }, "rememberEmail": { "message": "Zapamätať si e-mail" }, - "notYou": { - "message": "Nie ste to vy?" - }, "newAroundHere": { "message": "Ste tu nový?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Iniciované prihlásenie" }, + "logInRequestSent": { + "message": "Požiadavka bola odoslaná" + }, "notificationSentDevice": { "message": "Do vášho zariadenia bolo odoslané upozornenie." }, "aNotificationWasSentToYourDevice": { "message": "Do vášho zariadenia bolo odoslané upozornenie" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Uistite sa, že je váš účet odomknutý a fráza odtlačku prsta sa zhoduje s frázou na druhom zariadení" + "notificationSentDevicePart1": { + "message": "Odomknúť Bitwarden vo svojom zariadení alebo vo " + }, + "notificationSentDeviceAnchor": { + "message": "webovej aplikácii" + }, + "notificationSentDevicePart2": { + "message": "Pred schválením sa uistite, že sa odtlačok prístupovej frázy zhoduje s tou uvedenou nižšie." }, "needAnotherOptionV1": { "message": "Potrebujete inú možnosť?" @@ -2759,11 +2839,11 @@ "message": "Prepnúť počítadlo znakov", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Snažíte sa prihlásiť?" + "areYouTryingToAccessYourAccount": { + "message": "Snažíte sa získať prístup k svojmu účtu?" }, - "logInAttemptBy": { - "message": "Pokus o prihlásenie pomocou $EMAIL$", + "accessAttemptBy": { + "message": "Pokus o prístup z $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Čas" }, - "confirmLogIn": { - "message": "Potvrdiť prihlásenie" + "confirmAccess": { + "message": "Potvrdiť prístup" }, - "denyLogIn": { - "message": "Odmietnuť prihlásenie" + "denyAccess": { + "message": "Zamietnuť prístup" }, "logInConfirmedForEmailOnDevice": { "message": "Potvrdené prihlásenie pomocou $EMAIL$ na $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "Táto žiadosť už nie je platná." }, - "confirmLoginAtemptForMail": { - "message": "Potvrdiť pokus o prihlásenie pomocou $EMAIL$", + "confirmAccessAttempt": { + "message": "Potvrďte pokus o prístup pre $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Požadované prihlásenie" }, + "accountAccessRequested": { + "message": "Žiadosť o prístup k účtu" + }, "creatingAccountOn": { "message": "Vytváranie účtu na" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Nájdené slabé heslo v uniknuných údajoch. Na ochranu svojho účtu používajte silné a jedinečné heslo. Naozaj chcete používať toto heslo?" }, + "useThisPassword": { + "message": "Použiť toto heslo" + }, + "useThisUsername": { + "message": "Použiť toto používateľské meno" + }, "checkForBreaches": { "message": "Skontrolovať známe úniky údajov pre toto heslo" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Žiadosť o schválenie správcom" }, - "approveWithMasterPassword": { - "message": "Schváliť pomocou hlavného hesla" - }, "region": { "message": "Región" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Vaša žiadosť bola odoslaná správcovi." }, - "youWillBeNotifiedOnceApproved": { - "message": "Po schválení budete informovaný." - }, "troubleLoggingIn": { "message": "Máte problémy s prihlásením?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Pre váš účet je potrebné dvojstupňové prihlásenie Duo." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Pre váš účet sa vyžaduje dvojstupňové prihlásenie Duo. Na dokončenie prihlásenia postupujte podľa pokynov." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Na dokončenie prihlásenia postupujte podľa pokynov." + }, "launchDuo": { "message": "Spustiť Duo v prehliadači" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "Pre prihlásenie SSO sa nepodarilo nájsť žiadne voľné porty." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Odomykanie biometrickými údajmi je nedostupné pretože je najskôr potrebné odomykanie pomocou PIN alebo hesla." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Odomykanie biometrickými údajmi je momentálne nedostupné." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Odomykanie biometrickými údajmi je nedostupné v dôsledku zle nastavených systémových súborov." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Odomykanie biometrickými údajmi je nedostupné v dôsledku zle nastavených systémových súborov." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Odomykanie biometrickými údajmi je nedostupné pretože nie je povolené pre $EMAIL$ v aplikácii Bitwarden pre desktop.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Odomykanie biometrickými údajmi je z neznámych dôvodov nedostupné." + }, "authorize": { "message": "Autorizovať" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Potvrdiť použitie kľúča SSH" }, + "agentForwardingWarningTitle": { + "message": "Upozornenie: Presmerovanie agenta" + }, + "agentForwardingWarningText": { + "message": "Táto požiadavka prichádza zo vzdialeného zariadenia, do ktorého ste prihlásení" + }, "sshkeyApprovalMessageInfix": { "message": "žiada o prístup k" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "podpísať správu" + }, + "sshActionGitSign": { + "message": "podpísať git commit" + }, "unknownApplication": { "message": "Aplikácia" }, - "sshKeyPasswordUnsupported": { - "message": "Importovanie kľúčov SSH chránených heslom zatiaľ nie je podporované" - }, "invalidSshKey": { "message": "Kľúč SSH je neplatný" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Importovať kľúč zo schránky" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "Kľúč SSH bol úspešne importovaný" }, "fileSavedToDevice": { @@ -3411,7 +3539,7 @@ "message": "Pripomenúť neskôr" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "Máte spoľahlivý prístup k svojmu e-mailu, $EMAIL$?", + "message": "Máte zaručený prístup k e-mailu $EMAIL$?", "placeholders": { "email": { "content": "$1", @@ -3423,12 +3551,33 @@ "message": "Nie, nemám" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Áno, mám spoľahlivý prístup k svojmu e-mailu" + "message": "Áno, mám zaručený prístup k e-mailu" }, "turnOnTwoStepLogin": { "message": "Zapnúť dvojstupňové prihlásenie" }, "changeAcctEmail": { "message": "Zmeniť e-mail účtu" + }, + "allowScreenshots": { + "message": "Povoliť snímanie obrazovky" + }, + "allowScreenshotsDesc": { + "message": "Umožní zachytenie Bitwardenu na snímkach obrazovky a jej zobrazenie v reláciách vzdialenej plochy. Zakázanie tejto funkcie zabráni prístupu na niektoré externé displeje." + }, + "confirmWindowStillVisibleTitle": { + "message": "Potvrdiť viditeľnosť okna" + }, + "confirmWindowStillVisibleContent": { + "message": "Potvrďte, že okno je stále viditeľné." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Rozšírenie musíte aktualizovať" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "Rozšírenie prehliadača, ktoré používate, je zastarané. Aktualizujte ho alebo zakážte overenie odtlačkov integrácie prehliadača v nastaveniach desktopovej aplikácie." + }, + "changeAtRiskPassword": { + "message": "Zmeniť rizikové heslá" } } diff --git a/apps/desktop/src/locales/sl/messages.json b/apps/desktop/src/locales/sl/messages.json index 1973f6c017b..3987587828a 100644 --- a/apps/desktop/src/locales/sl/messages.json +++ b/apps/desktop/src/locales/sl/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Napaka" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januar" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Število besed" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Master password hint" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Namig za geslo" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Neveljavna verifikacijska koda" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Zapomni si me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Ponovno pošlji verifikacijsko kodo" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -871,6 +906,15 @@ "message": "Verify with Duo Security for your organization using the Duo Mobile app, SMS, phone call, or U2F security key.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Okolje z lastnim gostovanjem" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Okolje po meri" }, - "customEnvironmentFooter": { - "message": "Za napredne uporabnike. Lahko specificirate osnovni URL, ločeno za vsako storitev." - }, "baseUrl": { "message": "URL naslov strežnika" }, @@ -956,6 +997,9 @@ "no": { "message": "Ne" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Prepiši geslo" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Purchase Premium" }, - "premiumPurchaseAlert": { - "message": "You can purchase premium membership on the bitwarden.com web vault. Do you want to visit the website now?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Ob zagonu zahteva geslo ali PIN" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Recommended for security." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Hide my email address from recipients." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, "rememberEmail": { "message": "Remember email" }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "Toggle character count", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Are you trying to log in?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Login attempt by $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Time" }, - "confirmLogIn": { - "message": "Confirm login" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Deny login" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Login confirmed for $EMAIL$ on $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "confirmLoginAtemptForMail": { - "message": "Confirm login attempt for $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Log in requested" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "region": { "message": "Region" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index 9f9dec2dd68..69b45469dc8 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Грешка" }, + "decryptionError": { + "message": "Грешка при декрипцији" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden није могао да декриптује ставке из трезора наведене испод." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Обратите се корисничкој подршци", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "да бисте избегли додатни губитак података.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Јануар" }, @@ -529,10 +543,6 @@ "message": "Укључити специјална слова", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Број речи" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Пријавите се на Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Унесите кôд послат на ваш имејл" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Унесите кôд из апликације за аутентификацију" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Стисните Ваш YubiKey за аутентификацију" + }, "logInWithPasskey": { "message": "Пријавите се са приступним кључем" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Савет главне лозинке" }, + "passwordStrengthScore": { + "message": "Снага лозинкe $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Придружи Организацију" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Унесите имејл свог налога и биће вам послат савет за лозинку" }, - "passwordHint": { - "message": "Савет главне лозинке" - }, - "enterEmailToGetHint": { - "message": "Унесите Ваш имејл да би добили савет за Вашу Главну Лозинку." - }, "getMasterPasswordHint": { "message": "Добити савет за Главну Лозинку" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "Аутентификација је отказана или је трајала предуго. Молим вас, покушајте поново." }, + "openInNewTab": { + "message": "Отвори у новом језичку" + }, "invalidVerificationCode": { "message": "Неисправан верификациони код" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Запамти ме" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Не питајте поново на овом уређају 30 дана" + }, "sendVerificationCodeEmailAgain": { "message": "Поново послати верификациони код на имејл" }, "useAnotherTwoStepMethod": { "message": "Користите другу методу пријављивања у два корака" }, + "selectAnotherMethod": { + "message": "Изаберите другу методу", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Употребите шифру за опоравак" + }, "insertYubiKey": { "message": "Убаците свој YubiKey у УСБ порт рачунара, а затим додирните његово дугме." }, @@ -871,6 +906,15 @@ "message": "Провери са Duo Security за вашу организацију користећи Duo Mobile апликацију, СМС, телефонски позив, или U2F кључ.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Потврдите идентитет" + }, + "weDontRecognizeThisDevice": { + "message": "Не препознајемо овај уређај. Унесите код послат на адресу ваше електронске поште да би сте потврдили ваш идентитет." + }, + "continueLoggingIn": { + "message": "Настави са пријављивањем" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Опције дво-коракне пријаве" }, + "selectTwoStepLoginMethod": { + "message": "Одабрати методу пријављивања у два корака" + }, "selfHostedEnvironment": { "message": "Самостално окружење" }, - "selfHostedEnvironmentFooter": { - "message": "Наведите основни УРЛ ваше локалне Bitwarden инсталације." - }, "selfHostedBaseUrlHint": { "message": "Наведите основну УРЛ адресу вашег локалног хостовања Bitwarden-а. Пример: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Прилагођено окружење" }, - "customEnvironmentFooter": { - "message": "За напредне кориснике. Можете да одредите независно основни УРЛ сваког сервиса." - }, "baseUrl": { "message": "УРЛ Сервера" }, @@ -956,6 +997,9 @@ "no": { "message": "Не" }, + "location": { + "message": "Локација" + }, "overwritePassword": { "message": "Промени лозинку" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Купити премијум" }, - "premiumPurchaseAlert": { - "message": "Можете купити премијум претплату на bitwarden.com. Да ли желите да посетите веб сајт сада?" - }, "premiumPurchaseAlertV2": { "message": "Можете да купите Премиум у подешавањима налога у веб апликацији Bitwarden." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Захтевај лозинку или ПИН при покретању" }, + "requirePasswordWithoutPinOnStart": { + "message": "Захтевај лозинку при покретању" + }, "recommendedForSecurity": { "message": "Препоручује се за сигурност." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "WebAutn аутентификација" }, + "readSecurityKey": { + "message": "Читај сигурносни кључ" + }, + "awaitingSecurityKeyInteraction": { + "message": "Чека се интеракција сигурносног кључа..." + }, "hideEmail": { "message": "Сакриј моју е-адресу од примаоца." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Користите подешено catch-all пријемно сандуче вашег домена." }, + "useThisEmail": { + "message": "Користи овај имејл" + }, "random": { "message": "Случајно" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ одбио ваш захтев. Обратите се свом провајдеру сервиса за помоћ.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ одбио ваш захтев: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Није могуће добити ИД налога маскираног имејла $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Пријавите се са главном лозинком" }, - "loggingInAs": { - "message": "Пријављивање као" - }, "rememberEmail": { "message": "Запамти имејл" }, - "notYou": { - "message": "Не ти?" - }, "newAroundHere": { "message": "Нов овде?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Пријава је покренута" }, + "logInRequestSent": { + "message": "Захтев је послат" + }, "notificationSentDevice": { "message": "Обавештење је послато на ваш уређај." }, "aNotificationWasSentToYourDevice": { "message": "Обавештење је послато на ваш уређај" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Уверите се да је ваш налог откључан и да се фраза отиска подудара на другом уређају" + "notificationSentDevicePart1": { + "message": "Откључај Bitwarden на твом уређају или на " + }, + "notificationSentDeviceAnchor": { + "message": "веб апликација" + }, + "notificationSentDevicePart2": { + "message": "Потврдите да се фраза отиска поклапа са овом испод пре одобравања." }, "needAnotherOptionV1": { "message": "Треба Вам друга опција?" @@ -2759,11 +2839,11 @@ "message": "Пребаци бројање слова", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Да ли покушавате да се пријавите?" + "areYouTryingToAccessYourAccount": { + "message": "Да ли покушавате да приступите вашем налогу?" }, - "logInAttemptBy": { - "message": "Покушај пријаве од $EMAIL$", + "accessAttemptBy": { + "message": "Покушај приступа са $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Време" }, - "confirmLogIn": { - "message": "Потврди пријављивање" + "confirmAccess": { + "message": "Потврдите приступ" }, - "denyLogIn": { - "message": "Одбиј пријављивање" + "denyAccess": { + "message": "Одбити приступ" }, "logInConfirmedForEmailOnDevice": { "message": "Пријава потврђена за $EMAIL$ на $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "Овај захтев више не важи." }, - "confirmLoginAtemptForMail": { - "message": "Потврдити пријаву за $EMAIL$", + "confirmAccessAttempt": { + "message": "Потврдити приступ за $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Захтев пријаве" }, + "accountAccessRequested": { + "message": "Приступ рачуна је затражен" + }, "creatingAccountOn": { "message": "Креирај налог на" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Идентификована је слаба лозинка и пронађена у упаду података. Користите јаку и јединствену лозинку да заштитите свој налог. Да ли сте сигурни да желите да користите ову лозинку?" }, + "useThisPassword": { + "message": "Употреби ову лозинку" + }, + "useThisUsername": { + "message": "Употреби ово корисничко име" + }, "checkForBreaches": { "message": "Проверите познате упада података за ову лозинку" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Затражити одобрење администратора" }, - "approveWithMasterPassword": { - "message": "Одобрити са главном лозинком" - }, "region": { "message": "Регион" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Ваш захтев је послат вашем администратору." }, - "youWillBeNotifiedOnceApproved": { - "message": "Бићете обавештени када буде одобрено." - }, "troubleLoggingIn": { "message": "Имате проблема са пријављивањем?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo пријава у два корака је потребна за ваш налог." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "За ваш налог је потребан два корака. Следите наведене кораке да бисте завршили пријављивање." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Следите наведене кораке да бисте завршили пријављивање." + }, "launchDuo": { "message": "Покренути Duo у претраживачу" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "Нису пронађени портови за SSO пријаву." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Биометријско откључавање није доступно јер је пре тога потребно унети ПИН или лозинку за откључавање." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Биометријско откључавање тренутно није доступно." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Биометријско откључавање није доступно због лоше подешених системских датотека." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Биометријско откључавање није доступно због лоше подешених системских датотека." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Биометријско откључавање није доступно јер није омогућено за $EMAIL$ у Bitwarden апликацији на рачунару.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Биометријско откључавање није доступно из непознатог разлога." + }, "authorize": { "message": "Ауторизуј" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Потврдите употребу SSH кључа" }, + "agentForwardingWarningTitle": { + "message": "Упозорење: Прослеђивање агента" + }, + "agentForwardingWarningText": { + "message": "Овај захтев долази са удаљеног уређаја на којим сте пријављени" + }, "sshkeyApprovalMessageInfix": { "message": "тражи приступ" }, + "sshkeyApprovalMessageSuffix": { + "message": "да бисте" + }, + "sshActionLogin": { + "message": "се аутентификали на серверу" + }, + "sshActionSign": { + "message": "потписати поруку" + }, + "sshActionGitSign": { + "message": "потписати „git commit“" + }, "unknownApplication": { "message": "Апликација" }, - "sshKeyPasswordUnsupported": { - "message": "Увоз лозинке заштићене SSH кључом још увек није подржано" - }, "invalidSshKey": { "message": "SSH кључ је неважећи" }, @@ -3389,29 +3517,29 @@ "importSshKeyFromClipboard": { "message": "Увезите кључ из оставе" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH кључ је успешно увезен" }, "fileSavedToDevice": { "message": "Датотека је сачувана на уређају. Управљајте преузимањима са свог уређаја." }, "importantNotice": { - "message": "Important notice" + "message": "Важно обавештење" }, "setupTwoStepLogin": { - "message": "Set up two-step login" + "message": "Поставити дво-степенску пријаву" }, "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + "message": "Bitwarden ће послати кôд на имејл вашег налога за верификовање пријављивања са нових уређаја почевши од фебруара 2025." }, "newDeviceVerificationNoticeContentPage2": { - "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + "message": "Можете да подесите пријаву у два корака као алтернативни начин да заштитите свој налог или да промените свој имејл у један који можете да приступите." }, "remindMeLater": { - "message": "Remind me later" + "message": "Подсети ме касније" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", + "message": "Да ли имате поуздан приступ својим имејлом, $EMAIL$?", "placeholders": { "email": { "content": "$1", @@ -3420,15 +3548,36 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "Не, ненам" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" + "message": "Да, могу поуздано да приступим овим имејлом" }, "turnOnTwoStepLogin": { - "message": "Turn on two-step login" + "message": "Упалити дво-степенску пријаву" }, "changeAcctEmail": { - "message": "Change account email" + "message": "Променити имејл налога" + }, + "allowScreenshots": { + "message": "Дозволи снимање екрана" + }, + "allowScreenshotsDesc": { + "message": "Дозволите да Bitwarden апликација прихвата скриншот и преглед у удаљених радних површина. Онемогућавање то ће спречити приступ неким спољним дисплејима." + }, + "confirmWindowStillVisibleTitle": { + "message": "Потврдите да је прозор и даље видљив" + }, + "confirmWindowStillVisibleContent": { + "message": "Потврдите да је прозор и даље видљив." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Неопходна надоградња додатка" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "Додатак који користите је застарело. Молимо ажурирајте га или онемогућите валидацију отисача претраживача Интеграција на поставки апликација за компјутер." + }, + "changeAtRiskPassword": { + "message": "Променити ризичну лозинку" } } diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json index c9a463246dd..1303243a8cc 100644 --- a/apps/desktop/src/locales/sv/messages.json +++ b/apps/desktop/src/locales/sv/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Fel" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januari" }, @@ -529,10 +543,6 @@ "message": "Inkludera specialtecken", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Antal ord" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Logga in på Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Logga in med nyckel" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Huvudlösenordsledtråd" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Lösenordsledtråd" - }, - "enterEmailToGetHint": { - "message": "Ange din e-postadress för att få din huvudlösenordsledtråd skickad till dig." - }, "getMasterPasswordHint": { "message": "Hämta huvudlösenordsledtråd" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Ogiltig verifieringskod" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Kom ihåg mig" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Skicka e-postmeddelandet med verifieringskoden igen" }, "useAnotherTwoStepMethod": { "message": "Använd en annan metod för tvåstegsverifiering" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Anslut din YubiKey till datorns USB-port och tryck sedan på dess knapp." }, @@ -871,6 +906,15 @@ "message": "Verifiera med Duo Security för din organisation genom att använda Duo Mobile-appen, SMS, telefonsamtal eller en U2F-säkerhetsnyckel.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Alternativ för tvåstegsverifiering" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Egen-hostad miljö" }, - "selfHostedEnvironmentFooter": { - "message": "Ange bas-URL:en för din \"on-premises\"-hostade Bitwarden-installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Anpassad miljö" }, - "customEnvironmentFooter": { - "message": "För avancerade användare. Du kan ange bas-URL:en för varje tjänst oberoende av varandra." - }, "baseUrl": { "message": "Server-URL" }, @@ -956,6 +997,9 @@ "no": { "message": "Nej" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Skriv över lösenord" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Köp Premium" }, - "premiumPurchaseAlert": { - "message": "Du kan köpa premium-medlemskap i Bitwardens webbvalv. Vill du besöka webbplatsen nu?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Kräv lösenord eller PIN-kod vid appstart" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Rekommenderas för säkerhet." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Autentisera WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Dölj min e-postadress för mottagare." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Använd din domäns konfigurerade catch-all inkorg." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Slumpmässig" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Gick inte att få $SERVICENAME$ maskerat e-postkonto-ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Logga in med huvudlösenord" }, - "loggingInAs": { - "message": "Loggar in som" - }, "rememberEmail": { "message": "Kom ihåg e-postadress" }, - "notYou": { - "message": "Är det inte du?" - }, "newAroundHere": { "message": "Är du ny här?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Inloggning påbörjad" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "En avisering har skickats till din enhet." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "Växla teckenantal", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Försöker du logga in?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Inloggningsförsök av $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Tid" }, - "confirmLogIn": { - "message": "Bekräfta inloggning" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Neka inloggning" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Inloggning bekräftad för $EMAIL$ på $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "Denna begäran är inte längre giltig." }, - "confirmLoginAtemptForMail": { - "message": "Bekräfta inloggningsförsök för $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Inloggning begärd" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Skapa konto på" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Lösenordet är svagt och avslöjades vid ett dataintrång. Använd ett starkt och unikt lösenord för att skydda ditt konto. Är det säkert att du vill använda detta lösenord?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Kontrollera kända dataintrång för detta lösenord" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Be om godkännande från administratör" }, - "approveWithMasterPassword": { - "message": "Godkänn med huvudlösenord" - }, "region": { "message": "Region" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Din begäran har skickats till din administratör." }, - "youWillBeNotifiedOnceApproved": { - "message": "Du kommer att meddelas vid godkännande." - }, "troubleLoggingIn": { "message": "Problem med att logga in?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo tvåstegsverifiering krävs för ditt konto." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Starta Duo i webbläsaren" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "begär tillgång till" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "En applikation" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "SSH-nyckeln är ogiltig" }, @@ -3389,8 +3517,8 @@ "importSshKeyFromClipboard": { "message": "Importera nyckel från urklipp" }, - "sshKeyPasted": { - "message": "SSH-nyckel har importerats" + "sshKeyImported": { + "message": "SSH key imported successfully" }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/te/messages.json b/apps/desktop/src/locales/te/messages.json index 323d0cd3f7b..f93db44aa69 100644 --- a/apps/desktop/src/locales/te/messages.json +++ b/apps/desktop/src/locales/te/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Number of words" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Master password hint" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -871,6 +906,15 @@ "message": "Verify with Duo Security for your organization using the Duo Mobile app, SMS, phone call, or U2F security key.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Self-hosted environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Custom environment" }, - "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." - }, "baseUrl": { "message": "Server URL" }, @@ -956,6 +997,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Overwrite password" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Purchase Premium" }, - "premiumPurchaseAlert": { - "message": "You can purchase premium membership on the bitwarden.com web vault. Do you want to visit the website now?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Require password or PIN on app start" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Recommended for security." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Hide my email address from recipients." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, "rememberEmail": { "message": "Remember email" }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "Toggle character count", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Are you trying to log in?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Login attempt by $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Time" }, - "confirmLogIn": { - "message": "Confirm login" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Deny login" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Login confirmed for $EMAIL$ on $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "confirmLoginAtemptForMail": { - "message": "Confirm login attempt for $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Log in requested" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "region": { "message": "Region" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/th/messages.json b/apps/desktop/src/locales/th/messages.json index 16961afcc57..cb9df027ad7 100644 --- a/apps/desktop/src/locales/th/messages.json +++ b/apps/desktop/src/locales/th/messages.json @@ -249,6 +249,20 @@ "error": { "message": "ข้อผิดพลาด" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "มกราคม" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Number of Words" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Master password hint" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Join organization" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "คำใบ้รหัสผ่าน" - }, - "enterEmailToGetHint": { - "message": "ป้อนที่อยู่อีเมลบัญชีของคุณเพื่อรับคำใบ้รหัสผ่านหลักของคุณ" - }, "getMasterPasswordHint": { "message": "รับคำใบ้เกี่ยวกับรหัสผ่านหลักของคุณ" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "รหัสการตรวจสอบสิทธิ์ไม่ถูกต้อง" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "จดจำการเข้าระบบของฉัน" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "ส่งอีเมล์ยืนยันรหัสอีกครั้ง" }, "useAnotherTwoStepMethod": { "message": "ใช้วิธีลงชื่อเข้าใช้แบบสองขั้นตอนวิธีอื่น" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "เสียบ YubiKey ของคุณเข้ากับพอร์ต USB ของคอมพิวเตอร์ จากนั้นแตะปุ่ม" }, @@ -871,6 +906,15 @@ "message": "Verify with Duo Security for your organization using the Duo Mobile app, SMS, phone call, or U2F security key.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "ตัวเลือกการเข้าสู่ระบบแบบสองขั้นตอน" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Self-hosted Environment" }, - "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premise hosted bitwarden installation." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Custom Environment" }, - "customEnvironmentFooter": { - "message": "สำหรับผู้ใช้ขั้นสูง คุณสามารถระบุ URL พื้นฐานของแต่ละบริการแยกกันได้" - }, "baseUrl": { "message": "URL ของเซิร์ฟเวอร์" }, @@ -956,6 +997,9 @@ "no": { "message": "ไม่ใช่" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "เขียนทับรหัสผ่าน" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Purchase Premium" }, - "premiumPurchaseAlert": { - "message": "You can purchase premium membership on the bitwarden.com web vault. Do you want to visit the website now?" - }, "premiumPurchaseAlertV2": { "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Require password or PIN on app start" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Recommended for security." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Hide my email address from recipients." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Log in with master password" }, - "loggingInAs": { - "message": "Logging in as" - }, "rememberEmail": { "message": "Remember email" }, - "notYou": { - "message": "Not you?" - }, "newAroundHere": { "message": "New around here?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Login initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "A notification has been sent to your device." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "Toggle character count", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Are you trying to log in?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Login attempt by $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Time" }, - "confirmLogIn": { - "message": "Confirm login" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Deny login" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Login confirmed for $EMAIL$ on $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." }, - "confirmLoginAtemptForMail": { - "message": "Confirm login attempt for $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Log in requested" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "region": { "message": "Region" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index 8357f8616bc..55b3b6059b7 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Hata" }, + "decryptionError": { + "message": "Şifre çözme sorunu" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Ocak" }, @@ -529,10 +543,6 @@ "message": "Özel karakterleri dahil et", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Kelime sayısı" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Bitwarden'a giriş yapın" }, + "enterTheCodeSentToYourEmail": { + "message": "E-posta adresinize gönderilen kodu girin" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Kimlik doğrulama uygulamanızdaki kodu girin" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Geçiş anahtarıyla giriş yap" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Ana parola ipucu" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Kuruluşa katıl" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Hesabınızın e-posta adresini girdiğinizde parola ipucunuz size gönderilecektir" }, - "passwordHint": { - "message": "Parola ipucu" - }, - "enterEmailToGetHint": { - "message": "Ana parola ipucunu almak için hesabınızın e-posta adresini girin." - }, "getMasterPasswordHint": { "message": "Ana parola ipucunu al" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "Kimlik doğrulama iptal edildi ve çok uzun sürdü. Lütfen yeniden deneyin." }, + "openInNewTab": { + "message": "Yeni sekmede aç" + }, "invalidVerificationCode": { "message": "Geçersiz doğrulama kodu" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Beni hatırla" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Doğrulama kodunu yeniden e-postala" }, "useAnotherTwoStepMethod": { "message": "Başka bir iki aşamalı giriş yöntemini kullan" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "YubiKey'i bilgisayarınızın USB portuna takın, ardından düğmesine dokunun." }, @@ -871,6 +906,15 @@ "message": "Kuruluşunuzun Duo Security doğrulaması için Duo Mobile uygulaması, SMS, telefon görüşmesi veya U2F güvenlik anahtarını kullanın.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Kimliğinizi doğrulayın" + }, + "weDontRecognizeThisDevice": { + "message": "Bu cihazı tanıyamadık. Kimliğinizi doğrulamak için e-postanıza gönderilen kodu girin." + }, + "continueLoggingIn": { + "message": "Giriş yapmaya devam et" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "İki aşamalı giriş seçenekleri" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Şirket içinde barındırılan ortam" }, - "selfHostedEnvironmentFooter": { - "message": "Kurum içi barındırılan Bitwarden kurulumunuzun taban URL'sini belirtin." - }, "selfHostedBaseUrlHint": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Özel ortam" }, - "customEnvironmentFooter": { - "message": "Üst düzey kullanıcılar için. Her servisin taban URL'sini bağımsız olarak belirleyebilirsiniz." - }, "baseUrl": { "message": "Sunucu URL'si" }, @@ -956,6 +997,9 @@ "no": { "message": "Hayır" }, + "location": { + "message": "Konum" + }, "overwritePassword": { "message": "Parolanın üzerine yaz" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Premium satın al" }, - "premiumPurchaseAlert": { - "message": "Premium üyeliği bitwarden.com web kasası üzerinden satın alabilirsiniz. Şimdi siteye gitmek ister misiniz?" - }, "premiumPurchaseAlertV2": { "message": "Bitwarden web uygulamasındaki hesap ayarlarınızdan Premium abonelik satın alabilirsiniz." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Uygulamayı başlatırken parola veya PIN iste" }, + "requirePasswordWithoutPinOnStart": { + "message": "Uygulamayı başlatırken parola iste" + }, "recommendedForSecurity": { "message": "Güvenliğiniz için önerilir." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "WebAuthn ile doğrula" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "E-posta adresimi alıcılardan gizle." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Alan adınızın tüm iletileri yakalamaya ayarlanmış adresini kullanın." }, + "useThisEmail": { + "message": "Bu e-postayı kullan" + }, "random": { "message": "Rasgele" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Ana parola ile giriş yap" }, - "loggingInAs": { - "message": "Giriş yapılan kullanıcı:" - }, "rememberEmail": { "message": "E-postayı hatırla" }, - "notYou": { - "message": "Siz değil misiniz?" - }, "newAroundHere": { "message": "Buralarda yeni misiniz?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Giriş başlatıldı" }, + "logInRequestSent": { + "message": "İstek gönderildi" + }, "notificationSentDevice": { "message": "Cihazınıza bir bildirim gönderildi." }, "aNotificationWasSentToYourDevice": { "message": "Cihazınıza bir bildirim gönderildi" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Lütfen hesabınızın kilidinin açık olduğundan ve parmak izi ifadesinin diğer cihazla eşleştiğinden emin olun" + "notificationSentDevicePart1": { + "message": "Bitwarden kilidini cihazınızdan veya " + }, + "notificationSentDeviceAnchor": { + "message": "web uygulamasından açın" + }, + "notificationSentDevicePart2": { + "message": "Onay vermeden önce parmak izi ifadesinin aşağıdakiyle eşleştiğini kontrol edin." }, "needAnotherOptionV1": { "message": "Başka bir seçeneğe mi ihtiyacınız var?" @@ -2759,11 +2839,11 @@ "message": "Karakter sayacını aç/kapat", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Giriş yapmaya mı çalışıyorsunuz?" + "areYouTryingToAccessYourAccount": { + "message": "Hesabınıza erişmeye mi çalışıyorsunuz?" }, - "logInAttemptBy": { - "message": "$EMAIL$ giriş denemesi", + "accessAttemptBy": { + "message": "$EMAIL$ erişim denemesi", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Saat" }, - "confirmLogIn": { - "message": "Girişi onayla" + "confirmAccess": { + "message": "Erişimi onayla" }, - "denyLogIn": { - "message": "Girişi reddet" + "denyAccess": { + "message": "Erişimi reddet" }, "logInConfirmedForEmailOnDevice": { "message": "$DEVICE$ cihazında $EMAIL$ girişi onaylandı", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "Bu istek artık geçerli değil." }, - "confirmLoginAtemptForMail": { - "message": "$EMAIL$ giriş isteğini onayla", + "confirmAccessAttempt": { + "message": "$EMAIL$ erişimini onayla", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Giriş istendi" }, + "accountAccessRequested": { + "message": "Hesap erişimi istendi" + }, "creatingAccountOn": { "message": "Hesap oluşturuluyor:" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Hem zayıf hem de veri ihlalinde yer alan bir tespit edildi. Hesabınızı korumak için güçlü bir parola seçin ve o parolayı başka yerlerde kullanmayın. Bu parolayı kullanmak istediğinizden emin misiniz?" }, + "useThisPassword": { + "message": "Bu parolayı kullan" + }, + "useThisUsername": { + "message": "Bu kullanıcı adını kullan" + }, "checkForBreaches": { "message": "Bilinen veri ihlallerinde bu parolayı kontrol et" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Yönetici onayı iste" }, - "approveWithMasterPassword": { - "message": "Ana parola ile onayla" - }, "region": { "message": "Bölge" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "İsteğiniz yöneticinize gönderildi." }, - "youWillBeNotifiedOnceApproved": { - "message": "Onaylandıktan sonra bilgilendirileceksiniz." - }, "troubleLoggingIn": { "message": "Giriş yaparken sorun mu yaşıyorsunuz?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Hesabınız için Duo iki adımlı giriş gereklidir." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Duo'yu tarayıcıda aç" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "SSO girişi için açık port bulunamadı." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Yetkilendir" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Hesap e-postasını değiştir" + }, + "allowScreenshots": { + "message": "Ekran kaydına izin ver" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Uzantıyı güncellemeniz gerekiyor" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "Kullandığınız tarayı uzantısı eskimiş. Lütfen uzantıyı güncelleyin veya masaüstü uygulamasının ayarlarından parmak izi doğrulaması için tarayıcı entegrasyonunu kapatın." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/uk/messages.json b/apps/desktop/src/locales/uk/messages.json index 4103e8b28a6..d5f908e011c 100644 --- a/apps/desktop/src/locales/uk/messages.json +++ b/apps/desktop/src/locales/uk/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Помилка" }, + "decryptionError": { + "message": "Помилка розшифрування" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden не зміг розшифрувати вказані нижче елементи сховища." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Зверніться до служби підтримки клієнтів,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "щоб уникнути втрати даних.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Січень" }, @@ -529,10 +543,6 @@ "message": "Спеціальні символи", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Кількість слів" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Увійти в Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Введіть код, надісланий вам електронною поштою" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Введіть код з програми автентифікації" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Натисніть свій YubiKey для автентифікації" + }, "logInWithPasskey": { "message": "Увійти з ключем доступу" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Підказка для головного пароля" }, + "passwordStrengthScore": { + "message": "Рейтинг надійності пароля $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Приєднатися до організації" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Введіть адресу е-пошти свого облікового запису і вам буде надіслано підказку для пароля" }, - "passwordHint": { - "message": "Підказка для пароля" - }, - "enterEmailToGetHint": { - "message": "Введіть свою адресу е-пошти, щоб отримати підказку для головного пароля." - }, "getMasterPasswordHint": { "message": "Отримати підказку для головного пароля" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "Автентифікацію було скасовано або вона тривала надто довго. Повторіть спробу." }, + "openInNewTab": { + "message": "Відкрити в новій вкладці" + }, "invalidVerificationCode": { "message": "Недійсний код підтвердження" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Запам'ятати мене" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Більше не запитувати на цьому пристрої протягом 30 днів" + }, "sendVerificationCodeEmailAgain": { "message": "Надіслати код підтвердження ще раз" }, "useAnotherTwoStepMethod": { "message": "Інший спосіб двоетапної перевірки" }, + "selectAnotherMethod": { + "message": "Обрати інший спосіб", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Скористайтеся своїм кодом відновлення" + }, "insertYubiKey": { "message": "Вставте свій YubiKey в USB порт комп'ютера, потім торкніться цієї кнопки." }, @@ -871,6 +906,15 @@ "message": "Авторизуйтесь за допомогою Duo Security для вашої організації з використанням програми Duo Mobile, SMS, телефонного виклику, або ключа безпеки U2F.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Підтвердьте свою особу" + }, + "weDontRecognizeThisDevice": { + "message": "Ми не розпізнаємо цей пристрій. Введіть код, надісланий на вашу електронну пошту, щоб підтвердити вашу особу." + }, + "continueLoggingIn": { + "message": "Продовжити вхід" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Налаштування двоетапної перевірки" }, + "selectTwoStepLoginMethod": { + "message": "Виберіть спосіб двоетапної перевірки" + }, "selfHostedEnvironment": { "message": "Середовище власного хостингу" }, - "selfHostedEnvironmentFooter": { - "message": "Вкажіть основну URL-адресу вашого встановлення Bitwarden на власному хостингу." - }, "selfHostedBaseUrlHint": { "message": "Вкажіть основну URL-адресу вашого встановлення Bitwarden на власному хостингу. Зразок: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Власне середовище" }, - "customEnvironmentFooter": { - "message": "Для досвідчених користувачів. Ви можете вказати основну URL-адресу окремо для кожної служби." - }, "baseUrl": { "message": "URL-адреса сервера" }, @@ -956,6 +997,9 @@ "no": { "message": "Ні" }, + "location": { + "message": "Розташування" + }, "overwritePassword": { "message": "Перезаписати пароль" }, @@ -1181,7 +1225,7 @@ "message": "Показувати піктограми вебсайтів" }, "faviconDesc": { - "message": "Показувати впізнаване зображення біля кожного запису." + "message": "Показувати зображення біля кожного запису." }, "enableMinToTray": { "message": "Згортати до системного лотка" @@ -1214,7 +1258,7 @@ "message": "Завжди показувати піктограму в системному лотку." }, "startToTray": { - "message": "Запускати в згорнутому вигляді" + "message": "Запускати в системному лотку" }, "startToTrayDesc": { "message": "Під час першого запуску програми показувати лише піктограму в системному лотку." @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Придбати преміум" }, - "premiumPurchaseAlert": { - "message": "Ви можете передплатити преміум у сховищі на bitwarden.com. Хочете перейти на вебсайт зараз?" - }, "premiumPurchaseAlertV2": { "message": "Ви можете придбати Преміум у налаштуваннях облікового запису вебпрограмі Bitwarden." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Вимагати пароль чи PIN під час запуску" }, + "requirePasswordWithoutPinOnStart": { + "message": "Вимагати пароль під час запуску" + }, "recommendedForSecurity": { "message": "Рекомендовано для безпеки." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Автентифікація WebAuthn" }, + "readSecurityKey": { + "message": "Зчитати ключ безпеки" + }, + "awaitingSecurityKeyInteraction": { + "message": "Очікується взаємодія з ключем безпеки..." + }, "hideEmail": { "message": "Приховувати мою адресу електронної пошти від отримувачів." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Використовуйте свою скриньку вхідних Catch-All власного домену." }, + "useThisEmail": { + "message": "Використати цю е-пошту" + }, "random": { "message": "Випадково" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ відхилив ваш запит. Зверніться до провайдера послуг по допомогу.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ відхилив ваш запит: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Не вдалося отримати ідентифікатор замаскованої е-пошти облікового запису для $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Увійти з головним паролем" }, - "loggingInAs": { - "message": "Вхід у систему як" - }, "rememberEmail": { "message": "Запам'ятати е-пошту" }, - "notYou": { - "message": "Не ви?" - }, "newAroundHere": { "message": "Виконуєте вхід вперше?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Ініційовано вхід" }, + "logInRequestSent": { + "message": "Запит надіслано" + }, "notificationSentDevice": { "message": "Сповіщення було надіслано на ваш пристрій." }, "aNotificationWasSentToYourDevice": { "message": "Сповіщення надіслано на ваш пристрій" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Переконайтеся, що ваш обліковий запис розблоковано і фраза відбитка на іншому пристрої збігається" + "notificationSentDevicePart1": { + "message": "Розблокуйте Bitwarden на своєму пристрої або у " + }, + "notificationSentDeviceAnchor": { + "message": "вебпрограмі" + }, + "notificationSentDevicePart2": { + "message": "Перш ніж підтверджувати, обов'язково перевірте відповідність зазначеної нижче фрази відбитка." }, "needAnotherOptionV1": { "message": "Потрібен інший варіант?" @@ -2759,11 +2839,11 @@ "message": "Перемкнути лічильник символів", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Ви намагаєтесь увійти?" + "areYouTryingToAccessYourAccount": { + "message": "Ви намагаєтесь отримати доступ до свого облікового запису?" }, - "logInAttemptBy": { - "message": "Спроба входу з $EMAIL$", + "accessAttemptBy": { + "message": "Спроба доступу з $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Час" }, - "confirmLogIn": { - "message": "Підтвердити вхід" + "confirmAccess": { + "message": "Підтвердити доступ" }, - "denyLogIn": { - "message": "Заборонити вхід" + "denyAccess": { + "message": "Заборонити доступ" }, "logInConfirmedForEmailOnDevice": { "message": "Підтверджено вхід для $EMAIL$ на $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "Цей запит більше недійсний." }, - "confirmLoginAtemptForMail": { - "message": "Підтвердити спробу входу для $EMAIL$", + "confirmAccessAttempt": { + "message": "Підтвердити спробу доступу для $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Виконано запит входу" }, + "accountAccessRequested": { + "message": "Запитано доступ до облікового запису" + }, "creatingAccountOn": { "message": "Створення облікового запису" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Виявлено слабкий пароль, який знайдено у витоку даних. Використовуйте надійний та унікальний пароль для захисту свого облікового запису. Ви дійсно хочете використати цей пароль?" }, + "useThisPassword": { + "message": "Використати цей пароль" + }, + "useThisUsername": { + "message": "Використати це ім'я користувача" + }, "checkForBreaches": { "message": "Перевірити відомі витоки даних для цього пароля" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Запит підтвердження адміністратора" }, - "approveWithMasterPassword": { - "message": "Затвердити з головним паролем" - }, "region": { "message": "Регіон" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Ваш запит відправлено адміністратору." }, - "youWillBeNotifiedOnceApproved": { - "message": "Ви отримаєте сповіщення після затвердження." - }, "troubleLoggingIn": { "message": "Проблема під час входу?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Для вашого облікового запису необхідна двоетапна перевірка з Duo." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Для вашого облікового запису необхідно пройти двоетапну перевірку з Duo. Виконайте наведені нижче кроки, щоб завершити вхід." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Виконайте наведені нижче кроки, щоб завершити вхід." + }, "launchDuo": { "message": "Запустити Duo в браузері" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "Не знайдено вільних портів для цього входу SSO." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Біометричне розблокування недоступне, оскільки спочатку потрібно ввести PIN-код або пароль." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Біометричне розблокування наразі недоступне." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Біометричне розблокування недоступне через неправильно налаштовані системні файли." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Біометричне розблокування недоступне через неправильно налаштовані системні файли." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Біометричне розблокування недоступне, оскільки воно не увімкнене для $EMAIL$ у програмі Bitwarden для комп'ютера.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Біометричне розблокування зараз недоступне з невідомої причини." + }, "authorize": { "message": "Авторизувати" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Підтвердження використання ключа SSH" }, + "agentForwardingWarningTitle": { + "message": "Попередження: переспрямування агента" + }, + "agentForwardingWarningText": { + "message": "Цей запит здійснюється з віддаленого пристрою, до якого ви ввійшли" + }, "sshkeyApprovalMessageInfix": { "message": "запитує доступ до" }, + "sshkeyApprovalMessageSuffix": { + "message": "щоб" + }, + "sshActionLogin": { + "message": "пройти автентифікацію на сервері" + }, + "sshActionSign": { + "message": "підписати повідомлення" + }, + "sshActionGitSign": { + "message": "підписати git коміт" + }, "unknownApplication": { "message": "Програма" }, - "sshKeyPasswordUnsupported": { - "message": "Імпортування захищених паролем ключів SSH ще не підтримується" - }, "invalidSshKey": { "message": "Ключ SSH недійсний" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Імпортувати ключ із буфера обміну" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "Ключ SSH успішно імпортовано" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Змінити адресу е-пошти" + }, + "allowScreenshots": { + "message": "Дозволити захоплення екрана" + }, + "allowScreenshotsDesc": { + "message": "Дозволити захоплення екрана і перегляд у віддаленому доступі комп'ютерної програми Bitwarden. Вимкнення цього параметра запобігає доступу на деяких зовнішніх дисплеях." + }, + "confirmWindowStillVisibleTitle": { + "message": "Вікно підтвердження все ще видиме" + }, + "confirmWindowStillVisibleContent": { + "message": "Підтвердьте, що вікно все ще видиме." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Необхідно оновити розширення" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "Ви використовуєте застарілу версію розширення браузера. Оновіть його або вимкніть перевірку цифрового відбитка інтеграції з браузером у налаштуваннях комп'ютерної програми." + }, + "changeAtRiskPassword": { + "message": "Змінити ризикований пароль" } } diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json index e514124414b..f6495e4f915 100644 --- a/apps/desktop/src/locales/vi/messages.json +++ b/apps/desktop/src/locales/vi/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Lỗi" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Tháng 1" }, @@ -529,10 +543,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "Số lượng chữ" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "Gợi ý mật khẩu chính" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "Tham gia tổ chức" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Gợi ý mật khẩu" - }, - "enterEmailToGetHint": { - "message": "Nhập địa chỉ email tài khoản của bạn để nhận gợi ý mật khẩu chính." - }, "getMasterPasswordHint": { "message": "Nhận gợi ý mật khẩu chính" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "Quá trình xác thực đã bị hủy hoặc mất quá nhiều thời gian. Vui lòng thử lại." }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "Mã xác minh không đúng" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "Ghi nhớ đăng nhập" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Gửi lại email chứa mã xác minh" }, "useAnotherTwoStepMethod": { "message": "Sử dụng phương pháp xác minh hai lớp khác" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Cắm YubiKey vào cổng USB trên máy tính bạn và bấm nút trên Yubikey." }, @@ -871,6 +906,15 @@ "message": "Xác minh với Duo Security cho tổ chức của bạn sử dụng ứng dụng Duo Mobile, SMS, cuộc gọi điện thoại, hoặc khoá bảo mật U2F.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -895,12 +939,12 @@ "twoStepOptions": { "message": "Tùy chọn xác minh hai bước" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "Môi trường tự lưu trữ" }, - "selfHostedEnvironmentFooter": { - "message": "Chỉ định địa chỉ cơ sở của bản cài đặt Bitwarden được lưu trữ tại máy chủ của bạn." - }, "selfHostedBaseUrlHint": { "message": "Nhập địa chỉ cơ sở của bản cài đặt Bitwarden được lưu trữ tại máy chủ của bạn. Ví dụ: https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "Môi trường tùy chỉnh" }, - "customEnvironmentFooter": { - "message": "Đối với người dùng nâng cao. Bạn có thể chỉ định địa chỉ cơ sở của mỗi dịch vụ một cách độc lập." - }, "baseUrl": { "message": "Địa chỉ máy chủ" }, @@ -956,6 +997,9 @@ "no": { "message": "Không" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "Ghi đè lên mật khẩu" }, @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "Mua bản Cao Cấp" }, - "premiumPurchaseAlert": { - "message": "Bạn có thể nâng cấp làm thành viên cao cấp trong kho bitwarden nền web. Bạn có muốn truy cập trang web bây giờ?" - }, "premiumPurchaseAlertV2": { "message": "Bạn có thể mua gói Premium từ cài đặt tài khoản trên trang Bitwarden." }, @@ -1755,6 +1796,9 @@ "requirePasswordOnStart": { "message": "Yêu cầu mật khẩu hoặc mã PIN khi mở" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "Đề xuất để tăng cường bảo mật." }, @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "Xác thực WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "Ẩn địa chỉ email của tôi khỏi người nhận." }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "Sử dụng hộp thư bạn đã thiết lập để nhận tất cả email gửi đến tên miền của bạn." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Ngẫu nhiên" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Không thể lấy ID tài khoản email ẩn từ $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "Đăng nhập bằng mật khẩu chính" }, - "loggingInAs": { - "message": "Đang đăng nhập như" - }, "rememberEmail": { "message": "Ghi nhớ email" }, - "notYou": { - "message": "Không phải bạn?" - }, "newAroundHere": { "message": "Bạn mới tới đây sao?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "Bắt đầu đăng nhập" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "Một thông báo đã được gửi đến thiết bị của bạn." }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { "message": "Need another option?" @@ -2759,11 +2839,11 @@ "message": "Bật tắt đếm kí tự", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "Bạn đang cố đăng nhập?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "Nỗ lực đăng nhập bằng $EMAIL$", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "Thời Gian" }, - "confirmLogIn": { - "message": "Xác nhận đăng nhập" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "Từ chối đăng nhập" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "Đã xác nhận đăng nhập cho $EMAIL$ trên $DEVICE$", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "Yêu cầu này không còn hiệu lực." }, - "confirmLoginAtemptForMail": { - "message": "Phê duyệt đăng nhập cho $EMAIL$", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "Yêu cầu đăng nhập" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Đang tạo tài khoản trên" }, @@ -2865,6 +2948,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Mật khẩu yếu này đã bị rò rỉ trong một vụ tấn công dữ liệu. Dùng mật khẩu mới và an toàn để bảo vệ tài khoản bạn. Bạn có chắc muốn sử dụng mật khẩu đã bị rò rỉ?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Kiểm tra mật khẩu có lộ trong các vụ rò rỉ dữ liệu hay không" }, @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "Yêu cầu quản trị viên phê duyệt" }, - "approveWithMasterPassword": { - "message": "Phê duyệt bằng mật khẩu chính" - }, "region": { "message": "Khu vực" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Yêu cầu của bạn đã được gửi đến quản trị viên." }, - "youWillBeNotifiedOnceApproved": { - "message": "Bạn sẽ có thông báo nếu được phê duyệt." - }, "troubleLoggingIn": { "message": "Không thể đăng nhập?" }, @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "Tài khoản của bạn yêu cầu xác minh hai bước với Duo." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Khởi chạy Duo trong trình duyệt" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "Không thể tìm thấy cổng trống để đăng nhập SSO." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" + }, "sshkeyApprovalMessageInfix": { "message": "is requesting access to" }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" + }, "unknownApplication": { "message": "An application" }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" - }, "invalidSshKey": { "message": "The SSH key is invalid" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "Import key from clipboard" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 9bff79aa857..6a12138b48f 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -249,6 +249,20 @@ "error": { "message": "错误" }, + "decryptionError": { + "message": "解密错误" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden 无法解密下列密码库项目。" + }, + "contactCSToAvoidDataLossPart1": { + "message": "联系客户成功团队", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "以避免额外的数据丢失。", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "一月" }, @@ -445,7 +459,7 @@ "message": "确定要覆盖当前用户名吗?" }, "noneFolder": { - "message": "无文件夹", + "message": "默认文件夹", "description": "This is the folder for uncategorized items" }, "addFolder": { @@ -529,10 +543,6 @@ "message": "包含特殊字符", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "单词数" }, @@ -638,6 +648,15 @@ "logInToBitwarden": { "message": "登录到 Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "输入发送到您的电子邮箱的代码" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "输入来自您的验证器 App 的代码" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "按下 YubiKey 以验证身份" + }, "logInWithPasskey": { "message": "使用通行密钥登录" }, @@ -654,7 +673,7 @@ "message": "主密码" }, "masterPassDesc": { - "message": "主密码是访问密码库的唯一密码。它非常重要,请您不要忘记。忘记主密码后,我们无法为您恢复或重置它。" + "message": "主密码是用于访问您的密码库的密码。不要忘记您的主密码,这一点非常重要。一旦忘记,无任何办法恢复此密码。" }, "masterPassHintDesc": { "message": "主密码提示可以在您忘记密码时帮您回忆起来。" @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "主密码提示" }, + "passwordStrengthScore": { + "message": "密码强度评分 $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "加入组织" }, @@ -720,12 +748,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "输入您的账户电子邮箱地址,您的密码提示将发送给您" }, - "passwordHint": { - "message": "密码提示" - }, - "enterEmailToGetHint": { - "message": "请输入您的账户电子邮箱地址来接收主密码提示。" - }, "getMasterPasswordHint": { "message": "获取主密码提示" }, @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "身份验证被取消或耗时过长。请重试。" }, + "openInNewTab": { + "message": "在新标签页中打开" + }, "invalidVerificationCode": { "message": "无效的验证码" }, @@ -832,20 +857,30 @@ "rememberMe": { "message": "记住我" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "30 天内在此设备上不再询问" + }, "sendVerificationCodeEmailAgain": { "message": "再次发送验证码电子邮件" }, "useAnotherTwoStepMethod": { "message": "使用其他两步登录方式" }, + "selectAnotherMethod": { + "message": "选择其他方式", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "使用您的恢复代码" + }, "insertYubiKey": { "message": "将您的 YubiKey 插入计算机的 USB 端口,然后触摸其按钮。" }, "insertU2f": { - "message": "将您的安全钥匙插入计算机的 USB 端口。如果它有按钮,请触摸它。" + "message": "将您的安全密钥插入计算机的 USB 端口。如果它有按钮,请触摸它。" }, "recoveryCodeDesc": { - "message": "无法访问您所有的双重身份提供程序吗?请使用您的恢复代码来停用您账户中所有的双重身份提供程序。" + "message": "无法访问您所有的双重身份提供程序吗?请使用您的恢复代码来关闭您账户中所有的双重身份提供程序。" }, "recoveryCodeTitle": { "message": "恢复代码" @@ -858,7 +893,7 @@ "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP 安全钥匙" + "message": "Yubico OTP 安全密钥" }, "yubiKeyDesc": { "message": "使用 YubiKey 来访问您的账户。支持 YubiKey 4、4 Nano、4C 以及 NEO 设备。" @@ -868,14 +903,23 @@ "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { - "message": "为您的组织使用 Duo Security 的 Duo 移动 App、短信、电话或 U2F 安全钥匙来进行验证。", + "message": "为您的组织使用 Duo Security 的 Duo 移动 App、短信、电话或 U2F 安全密钥来进行验证。", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "验证您的身份" + }, + "weDontRecognizeThisDevice": { + "message": "我们无法识别这个设备。请输入发送到您电子邮箱中的代码以验证您的身份。" + }, + "continueLoggingIn": { + "message": "继续登录" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, "webAuthnDesc": { - "message": "使用任何 WebAuthn 兼容的安全钥匙访问您的帐户。" + "message": "使用任何 WebAuthn 兼容的安全密钥访问您的帐户。" }, "emailTitle": { "message": "电子邮箱" @@ -890,17 +934,17 @@ "message": "此账户已设置两步登录,但此设备不支持任何已配置的两步登录提供程序。" }, "noTwoStepProviders2": { - "message": "请添加能更好支持跨设备使用的其他提供程序(例如验证器 App)。" + "message": "请添加其他跨设备支持更好的提供程序(例如验证器 App)。" }, "twoStepOptions": { "message": "两步登录选项" }, + "selectTwoStepLoginMethod": { + "message": "选择两步登录方式" + }, "selfHostedEnvironment": { "message": "自托管环境" }, - "selfHostedEnvironmentFooter": { - "message": "指定您本地托管的 Bitwarden 安装的基础 URL。" - }, "selfHostedBaseUrlHint": { "message": "指定您的本地托管 Bitwarden 安装的基础 URL。例如:https://bitwarden.company.com" }, @@ -913,9 +957,6 @@ "customEnvironment": { "message": "自定义环境" }, - "customEnvironmentFooter": { - "message": "适用于高级用户。您可以分别指定各个服务的基础 URL。" - }, "baseUrl": { "message": "服务器 URL" }, @@ -956,6 +997,9 @@ "no": { "message": "否" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "覆盖密码" }, @@ -975,13 +1019,13 @@ "message": "您的登录会话已过期。" }, "restartRegistration": { - "message": "重新开始注册" + "message": "重启注册" }, "expiredLink": { "message": "失效链接" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "请重新注册或尝试登录。" + "message": "请重启注册或尝试登录。" }, "youMayAlreadyHaveAnAccount": { "message": "您可能已经有一个账户了" @@ -1008,7 +1052,7 @@ "message": "账户" }, "loading": { - "message": "加载中…" + "message": "正在加载..." }, "lockVault": { "message": "锁定密码库" @@ -1104,7 +1148,7 @@ "message": "无效的主密码" }, "twoStepLoginConfirmation": { - "message": "两步登录要求您从其他设备(例如安全钥匙、验证器 App、短信、电话或者电子邮件)来验证您的登录,这能使您的账户更加安全。两步登录需要在 bitwarden.com 网页版密码库中设置。现在访问此网站吗?" + "message": "两步登录要求您从其他设备(例如安全密钥、验证器 App、短信、电话或者电子邮件)来验证您的登录,这能使您的账户更加安全。两步登录需要在 bitwarden.com 网页版密码库中设置。现在访问此网站吗?" }, "twoStepLogin": { "message": "两步登录" @@ -1241,7 +1285,7 @@ "message": "确认隐藏到托盘" }, "confirmTrayDesc": { - "message": "禁用此设置也将禁用其他与托盘相关的设置。" + "message": "关闭此设置也将关闭其他与托盘相关的设置。" }, "language": { "message": "语言" @@ -1365,9 +1409,6 @@ "premiumPurchase": { "message": "购买高级版" }, - "premiumPurchaseAlert": { - "message": "您可以在 bitwarden.com 网页版密码库购买高级会员。现在要访问吗?" - }, "premiumPurchaseAlertV2": { "message": "您可以在 Bitwarden 网页 App 的账户设置中购买高级版。" }, @@ -1378,7 +1419,7 @@ "message": "感谢您支持 Bitwarden。" }, "premiumPrice": { - "message": "全部仅需 $PRICE$ /年!", + "message": "所有功能仅需 $PRICE$ /年!", "placeholders": { "price": { "content": "$1", @@ -1510,7 +1551,7 @@ "message": "检查密码是否已经被公开。" }, "passwordExposed": { - "message": "此密码在泄露数据中已被公开 $VALUE$ 次。请立即修改。", + "message": "此密码在数据泄露中已被暴露 $VALUE$ 次。请立即修改。", "placeholders": { "value": { "content": "$1", @@ -1522,7 +1563,7 @@ "message": "没有在已知的数据泄露中发现此密码,它暂时比较安全。" }, "baseDomain": { - "message": "基础域", + "message": "基础域名", "description": "Domain name. Ex. website.com" }, "domainName": { @@ -1565,7 +1606,7 @@ "message": "退出" }, "showHide": { - "message": "显示/隐藏", + "message": "显示 / 隐藏", "description": "Text for a button that toggles the visibility of the window. Shows the window when it is hidden or hides the window if it is currently open." }, "hideToTray": { @@ -1701,7 +1742,7 @@ "message": "脆弱的主密码" }, "weakMasterPasswordDesc": { - "message": "您选择的主密码较弱。您应该使用强密码(或密码短语)来正确保护您的 Bitwarden 账户。仍要使用此主密码吗?" + "message": "您选择的主密码较弱。您应该使用强密码(或密码短语)来正确保护您的 Bitwarden 账户。确定要使用这个主密码吗?" }, "pin": { "message": "PIN 码", @@ -1744,16 +1785,19 @@ "message": "解锁您的密码库" }, "autoPromptWindowsHello": { - "message": "应用程序启动时要求使用 Windows Hello" + "message": "应用程序启动时提示 Windows Hello" }, "autoPromptPolkit": { - "message": "启动时请求系统身份验证" + "message": "启动时提示系统身份验证" }, "autoPromptTouchId": { - "message": "应用程序启动时要求使用触控 ID" + "message": "应用程序启动时提示触控 ID" }, "requirePasswordOnStart": { - "message": "App 启动时要求输入密码或 PIN 码" + "message": "应用程序启动时要求密码或 PIN 码" + }, + "requirePasswordWithoutPinOnStart": { + "message": "应用程序启动时要求密码" }, "recommendedForSecurity": { "message": "安全起见,推荐设置。" @@ -1887,7 +1931,7 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "$TOTAL$ 不足", + "message": "总计 $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -1996,7 +2040,7 @@ "message": "很遗憾,目前仅 Mac App Store 版本支持浏览器集成。" }, "browserIntegrationWindowsStoreDesc": { - "message": "很遗憾,Microsoft Store 版本目前不支持浏览器整合。" + "message": "很遗憾,Microsoft Store 版本目前不支持浏览器集成。" }, "browserIntegrationLinuxDesc": { "message": "很遗憾,Linux 版本目前不支持浏览器集成。" @@ -2041,7 +2085,7 @@ "message": "生物识别未设置" }, "biometricsNotEnabledDesc": { - "message": "需要先在桌面应用程序的设置中设置生物识别,才能使用浏览器中的生物识别。" + "message": "需要首先在桌面端的设置中设置生物识别,才能使用浏览器的生物识别。" }, "biometricsManualSetupTitle": { "message": "自动设置不可用" @@ -2111,11 +2155,11 @@ "message": "当前访问次数" }, "disableSend": { - "message": "停用此 Send 则任何人无法访问它。", + "message": "停用此 Send 确保无人能访问它。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendPasswordDesc": { - "message": "可选,用户需要提供密码才能访问此 Send。", + "message": "可选。用户需要提供密码才能访问此 Send。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendNotesDesc": { @@ -2135,7 +2179,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSend": { - "message": "Send 已创建", + "message": "Send 已添加", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { @@ -2154,14 +2198,14 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createSend": { - "message": "创建 Send", + "message": "新增 Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTextDesc": { - "message": "您想要发送的文本。" + "message": "您想在此 Send 中附加的文本。" }, "sendFileDesc": { - "message": "您想要发送的文件。" + "message": "您想在此 Send 中附加的文件。" }, "days": { "message": "$DAYS$ 天", @@ -2219,13 +2263,19 @@ "message": "已过期" }, "pendingDeletion": { - "message": "等待删除" + "message": "待删除" }, "webAuthnAuthenticate": { "message": "验证 WebAuthn" }, + "readSecurityKey": { + "message": "读取安全密钥" + }, + "awaitingSecurityKeyInteraction": { + "message": "等待安全密钥交互……" + }, "hideEmail": { - "message": "对收件人隐藏我的电子邮箱地址。" + "message": "对接收者隐藏我的电子邮箱地址。" }, "sendOptionsPolicyInEffect": { "message": "一个或多个组织策略正在影响您的 Send 设置。" @@ -2255,7 +2305,7 @@ "message": "更新主密码" }, "updateMasterPasswordWarning": { - "message": "您的主密码最近被您组织的管理员更改过。要访问密码库,您必须立即更新它。继续操作将使您退出当前会话并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" + "message": "您的主密码最近被您组织的管理员更改过。要访问密码库,您必须立即更新它。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" }, "updateWeakMasterPasswordWarning": { "message": "您的主密码不符合某一项或多项组织策略要求。要访问密码库,必须立即更新您的主密码。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" @@ -2264,7 +2314,7 @@ "message": "您的组织禁用了信任设备加密。要访问您的密码库,请设置一个主密码。" }, "tryAgain": { - "message": "请重试" + "message": "重试" }, "verificationRequiredForActionSetPinToContinue": { "message": "此操作需要验证。设置一个 PIN 码以继续。" @@ -2354,7 +2404,7 @@ "message": "自动注册" }, "resetPasswordAutoEnrollInviteWarning": { - "message": "此组织有一个企业策略,将自动为你注册密码重置。注册后将允许组织管理员更改您的主密码。" + "message": "此组织有一个企业策略,将自动为您注册密码重置。注册后将允许组织管理员更改您的主密码。" }, "vaultExportDisabled": { "message": "密码库导出已禁用" @@ -2420,7 +2470,7 @@ "message": "切换账户" }, "alreadyHaveAccount": { - "message": "已经拥有账户了吗?" + "message": "已经有账户了吗?" }, "options": { "message": "选项" @@ -2495,7 +2545,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " 使用 $RECOMMENDED$ 或更多个字符生成强大的密码。", + "message": " 使用 $RECOMMENDED$ 个或更多字符生成强大的密码。", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2505,7 +2555,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " 使用 $RECOMMENDED$ 或更多个单词生成强大的密码短语。", + "message": " 使用 $RECOMMENDED$ 个或更多单词生成强大的密码短语。", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "使用您的域名配置的 Catch-all 收件箱。" }, + "useThisEmail": { + "message": "使用此电子邮箱" + }, "random": { "message": "随机" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ 拒绝了您的请求。请联系您的服务提供商寻求帮助。", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ 拒绝了您的请求:$ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "无法获取 $SERVICENAME$ 电子邮箱账户 ID。", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2684,7 +2761,7 @@ "message": "组织已停用" }, "disabledOrganizationFilterError": { - "message": "无法访问已停用组织中的项目。请联系您的组织所有者获取协助。" + "message": "无法访问已停用组织中的项目。请联系您的组织所有者寻求帮助。" }, "neverLockWarning": { "message": "确定要使用「从不」选项吗?将锁定选项设置为「从不」会将密码库的加密密钥存储在您的设备上。如果使用此选项,您必须确保您的设备安全。" @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "使用主密码登录" }, - "loggingInAs": { - "message": "正登录为" - }, "rememberEmail": { "message": "记住电子邮箱" }, - "notYou": { - "message": "不是您吗?" - }, "newAroundHere": { "message": "初来乍到吗?" }, @@ -2722,14 +2793,23 @@ "loginInitiated": { "message": "登录已发起" }, + "logInRequestSent": { + "message": "请求已发送" + }, "notificationSentDevice": { "message": "通知已发送到您的设备。" }, "aNotificationWasSentToYourDevice": { "message": "通知已发送到您的设备" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "确保您的账户已解锁,并且指纹短语与其他设备上的相匹配。" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "网页 App" + }, + "notificationSentDevicePart2": { + "message": "在批准前,请确保指纹短语与下面的相匹配。" }, "needAnotherOptionV1": { "message": "需要其他选项吗?" @@ -2759,11 +2839,11 @@ "message": "字符计数开关", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "您正在尝试登录吗?" + "areYouTryingToAccessYourAccount": { + "message": "您正在尝试访问您的账户吗?" }, - "logInAttemptBy": { - "message": "$EMAIL$ 的登录尝试", + "accessAttemptBy": { + "message": "$EMAIL$ 的访问尝试", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "时间" }, - "confirmLogIn": { - "message": "确认登录" + "confirmAccess": { + "message": "确认访问" }, - "denyLogIn": { - "message": "拒绝登录" + "denyAccess": { + "message": "拒绝访问" }, "logInConfirmedForEmailOnDevice": { "message": "已确认 $EMAIL$ 在 $DEVICE$ 上的登录", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "此请求已失效。" }, - "confirmLoginAtemptForMail": { - "message": "确认 $EMAIL$ 的登录尝试", + "confirmAccessAttempt": { + "message": "确认 $EMAIL$ 的访问尝试", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "已请求登录" }, + "accountAccessRequested": { + "message": "已请求账户访问" + }, "creatingAccountOn": { "message": "创建账户至" }, @@ -2863,7 +2946,13 @@ "message": "主密码弱且曾经暴露" }, "weakAndBreachedMasterPasswordDesc": { - "message": "识别到弱密码且其出现在数据泄露中。请使用一个强且唯一的密码以保护你的账户。确定要使用这个密码吗?" + "message": "识别到弱密码且其出现在数据泄露中。请使用一个强且唯一的密码以保护您的账户。确定要使用这个密码吗?" + }, + "useThisPassword": { + "message": "使用此密码" + }, + "useThisUsername": { + "message": "使用此用户名" }, "checkForBreaches": { "message": "检查已知的数据泄露是否包含此密码" @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "请求管理员批准" }, - "approveWithMasterPassword": { - "message": "使用主密码批准" - }, "region": { "message": "区域" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "您的请求已发送给您的管理员。" }, - "youWillBeNotifiedOnceApproved": { - "message": "批准后,您将收到通知。" - }, "troubleLoggingIn": { "message": "登录遇到问题吗?" }, @@ -2981,7 +3064,7 @@ "message": "搜索" }, "inputMinLength": { - "message": "至少输入 $COUNT$ 个字符。", + "message": "输入长度不能低于 $COUNT$ 个字符。", "placeholders": { "count": { "content": "$1", @@ -3130,7 +3213,7 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "与 Duo 服务连接时出错。请使用不同的两步登录方式或联系 Duo 获取协助。" + "message": "与 Duo 服务连接时出错。请使用其他两步登录方式或联系 Duo 寻求帮助。" }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "启动 Duo 然后按照步骤完成登录。" @@ -3138,6 +3221,12 @@ "duoRequiredByOrgForAccount": { "message": "您的账户要求使用 Duo 两步登录。" }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "您的账户要求使用 Duo 两步登录。请按照以下步骤完成登录。" + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "按照以下步骤完成登录。" + }, "launchDuo": { "message": "在浏览器中启动 Duo" }, @@ -3362,6 +3451,30 @@ "ssoError": { "message": "找不到用于 SSO 登录的可用端口。" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "生物识别解锁不可用,因为需要先使用 PIN 或密码解锁。" + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "生物识别解锁当前不可用。" + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "由于系统文件配置错误,生物识别解锁不可用。" + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "由于系统文件配置错误,生物识别解锁不可用。" + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "生物识别解锁不可用,因为在 Bitwarden 桌面 App 中没有为 $EMAIL$ 启用生物识别解锁。", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "由于某个未知的原因,生物识别解锁当前不可用。" + }, "authorize": { "message": "批准" }, @@ -3371,15 +3484,30 @@ "sshkeyApprovalTitle": { "message": "确认 SSH 密钥的使用" }, + "agentForwardingWarningTitle": { + "message": "警告:代理转发" + }, + "agentForwardingWarningText": { + "message": "此请求来自您登录的某台远程设备" + }, "sshkeyApprovalMessageInfix": { "message": "正在请求访问" }, + "sshkeyApprovalMessageSuffix": { + "message": "为了" + }, + "sshActionLogin": { + "message": "向服务器验证身份" + }, + "sshActionSign": { + "message": "签署一条消息" + }, + "sshActionGitSign": { + "message": "签名一个 Git 提交" + }, "unknownApplication": { "message": "某个应用程序" }, - "sshKeyPasswordUnsupported": { - "message": "尚不支持导入受密码保护的 SSH 密钥" - }, "invalidSshKey": { "message": "此 SSH 密钥无效" }, @@ -3389,7 +3517,7 @@ "importSshKeyFromClipboard": { "message": "从剪贴板导入密钥" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH 密钥导入成功" }, "fileSavedToDevice": { @@ -3430,5 +3558,26 @@ }, "changeAcctEmail": { "message": "更改账户电子邮箱" + }, + "allowScreenshots": { + "message": "允许屏幕截图" + }, + "allowScreenshotsDesc": { + "message": "允许捕获 Bitwarden 桌面应用程序屏幕截图并在远程桌面会话中查看。禁用后将阻止在某些外部显示器上访问。" + }, + "confirmWindowStillVisibleTitle": { + "message": "确认窗口仍然可见" + }, + "confirmWindowStillVisibleContent": { + "message": "请确认窗口仍然可见。" + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "扩展需要更新" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "您正在使用的浏览器扩展已过时。请更新它或在桌面 App 的设置中禁用浏览器集成指纹验证。" + }, + "changeAtRiskPassword": { + "message": "更改有风险的密码" } } diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json index 3c7884a22c5..5da6c4a0b6e 100644 --- a/apps/desktop/src/locales/zh_TW/messages.json +++ b/apps/desktop/src/locales/zh_TW/messages.json @@ -27,7 +27,7 @@ "message": "安全筆記" }, "typeSshKey": { - "message": "SSH key" + "message": "SSH 金鑰" }, "folders": { "message": "資料夾" @@ -64,7 +64,7 @@ } }, "welcomeBack": { - "message": "Welcome back" + "message": "歡迎回來" }, "moveToOrgDesc": { "message": "選擇您希望將這個項目移動到哪個組織。項目的擁有權將會轉移到該組織。一經移動,您將不再是此項目的直接擁有者。" @@ -181,16 +181,16 @@ "message": "地址" }, "sshPrivateKey": { - "message": "Private key" + "message": "私密金鑰" }, "sshPublicKey": { - "message": "Public key" + "message": "公開金鑰" }, "sshFingerprint": { - "message": "Fingerprint" + "message": "指紋" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "金鑰類型" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -208,19 +208,19 @@ "message": "一組SSH金鑰已在之前生成了" }, "sshKeyWrongPassword": { - "message": "The password you entered is incorrect." + "message": "您輸入的密碼錯誤。" }, "importSshKey": { - "message": "Import" + "message": "匯入" }, "confirmSshKeyPassword": { - "message": "Confirm password" + "message": "確認密碼" }, "enterSshKeyPasswordDesc": { - "message": "Enter the password for the SSH key." + "message": "輸入 SSH 金鑰的密碼" }, "enterSshKeyPassword": { - "message": "Enter password" + "message": "請輸入密碼" }, "sshAgentUnlockRequired": { "message": "請解鎖密碼庫以核准SSh金鑰的請求" @@ -229,7 +229,7 @@ "message": "SSH金鑰請求超時" }, "enableSshAgent": { - "message": "啟用SSH代理" + "message": "啟用 SSH 代理程式" }, "enableSshAgentDesc": { "message": "啟用SSBitwardenH代理以從 Bitwarden 密碼庫簽發SSH請求" @@ -249,6 +249,20 @@ "error": { "message": "錯誤" }, + "decryptionError": { + "message": "解密發生錯誤" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden 無法解密您密碼庫中下面的項目。" + }, + "contactCSToAvoidDataLossPart1": { + "message": "聯絡客戶支援部門", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "來避免更多資料遺失。", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "一月" }, @@ -510,7 +524,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "包含小寫字元", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -529,10 +543,6 @@ "message": "包含特殊字元", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "numWords": { "message": "單字數" }, @@ -638,14 +648,23 @@ "logInToBitwarden": { "message": "登入 Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "以通行密鑰 (passkey) 登入" }, "loginWithDevice": { - "message": "Log in with device" + "message": "使用裝置登入" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "使用單一登入" }, "submit": { "message": "送出" @@ -666,7 +685,7 @@ "message": "主密碼提示(選用)" }, "masterPassHintText": { - "message": "If you forget your password, the password hint can be sent to your email. $CURRENT$/$MAXIMUM$ character maximum.", + "message": "如果您忘記了密碼,可以傳送密碼提示到您的電子郵件。$CURRENT$ / 最多 $MAXIMUM$ 個字元", "placeholders": { "current": { "content": "$1", @@ -690,6 +709,15 @@ "masterPassHintLabel": { "message": "主密碼提示" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "joinOrganization": { "message": "加入組織" }, @@ -703,7 +731,7 @@ } }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Finish joining this organization by setting a master password." + "message": "設定主密碼以完成加入這個組織" }, "settings": { "message": "設定" @@ -712,19 +740,13 @@ "message": "電子郵件帳戶:" }, "requestHint": { - "message": "Request hint" + "message": "請求提示" }, "requestPasswordHint": { "message": "主密碼提示" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { - "message": "Enter your account email address and your password hint will be sent to you" - }, - "passwordHint": { - "message": "密碼提示" - }, - "enterEmailToGetHint": { - "message": "請輸入您的帳户電子郵件地址以接收主密碼提示。" + "message": "輸入您帳號的電子郵件,您的密碼提示會傳送給您" }, "getMasterPasswordHint": { "message": "取得主密碼提示" @@ -764,7 +786,7 @@ "message": "帳戶已建立!現在可以登入了。" }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "您已成功建立新帳號!" }, "youHaveBeenLoggedIn": { "message": "你已經登入!" @@ -802,6 +824,9 @@ "webauthnCancelOrTimeout": { "message": "驗證已被取消或時間過長。請再試一次。" }, + "openInNewTab": { + "message": "Open in new tab" + }, "invalidVerificationCode": { "message": "無效的驗證碼" }, @@ -832,12 +857,22 @@ "rememberMe": { "message": "記住我" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "再次傳送​​包含驗證碼的電子郵件" }, "useAnotherTwoStepMethod": { "message": "使用另一種兩步驟登入方式" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "將您的 YubiKey 插入電腦的 USB 連接埠,然後按一下它的按鈕。" }, @@ -854,23 +889,32 @@ "message": "驗證器應用程式" }, "authenticatorAppDescV2": { - "message": "Enter a code generated by an authenticator app like Bitwarden Authenticator.", + "message": "輸入驗證器應用程式產生的驗證碼,例如 Bitwarden 驗證器。", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP security key" + "message": "YubiKey OTP 安全金鑰" }, "yubiKeyDesc": { "message": "使用 YubiKey 來存取您的帳戶。支援 YubiKey 4、4 Nano、4C、以及 NEO 裝置。" }, "duoDescV2": { - "message": "Enter a code generated by Duo Security.", + "message": "輸入 Duo 應用程式產生的驗證碼。", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { "message": "爲您的組織使用 Duo Security 的 Duo Mobile 程式、SMS、致電或 U2F 安全鑰匙進行驗證。", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, @@ -881,7 +925,7 @@ "message": "電子郵件" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "輸入寄送到您電子郵件信箱的驗證碼。" }, "loginUnavailable": { "message": "登入無法使用" @@ -895,38 +939,35 @@ "twoStepOptions": { "message": "兩步驟登入選項" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "selfHostedEnvironment": { "message": "自我裝載環境" }, - "selfHostedEnvironmentFooter": { - "message": "指定您本地托管的 Bitwarden 安裝之基礎 URL。" - }, "selfHostedBaseUrlHint": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" + "message": "指定您自建的 Bitwarden 伺服器的網域 URL。例如:https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { - "message": "For advanced configuration, you can specify the base URL of each service independently." + "message": "適用於進階設定。您可以單獨指定各個服務的網域 URL。" }, "selfHostedEnvFormInvalid": { - "message": "You must add either the base Server URL or at least one custom environment." + "message": "您必須新增伺服器網域 URL 或至少一個自定義環境。" }, "customEnvironment": { "message": "自訂環境" }, - "customEnvironmentFooter": { - "message": "適用於進階使用者。您可以單獨指定各個服務的基礎 URL。" - }, "baseUrl": { "message": "伺服器 URL" }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "驗證逾時" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "驗證工作階段因時間過久已逾時。請重試登入。" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "自建伺服器 URL", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -956,6 +997,9 @@ "no": { "message": "否" }, + "location": { + "message": "Location" + }, "overwritePassword": { "message": "覆寫密碼" }, @@ -969,22 +1013,22 @@ "message": "已登出" }, "loggedOutDesc": { - "message": "You have been logged out of your account." + "message": "您已經登出了您的帳號。" }, "loginExpired": { "message": "您的登入會話已過期。" }, "restartRegistration": { - "message": "Restart registration" + "message": "重新啟動註冊" }, "expiredLink": { - "message": "Expired link" + "message": "過期連結" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "請重新啟動註冊流程或是重試登入。" }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "您可能已經有帳號" }, "logOutConfirmation": { "message": "您確定要登出嗎?" @@ -1041,10 +1085,10 @@ "message": "變更主密碼" }, "continueToWebApp": { - "message": "Continue to web app?" + "message": "前往網頁應用程式嗎?" }, "changeMasterPasswordOnWebConfirmation": { - "message": "You can change your master password on the Bitwarden web app." + "message": "您可以在 Bitwarden 網頁應用程式上變更主密碼。" }, "fingerprintPhrase": { "message": "指紋短語", @@ -1073,16 +1117,16 @@ "message": "您的密碼庫已鎖定。請驗證身分以繼續。" }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "您的帳戶已被鎖定。" }, "or": { - "message": "or" + "message": "或" }, "unlockWithBiometrics": { - "message": "Unlock with biometrics" + "message": "使用生物辨識解鎖" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "使用主密碼解鎖" }, "unlock": { "message": "解鎖" @@ -1113,7 +1157,7 @@ "message": "密碼庫逾時時間" }, "vaultTimeout1": { - "message": "Timeout" + "message": "逾時" }, "vaultTimeoutDesc": { "message": "選擇密碼庫何時執行密碼庫逾時動作。" @@ -1320,7 +1364,7 @@ "description": "Copy credit card number" }, "copyEmail": { - "message": "Copy email" + "message": "複製電子郵件地址" }, "copySecurityCode": { "message": "複製安全代碼", @@ -1365,11 +1409,8 @@ "premiumPurchase": { "message": "購買進階會員資格" }, - "premiumPurchaseAlert": { - "message": "您可以在 bitwarden.com 網頁版密碼庫購買進階會員資格。現在要前往嗎?" - }, "premiumPurchaseAlertV2": { - "message": "You can purchase Premium from your account settings on the Bitwarden web app." + "message": "您可以在 Bitwarden 網頁應用程式的帳號設定中購買進階版。" }, "premiumCurrentMember": { "message": "您目前是進階會員!" @@ -1393,13 +1434,13 @@ "message": "密碼歷史記錄" }, "generatorHistory": { - "message": "Generator history" + "message": "產生器歷史記錄" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "清除產生器歷史記錄" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "若繼續,所有產生器曾經產生的記錄會被刪除。您確定要繼續?" }, "clear": { "message": "清除", @@ -1409,13 +1450,13 @@ "message": "沒有可列出的密碼。" }, "clearHistory": { - "message": "Clear history" + "message": "清除歷史紀錄" }, "nothingToShow": { - "message": "Nothing to show" + "message": "沒有可顯示的內容" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "您最近未產生任何密碼" }, "undo": { "message": "復原" @@ -1492,13 +1533,13 @@ } }, "copySuccessful": { - "message": "Copy Successful" + "message": "複製成功" }, "errorRefreshingAccessToken": { - "message": "Access Token Refresh Error" + "message": "存取權杖更新失敗" }, "errorRefreshingAccessTokenDesc": { - "message": "No refresh token or API keys found. Please try logging out and logging back in." + "message": "未找到存取權杖或 API 密鑰。請重試登出再登入。" }, "help": { "message": "說明" @@ -1588,7 +1629,7 @@ "description": "ex. Date this password was updated" }, "exportFrom": { - "message": "Export from" + "message": "匯出自" }, "exportVault": { "message": "匯出密碼庫" @@ -1720,7 +1761,7 @@ "message": "無效的 PIN 碼。" }, "tooManyInvalidPinEntryAttemptsLoggingOut": { - "message": "Too many invalid PIN entry attempts. Logging out." + "message": "輸入太多無效 PIN 碼。 正在登出。" }, "unlockWithWindowsHello": { "message": "使用 Windows Hello 解鎖" @@ -1729,7 +1770,7 @@ "message": "額外的 Windows Hello 設定" }, "unlockWithPolkit": { - "message": "Unlock with system authentication" + "message": "使用系統驗證解鎖" }, "windowsHelloConsentMessage": { "message": "驗證 Bitwarden。" @@ -1747,7 +1788,7 @@ "message": "啟動應用程式時詢問 Windows Hello" }, "autoPromptPolkit": { - "message": "Ask for system authentication on launch" + "message": "在啟動時詢問系統驗證" }, "autoPromptTouchId": { "message": "啟動應用程式時要求 Touch ID" @@ -1755,11 +1796,14 @@ "requirePasswordOnStart": { "message": "要求在啟動應用程式時輸入密碼或 PIN 碼" }, + "requirePasswordWithoutPinOnStart": { + "message": "Require password on app start" + }, "recommendedForSecurity": { "message": "為提升安全,建議使用。" }, "lockWithMasterPassOnRestart1": { - "message": "Lock with master password on restart" + "message": "重啟後使用主密碼鎖定" }, "deleteAccount": { "message": "刪除帳戶" @@ -1771,7 +1815,7 @@ "message": "刪除您的帳戶是永久性的。並且無法復原。" }, "cannotDeleteAccount": { - "message": "Cannot delete account" + "message": "無法刪除帳號" }, "cannotDeleteAccountDesc": { "message": "This action cannot be completed because your account is owned by an organization. Contact your organization administrator for additional details." @@ -2224,6 +2268,12 @@ "webAuthnAuthenticate": { "message": "驗證 WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "hideEmail": { "message": "對收件人隱藏我的電子郵件位址。" }, @@ -2530,6 +2580,9 @@ "catchallEmailDesc": { "message": "使用您的網域設定的 Catch-all 收件匣。" }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "隨機" }, @@ -2617,6 +2670,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -2695,15 +2772,9 @@ "loginWithMasterPassword": { "message": "使用主密碼登入" }, - "loggingInAs": { - "message": "正登入為" - }, "rememberEmail": { "message": "記住電子郵件地址" }, - "notYou": { - "message": "不是您嗎?" - }, "newAroundHere": { "message": "第一次使用?" }, @@ -2722,17 +2793,26 @@ "loginInitiated": { "message": "登入已啟動" }, + "logInRequestSent": { + "message": "Request sent" + }, "notificationSentDevice": { "message": "已傳送通知至您的裝置。" }, "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "message": "已傳送通知至您的裝置" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "需要另一個選項嗎?" }, "fingerprintMatchInfo": { "message": "請確保您的密碼庫已解鎖,並且指紋短語與其他裝置的一致。" @@ -2747,7 +2827,7 @@ "message": "必須先在 Bitwarden 應用程式設定中開啟,才可以使用裝置登入。要改用其他選項嗎?" }, "viewAllLogInOptions": { - "message": "View all log in options" + "message": "檢視所有登入選項" }, "viewAllLoginOptions": { "message": "檢視所有登入選項" @@ -2759,11 +2839,11 @@ "message": "切換字元計數", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, - "areYouTryingtoLogin": { - "message": "您正在嘗試登入嗎?" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" }, - "logInAttemptBy": { - "message": "來自 $EMAIL$ 的登入嘗試", + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2780,11 +2860,11 @@ "time": { "message": "時間" }, - "confirmLogIn": { - "message": "確認登入" + "confirmAccess": { + "message": "Confirm access" }, - "denyLogIn": { - "message": "拒絕登入" + "denyAccess": { + "message": "Deny access" }, "logInConfirmedForEmailOnDevice": { "message": "已確認 $EMAIL$ 在 $DEVICE$ 上的登入", @@ -2820,8 +2900,8 @@ "thisRequestIsNoLongerValid": { "message": "此請求已失效。" }, - "confirmLoginAtemptForMail": { - "message": "確認 $EMAIL$ 的登入嘗試", + "confirmAccessAttempt": { + "message": "Confirm access attempt for $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2832,6 +2912,9 @@ "logInRequested": { "message": "已要求登入" }, + "accountAccessRequested": { + "message": "Account access requested" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -2848,7 +2931,7 @@ "message": "No email?" }, "goBack": { - "message": "Go back" + "message": "返回" }, "toEditYourEmailAddress": { "message": "to edit your email address." @@ -2865,11 +2948,17 @@ "weakAndBreachedMasterPasswordDesc": { "message": "密碼強度不足,且該密碼已洩露。請使用一個強度足夠和獨特的密碼來保護您的帳戶。您確定要使用這個密碼嗎?" }, + "useThisPassword": { + "message": "使用此密碼" + }, + "useThisUsername": { + "message": "使用此使用者名稱" + }, "checkForBreaches": { "message": "檢查外洩的密碼資料庫中是否包含此密碼" }, "loggedInExclamation": { - "message": "Logged in!" + "message": "已登入!" }, "important": { "message": "重要:" @@ -2908,10 +2997,10 @@ "message": "裝置需要取得核准。請在下面選擇一個核准選項:" }, "deviceApprovalRequiredV2": { - "message": "Device approval required" + "message": "需要核准裝置" }, "selectAnApprovalOptionBelow": { - "message": "Select an approval option below" + "message": "選擇下面的一個核准選項" }, "rememberThisDevice": { "message": "記住這個裝置" @@ -2925,9 +3014,6 @@ "requestAdminApproval": { "message": "要求管理員核准" }, - "approveWithMasterPassword": { - "message": "使用主密碼核准" - }, "region": { "message": "區域" }, @@ -2953,9 +3039,6 @@ "adminApprovalRequestSentToAdmins": { "message": "您的要求已傳送給您的管理員。" }, - "youWillBeNotifiedOnceApproved": { - "message": "核准後將通知您。" - }, "troubleLoggingIn": { "message": "登入時遇到困難?" }, @@ -2966,7 +3049,7 @@ "message": "缺少使用者電子郵件地址" }, "activeUserEmailNotFoundLoggingYouOut": { - "message": "Active user email not found. Logging you out." + "message": "未找到使用中帳號的電子郵件。正在將您登出。" }, "deviceTrusted": { "message": "裝置已信任" @@ -3072,7 +3155,7 @@ "message": "子選單" }, "toggleSideNavigation": { - "message": "Toggle side navigation" + "message": "切換側邊欄" }, "skipToContent": { "message": "跳至內容" @@ -3130,16 +3213,22 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + "message": "連接到 Duo 服務時發生錯誤。使用不同的兩階段認證或聯繫 Duo 來獲得支援。" }, "launchDuoAndFollowStepsToFinishLoggingIn": { - "message": "Launch Duo and follow the steps to finish logging in." + "message": "啟動 Duo 並依照步驟完成登入。" }, "duoRequiredByOrgForAccount": { - "message": "Duo two-step login is required for your account." + "message": "您的帳號要求使用 Duo 兩步驟驗證登入。" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." }, "launchDuo": { - "message": "Launch Duo in Browser" + "message": "使用瀏覽器啟動 Duo" }, "importFormatError": { "message": "資料格式不正確。請檢查匯入檔案後再試一次。" @@ -3154,7 +3243,7 @@ "message": "檔案密碼無效,請使用您當初匯出檔案時輸入的密碼。" }, "destination": { - "message": "Destination" + "message": "目的" }, "learnAboutImportOptions": { "message": "瞭解更多匯入選項" @@ -3293,7 +3382,7 @@ "description": "Label indicating the most common import formats" }, "success": { - "message": "Success" + "message": "成功" }, "troubleshooting": { "message": "疑難排解" @@ -3311,13 +3400,13 @@ "message": "密碼金鑰已移除" }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "指定目標集合時發生錯誤。" }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "指定目標資料夾時發生錯誤。" }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "檢視 $NAME$ 中的項目", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -3327,7 +3416,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "回到 $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -3337,11 +3426,11 @@ } }, "back": { - "message": "Back", + "message": "返回", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "移除 $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -3351,67 +3440,106 @@ } }, "data": { - "message": "Data" + "message": "資料" }, "fileSends": { - "message": "File Sends" + "message": "檔案 Send" }, "textSends": { - "message": "Text Sends" + "message": "文字 Sends" }, "ssoError": { - "message": "No free ports could be found for the sso login." + "message": "無法找到可用於 SSO 登入的空閒連接埠。" + }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "需要 PIN 碼或密碼解鎖才能使用生物辨識解鎖。" + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "生物辨識解鎖暫時無法使用。" + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "由於系統檔案不正確,生物辨識解鎖無法使用。" + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "由於系統檔案不正確,生物辨識解鎖無法使用。" + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "由於未在 Bitwarden 桌面應用程式的 $EMAIL$ 帳號上啟動,生物辨識解鎖無法使用。", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "基於不明原因,生物辨識解鎖無法使用。" }, "authorize": { - "message": "Authorize" + "message": "授權" }, "deny": { - "message": "Deny" + "message": "拒絕" }, "sshkeyApprovalTitle": { - "message": "Confirm SSH key usage" + "message": "確認 SSH 密鑰使用" + }, + "agentForwardingWarningTitle": { + "message": "Warning: Agent Forwarding" + }, + "agentForwardingWarningText": { + "message": "This request comes from a remote device that you are logged into" }, "sshkeyApprovalMessageInfix": { - "message": "is requesting access to" + "message": "正在請求存取權限到" + }, + "sshkeyApprovalMessageSuffix": { + "message": "in order to" + }, + "sshActionLogin": { + "message": "authenticate to a server" + }, + "sshActionSign": { + "message": "sign a message" + }, + "sshActionGitSign": { + "message": "sign a git commit" }, "unknownApplication": { - "message": "An application" - }, - "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" + "message": "應用程式" }, "invalidSshKey": { - "message": "The SSH key is invalid" + "message": "SSH 密鑰不正確" }, "sshKeyTypeUnsupported": { - "message": "The SSH key type is not supported" + "message": "SSH 密鑰類型不支援" }, "importSshKeyFromClipboard": { - "message": "Import key from clipboard" + "message": "從剪貼簿中匯入密鑰" }, - "sshKeyPasted": { + "sshKeyImported": { "message": "SSH key imported successfully" }, "fileSavedToDevice": { - "message": "File saved to device. Manage from your device downloads." + "message": "檔案已儲存到裝置。在您的裝置上管理下載檔案。" }, "importantNotice": { - "message": "Important notice" + "message": "重要通知" }, "setupTwoStepLogin": { - "message": "Set up two-step login" + "message": "啟動兩階段登入" }, "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + "message": "從 2025 年 2 月開始,Bitwarden 會傳送代碼到您的帳號電子郵件中來驗證新裝置的登入。" }, "newDeviceVerificationNoticeContentPage2": { - "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + "message": "您可以啟動兩階段認證來保護您的帳號或更改您可以存取的電子郵件位址。" }, "remindMeLater": { - "message": "Remind me later" + "message": "稍後再提醒我" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", + "message": "您可以存取您的電子郵件位址 $EMAIL$ 嗎?", "placeholders": { "email": { "content": "$1", @@ -3420,15 +3548,36 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "不,我不行" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" + "message": "是,我可以存取我的電子郵件位址" }, "turnOnTwoStepLogin": { - "message": "Turn on two-step login" + "message": "啟動兩階段登入" }, "changeAcctEmail": { - "message": "Change account email" + "message": "更改帳號電子郵件位址" + }, + "allowScreenshots": { + "message": "Allow screen capture" + }, + "allowScreenshotsDesc": { + "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + }, + "confirmWindowStillVisibleTitle": { + "message": "Confirm window still visible" + }, + "confirmWindowStillVisibleContent": { + "message": "Please confirm that the window is still visible." + }, + "updateBrowserOrDisableFingerprintDialogTitle": { + "message": "Extension update required" + }, + "updateBrowserOrDisableFingerprintDialogMessage": { + "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" } } diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index 01d84a8f769..20c632ec4ac 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -1,10 +1,13 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import "core-js/proposals/explicit-resource-management"; + import * as path from "path"; import { app } from "electron"; import { Subject, firstValueFrom } from "rxjs"; +import { SsoUrlService } from "@bitwarden/auth/common"; import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service"; import { ClientType } from "@bitwarden/common/enums"; import { RegionConfig } from "@bitwarden/common/platform/abstractions/environment.service"; @@ -27,6 +30,7 @@ import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@ import { DefaultBiometricStateService } from "@bitwarden/key-management"; /* eslint-enable import/no-restricted-paths */ +import { MainSshAgentService } from "./autofill/main/main-ssh-agent.service"; import { DesktopAutofillSettingsService } from "./autofill/services/desktop-autofill-settings.service"; import { DesktopBiometricsService } from "./key-management/biometrics/desktop.biometrics.service"; import { MainBiometricsIPCListener } from "./key-management/biometrics/main-biometrics-ipc.listener"; @@ -42,7 +46,6 @@ import { NativeAutofillMain } from "./platform/main/autofill/native-autofill.mai import { ClipboardMain } from "./platform/main/clipboard.main"; import { DesktopCredentialStorageListener } from "./platform/main/desktop-credential-storage-listener"; import { MainCryptoFunctionService } from "./platform/main/main-crypto-function.service"; -import { MainSshAgentService } from "./platform/main/main-ssh-agent.service"; import { VersionMain } from "./platform/main/version.main"; import { DesktopSettingsService } from "./platform/services/desktop-settings.service"; import { ElectronLogMainService } from "./platform/services/electron-log.main.service"; @@ -66,6 +69,7 @@ export class Main { desktopSettingsService: DesktopSettingsService; mainCryptoFunctionService: MainCryptoFunctionService; migrationRunner: MigrationRunner; + ssoUrlService: SsoUrlService; windowMain: WindowMain; messagingMain: MessagingMain; @@ -133,12 +137,6 @@ export class Main { this.mainCryptoFunctionService = new MainCryptoFunctionService(); this.mainCryptoFunctionService.init(); - const accountService = new AccountServiceImplementation( - MessageSender.EMPTY, - this.logService, - globalStateProvider, - ); - const stateEventRegistrarService = new StateEventRegistrarService( globalStateProvider, storageServiceProvider, @@ -150,6 +148,13 @@ export class Main { this.logService, ); + const accountService = new AccountServiceImplementation( + MessageSender.EMPTY, + this.logService, + globalStateProvider, + singleUserStateProvider, + ); + const activeUserStateProvider = new DefaultActiveUserStateProvider( accountService, singleUserStateProvider, @@ -204,6 +209,14 @@ export class Main { new ElectronMainMessagingService(this.windowMain), ); + this.trayMain = new TrayMain( + this.windowMain, + this.i18nService, + this.desktopSettingsService, + this.messagingService, + this.biometricsService, + ); + messageSubject.asObservable().subscribe((message) => { void this.messagingMain.onMessage(message).catch((err) => { this.logService.error( @@ -231,7 +244,7 @@ export class Main { this.windowMain, this.i18nService, this.desktopSettingsService, - biometricStateService, + this.messagingService, this.biometricsService, ); @@ -260,7 +273,13 @@ export class Main { this.sshAgentService = new MainSshAgentService(this.logService, this.messagingService); new EphemeralValueStorageService(); - new SSOLocalhostCallbackService(this.environmentService, this.messagingService); + + this.ssoUrlService = new SsoUrlService(); + new SSOLocalhostCallbackService( + this.environmentService, + this.messagingService, + this.ssoUrlService, + ); this.nativeAutofillMain = new NativeAutofillMain(this.logService, this.windowMain); void this.nativeAutofillMain.init(); @@ -273,6 +292,8 @@ export class Main { this.migrationRunner.run().then( async () => { await this.toggleHardwareAcceleration(); + // Reset modal mode to make sure main window is displayed correctly + await this.desktopSettingsService.resetModalMode(); await this.windowMain.init(); await this.i18nService.init(); await this.messagingMain.init(); diff --git a/apps/desktop/src/main/messaging.main.ts b/apps/desktop/src/main/messaging.main.ts index 0d86a7234e6..bb4063d64fd 100644 --- a/apps/desktop/src/main/messaging.main.ts +++ b/apps/desktop/src/main/messaging.main.ts @@ -37,6 +37,10 @@ export class MessagingMain { async onMessage(message: any) { switch (message.command) { + case "loadurl": + // TODO: Remove this once fakepopup is removed from tray (just used for dev) + await this.main.windowMain.loadUrl(message.url, message.modal); + break; case "scheduleNextSync": this.scheduleNextSync(); break; diff --git a/apps/desktop/src/main/native-messaging.main.ts b/apps/desktop/src/main/native-messaging.main.ts index 6915a18deda..107d546811c 100644 --- a/apps/desktop/src/main/native-messaging.main.ts +++ b/apps/desktop/src/main/native-messaging.main.ts @@ -405,6 +405,8 @@ export class NativeMessagingMain { this.logService.info(`Error reading preferences: ${e}`); } } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // Browser is not installed, we can just skip it } diff --git a/apps/desktop/src/main/tray.main.ts b/apps/desktop/src/main/tray.main.ts index 9fa7fe6143f..b7ddefe6e1b 100644 --- a/apps/desktop/src/main/tray.main.ts +++ b/apps/desktop/src/main/tray.main.ts @@ -6,9 +6,11 @@ import { app, BrowserWindow, Menu, MenuItemConstructorOptions, nativeImage, Tray import { firstValueFrom } from "rxjs"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { BiometricStateService, BiometricsService } from "@bitwarden/key-management"; +import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { BiometricsService } from "@bitwarden/key-management"; import { DesktopSettingsService } from "../platform/services/desktop-settings.service"; +import { isDev } from "../utils"; import { WindowMain } from "./window.main"; @@ -24,7 +26,7 @@ export class TrayMain { private windowMain: WindowMain, private i18nService: I18nService, private desktopSettingsService: DesktopSettingsService, - private biometricsStateService: BiometricStateService, + private messagingService: MessagingService, private biometricService: BiometricsService, ) { if (process.platform === "win32") { @@ -49,6 +51,11 @@ export class TrayMain { label: this.i18nService.t("showHide"), click: () => this.toggleWindow(), }, + { + visible: isDev(), + label: "Fake Popup", + click: () => this.fakePopup(), + }, { type: "separator" }, { label: this.i18nService.t("exit"), @@ -190,7 +197,7 @@ export class TrayMain { this.hideDock(); } } else { - this.windowMain.win.show(); + this.windowMain.show(); if (this.isDarwin()) { this.showDock(); } @@ -203,4 +210,12 @@ export class TrayMain { this.windowMain.win.close(); } } + + /** + * This method is used to test modal behavior during development and could be removed in the future. + * @returns + */ + private async fakePopup() { + await this.messagingService.send("loadurl", { url: "/passkeys", modal: true }); + } } diff --git a/apps/desktop/src/main/window.main.ts b/apps/desktop/src/main/window.main.ts index 8fe17772072..73231f1f730 100644 --- a/apps/desktop/src/main/window.main.ts +++ b/apps/desktop/src/main/window.main.ts @@ -5,14 +5,16 @@ import * as path from "path"; import * as url from "url"; import { app, BrowserWindow, ipcMain, nativeTheme, screen, session } from "electron"; -import { firstValueFrom } from "rxjs"; +import { concatMap, firstValueFrom, pairwise } from "rxjs"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; +import { ThemeTypes, Theme } from "@bitwarden/common/platform/enums"; import { processisolations } from "@bitwarden/desktop-napi"; import { BiometricStateService } from "@bitwarden/key-management"; import { WindowState } from "../platform/models/domain/window-state"; +import { applyMainWindowStyles, applyPopupModalStyles } from "../platform/popup-modal-styles"; import { DesktopSettingsService } from "../platform/services/desktop-settings.service"; import { cleanUserAgent, isDev, isLinux, isMac, isMacAppStore, isWindows } from "../utils"; @@ -76,6 +78,32 @@ export class WindowMain { } }); + this.desktopSettingsService.modalMode$ + .pipe( + pairwise(), + concatMap(async ([lastValue, newValue]) => { + if (lastValue.isModalModeActive && !newValue.isModalModeActive) { + // Reset the window state to the main window state + applyMainWindowStyles(this.win, this.windowStates[mainWindowSizeKey]); + // Because modal is used in front of another app, UX wise it makes sense to hide the main window when leaving modal mode. + this.win.hide(); + } else if (!lastValue.isModalModeActive && newValue.isModalModeActive) { + // Apply the popup modal styles + this.logService.info("Applying popup modal styles", newValue.modalPosition); + applyPopupModalStyles(this.win, newValue.modalPosition); + this.win.show(); + } + }), + ) + .subscribe(); + + this.desktopSettingsService.preventScreenshots$.subscribe((prevent) => { + if (this.win == null) { + return; + } + this.win.setContentProtection(prevent); + }); + return new Promise((resolve, reject) => { try { if (!isMacAppStore()) { @@ -84,7 +112,6 @@ export class WindowMain { app.quit(); return; } else { - // eslint-disable-next-line app.on("second-instance", (event, argv, workingDirectory) => { // Someone tried to run a second instance, we should focus our window. if (this.win != null) { @@ -175,7 +202,49 @@ export class WindowMain { }); } - async createWindow(): Promise { + /// Show the window with main window styles + show() { + if (this.win != null) { + applyMainWindowStyles(this.win, this.windowStates[mainWindowSizeKey]); + this.win.show(); + } + } + + // TODO: REMOVE ONCE WE CAN STOP USING FAKE POP UP BTN FROM TRAY + // Only used for development + async loadUrl(targetPath: string, modal: boolean = false) { + if (this.win == null || this.win.isDestroyed()) { + await this.createWindow("modal-app"); + return; + } + + await this.desktopSettingsService.setModalMode(modal); + await this.win.loadURL( + url.format({ + protocol: "file:", + //pathname: `${__dirname}/index.html`, + pathname: path.join(__dirname, "/index.html"), + slashes: true, + hash: targetPath, + query: { + redirectUrl: targetPath, + }, + }), + { + userAgent: cleanUserAgent(this.win.webContents.userAgent), + }, + ); + this.win.once("ready-to-show", () => { + this.win.show(); + }); + } + + /** + * Creates the main window. The template argument is used to determine the styling of the window and what url will be loaded. + * When the template is "modal-app", the window will be styled as a modal and the passkeys page will be loaded. + * TODO: We might want to refactor the template argument to accomodate more target pages, e.g. ssh-agent. + */ + async createWindow(template: "full-app" | "modal-app" = "full-app"): Promise { this.windowStates[mainWindowSizeKey] = await this.getWindowState( this.defaultWidth, this.defaultHeight, @@ -209,6 +278,12 @@ export class WindowMain { }, }); + if (template === "modal-app") { + applyPopupModalStyles(this.win); + } else { + applyMainWindowStyles(this.win, this.windowStates[mainWindowSizeKey]); + } + this.win.webContents.on("dom-ready", () => { this.win.webContents.zoomFactor = this.windowStates[mainWindowSizeKey].zoomFactor ?? 1.0; }); @@ -218,21 +293,41 @@ export class WindowMain { } // Show it later since it might need to be maximized. - this.win.show(); + // use once event to avoid flash on unstyled content. + this.win.once("ready-to-show", () => { + this.win.show(); + }); - // and load the index.html of the app. - // 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.win.loadURL( - url.format({ - protocol: "file:", - pathname: path.join(__dirname, "/index.html"), - slashes: true, - }), - { - userAgent: cleanUserAgent(this.win.webContents.userAgent), - }, - ); + if (template === "full-app") { + // and load the index.html of the app. + // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. + void this.win.loadURL( + url.format({ + protocol: "file:", + pathname: path.join(__dirname, "/index.html"), + slashes: true, + }), + { + userAgent: cleanUserAgent(this.win.webContents.userAgent), + }, + ); + } else { + // we're in modal mode - load the passkeys page + await this.win.loadURL( + url.format({ + protocol: "file:", + pathname: path.join(__dirname, "/index.html"), + slashes: true, + hash: "/passkeys", + query: { + redirectUrl: "/passkeys", + }, + }), + { + userAgent: cleanUserAgent(this.win.webContents.userAgent), + }, + ); + } // Open the DevTools. if (isDev()) { @@ -277,17 +372,29 @@ export class WindowMain { }); }); + firstValueFrom(this.desktopSettingsService.preventScreenshots$) + .then((preventScreenshots) => { + this.win.setContentProtection(preventScreenshots); + }) + .catch((e) => { + this.logService.error(e); + }); + if (this.createWindowCallback) { this.createWindowCallback(this.win); } } // Retrieve the background color - // Resolves background color missmatch when starting the application. + // Resolves background color mismatch when starting the application. async getBackgroundColor(): Promise { let theme = await this.storageService.get("global_theming_selection"); - if (theme == null || theme === "system") { + if ( + theme == null || + !Object.values(ThemeTypes).includes(theme as Theme) || + theme === "system" + ) { theme = nativeTheme.shouldUseDarkColors ? "dark" : "light"; } @@ -296,8 +403,6 @@ export class WindowMain { return "#ededed"; case "dark": return "#15181e"; - case "nord": - return "#3b4252"; } } @@ -319,6 +424,12 @@ export class WindowMain { return; } + const modalMode = await firstValueFrom(this.desktopSettingsService.modalMode$); + + if (modalMode.isModalModeActive) { + return; + } + try { const bounds = win.getBounds(); @@ -329,9 +440,14 @@ export class WindowMain { } } - this.windowStates[configKey].isMaximized = win.isMaximized(); + // We treat fullscreen as maximized (would be even better to store isFullscreen as its own flag). + this.windowStates[configKey].isMaximized = win.isMaximized() || win.isFullScreen(); this.windowStates[configKey].displayBounds = screen.getDisplayMatching(bounds).bounds; + // Maybe store these as well? + // win.isFocused(); + // win.isVisible(); + if (!win.isMaximized() && !win.isMinimized() && !win.isFullScreen()) { this.windowStates[configKey].x = bounds.x; this.windowStates[configKey].y = bounds.y; diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index f93d9059bc7..effbb27ad23 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2025.1.0", + "version": "2025.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2025.1.0", + "version": "2025.2.1", "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 39e16815020..713d9074f8c 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.1.0", + "version": "2025.3.0", "author": "Bitwarden Inc. (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/apps/desktop/src/platform/app/locales.ts b/apps/desktop/src/platform/app/locales.ts index 351f6fb3869..5e86a596d6a 100644 --- a/apps/desktop/src/platform/app/locales.ts +++ b/apps/desktop/src/platform/app/locales.ts @@ -18,6 +18,7 @@ import localeEo from "@angular/common/locales/eo"; import localeEs from "@angular/common/locales/es"; import localeEt from "@angular/common/locales/et"; import localeEu from "@angular/common/locales/eu"; +import localeExtraZhTw from "@angular/common/locales/extra/zh-Hant"; import localeFa from "@angular/common/locales/fa"; import localeFi from "@angular/common/locales/fi"; import localeFil from "@angular/common/locales/fil"; @@ -125,4 +126,4 @@ registerLocaleData(localeTr, "tr"); registerLocaleData(localeUk, "uk"); registerLocaleData(localeVi, "vi"); registerLocaleData(localeZhCn, "zh-CN"); -registerLocaleData(localeZhTw, "zh-TW"); +registerLocaleData(localeZhTw, "zh-TW", localeExtraZhTw); diff --git a/apps/desktop/src/platform/components/approve-ssh-request.html b/apps/desktop/src/platform/components/approve-ssh-request.html index eac451a1fbe..b7005872f25 100644 --- a/apps/desktop/src/platform/components/approve-ssh-request.html +++ b/apps/desktop/src/platform/components/approve-ssh-request.html @@ -2,16 +2,25 @@
{{ "sshkeyApprovalTitle" | i18n }}
+ + {{ 'agentForwardingWarningText' | i18n }} + + {{params.applicationName}} {{ "sshkeyApprovalMessageInfix" | i18n }} - {{params.cipherName}}. + {{params.cipherName}} + {{ "sshkeyApprovalMessageSuffix" | i18n }} {{ params.action | i18n }}
-
+ -
+
diff --git a/apps/desktop/src/platform/components/approve-ssh-request.ts b/apps/desktop/src/platform/components/approve-ssh-request.ts index 62200962dca..c2351f6c1b0 100644 --- a/apps/desktop/src/platform/components/approve-ssh-request.ts +++ b/apps/desktop/src/platform/components/approve-ssh-request.ts @@ -10,13 +10,15 @@ import { DialogModule, FormFieldModule, IconButtonModule, + DialogService, } from "@bitwarden/components"; -import { DialogService } from "@bitwarden/components/src/dialog"; import { CipherFormGeneratorComponent } from "@bitwarden/vault"; export interface ApproveSshRequestParams { cipherName: string; applicationName: string; + isAgentForwarding: boolean; + action: string; } @Component({ @@ -44,11 +46,26 @@ export class ApproveSshRequestComponent { private formBuilder: FormBuilder, ) {} - static open(dialogService: DialogService, cipherName: string, applicationName: string) { + static open( + dialogService: DialogService, + cipherName: string, + applicationName: string, + isAgentForwarding: boolean, + namespace: string, + ) { + let actioni18nKey = "sshActionLogin"; + if (namespace === "git") { + actioni18nKey = "sshActionGitSign"; + } else if (namespace != null && namespace != "") { + actioni18nKey = "sshActionSign"; + } + return dialogService.open(ApproveSshRequestComponent, { data: { cipherName, applicationName, + isAgentForwarding, + action: actioni18nKey, }, }); } diff --git a/apps/desktop/src/platform/flags.ts b/apps/desktop/src/platform/flags.ts index dc0103e2436..a762053da35 100644 --- a/apps/desktop/src/platform/flags.ts +++ b/apps/desktop/src/platform/flags.ts @@ -7,11 +7,9 @@ import { } from "@bitwarden/common/platform/misc/flags"; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type Flags = {} & SharedFlags; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type DevFlags = {} & SharedDevFlags; export function flagEnabled(flag: keyof Flags): boolean { diff --git a/apps/desktop/src/platform/main/autofill/native-autofill.main.ts b/apps/desktop/src/platform/main/autofill/native-autofill.main.ts index 1465831340f..f66eea180cf 100644 --- a/apps/desktop/src/platform/main/autofill/native-autofill.main.ts +++ b/apps/desktop/src/platform/main/autofill/native-autofill.main.ts @@ -40,6 +40,7 @@ export class NativeAutofillMain { (error, clientId, sequenceNumber, request) => { if (error) { this.logService.error("autofill.IpcServer.registration", error); + this.ipcServer.completeError(clientId, sequenceNumber, String(error)); return; } this.windowMain.win.webContents.send("autofill.passkeyRegistration", { @@ -52,6 +53,7 @@ export class NativeAutofillMain { (error, clientId, sequenceNumber, request) => { if (error) { this.logService.error("autofill.IpcServer.assertion", error); + this.ipcServer.completeError(clientId, sequenceNumber, String(error)); return; } this.windowMain.win.webContents.send("autofill.passkeyAssertion", { @@ -60,6 +62,19 @@ export class NativeAutofillMain { request, }); }, + // AssertionWithoutUserInterfaceCallback + (error, clientId, sequenceNumber, request) => { + if (error) { + this.logService.error("autofill.IpcServer.assertion", error); + this.ipcServer.completeError(clientId, sequenceNumber, String(error)); + return; + } + this.windowMain.win.webContents.send("autofill.passkeyAssertionWithoutUserInterface", { + clientId, + sequenceNumber, + request, + }); + }, ); ipcMain.on("autofill.completePasskeyRegistration", (event, data) => { @@ -77,7 +92,7 @@ export class NativeAutofillMain { ipcMain.on("autofill.completeError", (event, data) => { this.logService.warning("autofill.completeError", data); const { clientId, sequenceNumber, error } = data; - this.ipcServer.completeAssertion(clientId, sequenceNumber, error); + this.ipcServer.completeError(clientId, sequenceNumber, String(error)); }); } diff --git a/apps/desktop/src/platform/models/domain/window-state.ts b/apps/desktop/src/platform/models/domain/window-state.ts index 00230319972..0efc9a1efab 100644 --- a/apps/desktop/src/platform/models/domain/window-state.ts +++ b/apps/desktop/src/platform/models/domain/window-state.ts @@ -11,3 +11,8 @@ export class WindowState { y?: number; zoomFactor?: number; } + +export class ModalModeState { + isModalModeActive: boolean; + modalPosition?: { x: number; y: number }; // Modal position is often passed from the native UI +} diff --git a/apps/desktop/src/platform/popup-modal-styles.ts b/apps/desktop/src/platform/popup-modal-styles.ts new file mode 100644 index 00000000000..ae46ebb5c76 --- /dev/null +++ b/apps/desktop/src/platform/popup-modal-styles.ts @@ -0,0 +1,66 @@ +import { BrowserWindow } from "electron"; + +import { WindowState } from "./models/domain/window-state"; + +// change as needed, however limited by mainwindow minimum size +const popupWidth = 680; +const popupHeight = 500; + +type Position = { x: number; y: number }; + +export function applyPopupModalStyles(window: BrowserWindow, position?: Position) { + window.unmaximize(); + window.setSize(popupWidth, popupHeight); + window.setWindowButtonVisibility?.(false); + window.setMenuBarVisibility?.(false); + window.setResizable(false); + window.setAlwaysOnTop(true); + + // Adjusting from full screen is a bit more hassle + if (window.isFullScreen()) { + window.setFullScreen(false); + window.once("leave-full-screen", () => { + window.setSize(popupWidth, popupHeight); + positionWindow(window, position); + }); + } else { + // If not in full screen + positionWindow(window, position); + } +} + +function positionWindow(window: BrowserWindow, position?: Position) { + if (position) { + const centeredX = position.x - popupWidth / 2; + const centeredY = position.y - popupHeight / 2; + window.setPosition(centeredX, centeredY); + } else { + window.center(); + } +} + +export function applyMainWindowStyles(window: BrowserWindow, existingWindowState: WindowState) { + window.setMinimumSize(680, 500); + + // need to guard against null/undefined values + + if (existingWindowState?.width && existingWindowState?.height) { + window.setSize(existingWindowState.width, existingWindowState.height); + } + + if (existingWindowState?.x && existingWindowState?.y) { + window.setPosition(existingWindowState.x, existingWindowState.y); + } + + window.setWindowButtonVisibility?.(true); + window.setMenuBarVisibility?.(true); + window.setResizable(true); + window.setAlwaysOnTop(false); + + // We're currently not recovering the maximized state, mostly due to conflicts with hiding the window. + // window.setFullScreen(existingWindowState.isMaximized); + + // if (existingWindowState.isMaximized) { + // window.maximize(); + // } +} diff --git a/apps/desktop/src/platform/preload.ts b/apps/desktop/src/platform/preload.ts index a37274677ff..bf81021922f 100644 --- a/apps/desktop/src/platform/preload.ts +++ b/apps/desktop/src/platform/preload.ts @@ -1,4 +1,3 @@ -import { sshagent as ssh } from "desktop_native/napi"; import { ipcRenderer } from "electron"; import { DeviceType } from "@bitwarden/common/enums"; @@ -64,13 +63,6 @@ const sshAgent = { clearKeys: async () => { return await ipcRenderer.invoke("sshagent.clearkeys"); }, - importKey: async (key: string, password: string): Promise => { - const res = await ipcRenderer.invoke("sshagent.importkey", { - privateKey: key, - password: password, - }); - return res; - }, isLoaded(): Promise { return ipcRenderer.invoke("sshagent.isloaded"); }, @@ -123,11 +115,12 @@ const ephemeralStore = { getEphemeralValue: (key: string): Promise => ipcRenderer.invoke("getEphemeralValue", key), removeEphemeralValue: (key: string): Promise => ipcRenderer.invoke("deleteEphemeralValue", key), + listEphemeralValueKeys: (): Promise => ipcRenderer.invoke("listEphemeralValueKeys"), }; const localhostCallbackService = { - openSsoPrompt: (codeChallenge: string, state: string): Promise => { - return ipcRenderer.invoke("openSsoPrompt", { codeChallenge, state }); + openSsoPrompt: (codeChallenge: string, state: string, email: string): Promise => { + return ipcRenderer.invoke("openSsoPrompt", { codeChallenge, state, email }); }, }; diff --git a/apps/desktop/src/platform/services/desktop-settings.service.ts b/apps/desktop/src/platform/services/desktop-settings.service.ts index c698e7d5b1b..f5789d6f40c 100644 --- a/apps/desktop/src/platform/services/desktop-settings.service.ts +++ b/apps/desktop/src/platform/services/desktop-settings.service.ts @@ -8,7 +8,7 @@ import { } from "@bitwarden/common/platform/state"; import { UserId } from "@bitwarden/common/types/guid"; -import { WindowState } from "../models/domain/window-state"; +import { ModalModeState, WindowState } from "../models/domain/window-state"; export const HARDWARE_ACCELERATION = new KeyDefinition( DESKTOP_SETTINGS_DISK, @@ -75,6 +75,18 @@ const MINIMIZE_ON_COPY = new UserKeyDefinition(DESKTOP_SETTINGS_DISK, " clearOn: [], // User setting, no need to clear }); +const MODAL_MODE = new KeyDefinition(DESKTOP_SETTINGS_DISK, "modalMode", { + deserializer: (b) => b, +}); + +const PREVENT_SCREENSHOTS = new KeyDefinition( + DESKTOP_SETTINGS_DISK, + "preventScreenshots", + { + deserializer: (b) => b, + }, +); + /** * Various settings for controlling application behavior specific to the desktop client. */ @@ -147,6 +159,13 @@ export class DesktopSettingsService { sshAgentEnabled$ = this.sshAgentEnabledState.state$.pipe(map(Boolean)); + private readonly preventScreenshotState = this.stateProvider.getGlobal(PREVENT_SCREENSHOTS); + + /** + * The application setting for whether or not to allow screenshots of the app. + */ + preventScreenshots$ = this.preventScreenshotState.state$.pipe(map(Boolean)); + private readonly minimizeOnCopyState = this.stateProvider.getActive(MINIMIZE_ON_COPY); /** @@ -155,6 +174,10 @@ export class DesktopSettingsService { */ minimizeOnCopy$ = this.minimizeOnCopyState.state$.pipe(map(Boolean)); + private readonly modalModeState = this.stateProvider.getGlobal(MODAL_MODE); + + modalMode$ = this.modalModeState.state$; + constructor(private stateProvider: StateProvider) { this.window$ = this.windowState.state$.pipe( map((window) => @@ -163,6 +186,14 @@ export class DesktopSettingsService { ); } + /** + * This is used to clear the setting on application start to make sure we don't end up + * stuck in modal mode if the application is force-closed in modal mode. + */ + async resetModalMode() { + await this.modalModeState.update(() => ({ isModalModeActive: false })); + } + async setHardwareAcceleration(enabled: boolean) { await this.hwState.update(() => enabled); } @@ -270,4 +301,23 @@ export class DesktopSettingsService { async setMinimizeOnCopy(value: boolean, userId: UserId) { await this.stateProvider.getUser(userId, MINIMIZE_ON_COPY).update(() => value); } + + /** + * Sets the modal mode of the application. Setting this changes the windows-size and other properties. + * @param value `true` if the application is in modal mode, `false` if it is not. + */ + async setModalMode(value: boolean, modalPosition?: { x: number; y: number }) { + await this.modalModeState.update(() => ({ + isModalModeActive: value, + modalPosition, + })); + } + + /** + * Sets the setting for whether or not the screenshot protection is enabled. + * @param value `true` if the screenshot protection is enabled, `false` if it is not. + */ + async setPreventScreenshots(value: boolean) { + await this.preventScreenshotState.update(() => value); + } } diff --git a/apps/desktop/src/platform/services/electron-key.service.ts b/apps/desktop/src/platform/services/electron-key.service.ts index 0db634375ef..e378f3cf374 100644 --- a/apps/desktop/src/platform/services/electron-key.service.ts +++ b/apps/desktop/src/platform/services/electron-key.service.ts @@ -1,10 +1,8 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -59,8 +57,6 @@ export class ElectronKeyService extends DefaultKeyService { } override async clearStoredUserKey(keySuffix: KeySuffixOptions, userId?: UserId): Promise { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises await super.clearStoredUserKey(keySuffix, userId); } @@ -75,14 +71,11 @@ export class ElectronKeyService extends DefaultKeyService { protected override async getKeyFromStorage( keySuffix: KeySuffixOptions, userId?: UserId, - ): Promise { + ): Promise { return await super.getKeyFromStorage(keySuffix, userId); } - protected async storeBiometricsProtectedUserKey( - userKey: UserKey, - userId?: UserId, - ): Promise { + private async storeBiometricsProtectedUserKey(userKey: UserKey, userId: UserId): Promise { // May resolve to null, in which case no client key have is required // TODO: Move to windows implementation const clientEncKeyHalf = await this.getBiometricEncryptionClientKeyHalf(userKey, userId); @@ -90,11 +83,11 @@ export class ElectronKeyService extends DefaultKeyService { await this.biometricService.setBiometricProtectedUnlockKeyForUser(userId, userKey.keyB64); } - protected async shouldStoreKey(keySuffix: KeySuffixOptions, userId?: UserId): Promise { + protected async shouldStoreKey(keySuffix: KeySuffixOptions, userId: UserId): Promise { return await super.shouldStoreKey(keySuffix, userId); } - protected override async clearAllStoredUserKeys(userId?: UserId): Promise { + protected override async clearAllStoredUserKeys(userId: UserId): Promise { await this.biometricService.deleteBiometricUnlockKeyForUser(userId); await super.clearAllStoredUserKeys(userId); } diff --git a/apps/desktop/src/platform/services/electron-platform-utils.service.ts b/apps/desktop/src/platform/services/electron-platform-utils.service.ts index b61d2a0c5e9..b7c82f4e5db 100644 --- a/apps/desktop/src/platform/services/electron-platform-utils.service.ts +++ b/apps/desktop/src/platform/services/electron-platform-utils.service.ts @@ -77,10 +77,9 @@ export class ElectronPlatformUtilsService implements PlatformUtilsService { return (await this.getApplicationVersion()).split(/[+|-]/)[0].trim(); } - // Temporarily restricted to only Windows until https://github.com/electron/electron/pull/28349 - // has been merged and an updated electron build is available. + // Linux and Mac are missing a ui to enter a pin, so this works for two-factor security keys, when always-uv is not active supportsWebAuthn(win: Window): boolean { - return this.getDevice() === DeviceType.WindowsDesktop; + return true; } supportsDuo(): boolean { diff --git a/apps/desktop/src/platform/services/ephemeral-value-storage.main.service.ts b/apps/desktop/src/platform/services/ephemeral-value-storage.main.service.ts index b59b48be1e1..0dd6ef56ae4 100644 --- a/apps/desktop/src/platform/services/ephemeral-value-storage.main.service.ts +++ b/apps/desktop/src/platform/services/ephemeral-value-storage.main.service.ts @@ -17,5 +17,8 @@ export class EphemeralValueStorageService { ipcMain.handle("deleteEphemeralValue", async (event, key: string) => { this.ephemeralValues.delete(key); }); + ipcMain.handle("listEphemeralValueKeys", async (event) => { + return Array.from(this.ephemeralValues.keys()); + }); } } diff --git a/apps/desktop/src/platform/services/sso-localhost-callback.service.ts b/apps/desktop/src/platform/services/sso-localhost-callback.service.ts index 2baba6275bc..14baee51b90 100644 --- a/apps/desktop/src/platform/services/sso-localhost-callback.service.ts +++ b/apps/desktop/src/platform/services/sso-localhost-callback.service.ts @@ -5,6 +5,8 @@ import * as http from "http"; import { ipcMain } from "electron"; import { firstValueFrom } from "rxjs"; +import { SsoUrlService } from "@bitwarden/auth/common"; +import { ClientType } from "@bitwarden/common/enums"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { MessageSender } from "@bitwarden/common/platform/messaging"; @@ -18,9 +20,10 @@ export class SSOLocalhostCallbackService { constructor( private environmentService: EnvironmentService, private messagingService: MessageSender, + private ssoUrlService: SsoUrlService, ) { - ipcMain.handle("openSsoPrompt", async (event, { codeChallenge, state }) => { - const { ssoCode, recvState } = await this.openSsoPrompt(codeChallenge, state); + ipcMain.handle("openSsoPrompt", async (event, { codeChallenge, state, email }) => { + const { ssoCode, recvState } = await this.openSsoPrompt(codeChallenge, state, email); this.messagingService.send("ssoCallback", { code: ssoCode, state: recvState, @@ -32,6 +35,7 @@ export class SSOLocalhostCallbackService { private async openSsoPrompt( codeChallenge: string, state: string, + email: string, ): Promise<{ ssoCode: string; recvState: string }> { const env = await firstValueFrom(this.environmentService.environment$); @@ -78,18 +82,17 @@ export class SSOLocalhostCallbackService { for (let port = 8065; port <= 8070; port++) { try { this.ssoRedirectUri = "http://localhost:" + port; + const ssoUrl = this.ssoUrlService.buildSsoUrl( + webUrl, + ClientType.Desktop, + this.ssoRedirectUri, + state, + codeChallenge, + email, + ); callbackServer.listen(port, () => { this.messagingService.send("launchUri", { - url: - webUrl + - "/#/sso?clientId=" + - "desktop" + - "&redirectUri=" + - encodeURIComponent(this.ssoRedirectUri) + - "&state=" + - state + - "&codeChallenge=" + - codeChallenge, + url: ssoUrl, }); }); foundPort = true; @@ -112,15 +115,6 @@ export class SSOLocalhostCallbackService { }); } - private getOrgIdentifierFromState(state: string): string { - if (state === null || state === undefined) { - return null; - } - - const stateSplit = state.split("_identifier="); - return stateSplit.length > 1 ? stateSplit[1] : null; - } - private checkState(state: string, checkState: string): boolean { if (state === null || state === undefined) { return false; diff --git a/apps/desktop/src/scss/base.scss b/apps/desktop/src/scss/base.scss index 3912d6cbf1d..22eb3df0d17 100644 --- a/apps/desktop/src/scss/base.scss +++ b/apps/desktop/src/scss/base.scss @@ -66,9 +66,9 @@ a { } } -input, +input:not(bit-form-field input), select, -textarea { +textarea:not(bit-form-field textarea) { @include themify($themes) { color: themed("textColor"); background-color: themed("inputBackgroundColor"); diff --git a/apps/desktop/src/scss/misc.scss b/apps/desktop/src/scss/misc.scss index 75a72640f2b..885040cc6f9 100644 --- a/apps/desktop/src/scss/misc.scss +++ b/apps/desktop/src/scss/misc.scss @@ -188,44 +188,6 @@ p.lead { } } -.generated-block { - font-size: $font-size-large; - font-family: $font-family-monospace; - padding: 8px 10px 8px 0; - display: flex; - border-radius: $border-radius; - border: 1px solid; - - @include themify($themes) { - background-color: transparent; - border-color: themed("borderColorAlt"); - } - - .modal-body & { - margin-top: 10px; - } - - .generated-wrapper { - text-align: left; - width: 100%; - min-width: 0; - white-space: pre-wrap; - word-break: break-all; - padding: 15px; - } - - .action-buttons { - display: flex; - align-self: center; - height: 100%; - margin-left: 10px; - - button { - margin-left: 10px; - } - } -} - .password-wrapper { overflow-wrap: break-word; white-space: pre-wrap; @@ -281,14 +243,12 @@ p.lead { } #web-authn-frame { - background: url("../images/loading.svg") 0 0 no-repeat; - height: 250px; - margin: 0 0 15px 0; + height: 40px; iframe { - width: 100%; - height: 100%; border: none; + height: 100%; + width: 100%; } } diff --git a/apps/desktop/src/scss/pages.scss b/apps/desktop/src/scss/pages.scss index fda75e834f3..b9559e13a26 100644 --- a/apps/desktop/src/scss/pages.scss +++ b/apps/desktop/src/scss/pages.scss @@ -1,12 +1,9 @@ @import "variables.scss"; -#login-page, -#login-with-device-page, #lock-page, #sso-page, #set-password-page, -#remove-password-page, -#login-decryption-options-page { +#remove-password-page { display: flex; justify-content: center; align-items: center; @@ -49,13 +46,11 @@ } #accessibility-cookie-page, -#login-page, #register-page, #hint-page, #two-factor-page, #lock-page, -#update-temp-password-page, -#login-decryption-options-page { +#update-temp-password-page { .content { width: 325px; transition: width 0.25s linear; @@ -190,73 +185,6 @@ } } -#login-page, -#login-with-device-page, -#login-decryption-options-page { - flex-direction: column; - justify-content: unset; - padding-top: 20px; - - .login-header { - align-self: flex-start; - padding: 1em; - font-size: 1.2em; - .environment-urls-settings-icon { - @include themify($themes) { - color: themed("mutedColor"); - } - - span { - visibility: hidden; - } - - &:hover, - &:focus { - text-decoration: none; - - @include themify($themes) { - color: themed("primaryColor"); - } - } - } - } -} - -#login-with-device-page { - .content { - display: block; - padding-top: 70px; - width: 350px !important; - - .fingerprint { - margin: auto; - width: 315px; - - .fingerpint-header { - padding-left: 15px; - } - } - - .section { - margin-bottom: 30px; - } - - .another-method { - display: flex; - margin: auto; - .description-text { - padding-right: 5px; - } - } - - code { - @include themify($themes) { - color: themed("codeColor"); - } - } - } -} - #login-approval-page { .section-title { padding: 20px; @@ -276,14 +204,3 @@ } } } - -#login-decryption-options-page { - .standard-bottom-margin { - margin-bottom: 20px; - } - - #rememberThisDeviceHintText { - font-size: $font-size-small; - color: $text-muted; - } -} diff --git a/apps/desktop/src/scss/tailwind.css b/apps/desktop/src/scss/tailwind.css index e58785aecb0..531133affc0 100644 --- a/apps/desktop/src/scss/tailwind.css +++ b/apps/desktop/src/scss/tailwind.css @@ -1,5 +1,5 @@ +@import "../../../../libs/components/src/tw-theme.css"; + @tailwind base; @tailwind components; @tailwind utilities; - -@import "../../../../libs/components/src/tw-theme.css"; diff --git a/apps/desktop/src/scss/variables.scss b/apps/desktop/src/scss/variables.scss index 23a4644d3da..b8978e284e5 100644 --- a/apps/desktop/src/scss/variables.scss +++ b/apps/desktop/src/scss/variables.scss @@ -1,6 +1,4 @@ -@import "~nord/src/sass/nord.scss"; - -$dark-icon-themes: "theme_dark", "theme_nord"; +$dark-icon-themes: "theme_dark"; $font-family-sans-serif: "DM Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; $font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace; @@ -166,65 +164,6 @@ $themes: ( hrColor: #bac0ce, codeColor: $code-color-dark, ), - nord: ( - textColor: $nord5, - borderColor: $nord0, - backgroundColor: $nord2, - borderColorAlt: $nord5, - backgroundColorAlt: $nord1, - // Ensure the `window.main.ts` is updated with this value - backgroundColorAlt2: $nord1, - scrollbarColor: $nord4, - scrollbarHoverColor: $nord6, - boxBackgroundColor: $nord2, - boxBackgroundHoverColor: $nord3, - boxBorderColor: $nord1, - headerBackgroundColor: $nord2, - headerBorderColor: $nord0, - headerInputBackgroundColor: $nord6, - headerInputBackgroundFocusColor: $nord5, - headerInputColor: $nord2, - headerInputPlaceholderColor: $nord3, - listItemBackgroundColor: $nord2, - listItemBackgroundHoverColor: $nord3, - listItemBorderColor: $nord1, - disabledIconColor: $nord5, - headingColor: $nord4, - headingButtonColor: $nord5, - headingButtonHoverColor: $nord6, - labelColor: $nord4, - mutedColor: $nord4, - totpStrokeColor: $nord4, - boxRowButtonColor: $nord4, - boxRowButtonHoverColor: $nord6, - inputBorderColor: $nord0, - inputBackgroundColor: $nord2, - inputPlaceholderColor: lighten($nord3, 20%), - buttonBackgroundColor: $nord3, - buttonBorderColor: $nord0, - buttonColor: $nord5, - buttonPrimaryColor: $nord8, - buttonDangerColor: $nord11, - primaryColor: $nord9, - primaryAccentColor: $nord8, - dangerColor: $nord11, - successColor: $nord14, - infoColor: $nord9, - warningColor: $nord12, - logoSuffix: "white", - mfaLogoSuffix: "-w.png", - passwordNumberColor: $nord8, - passwordSpecialColor: $nord12, - passwordCountText: $nord5, - calloutBorderColor: $nord1, - calloutBackgroundColor: $nord2, - toastTextColor: #000000, - accountSwitcherBackgroundColor: $nord0, - accountSwitcherTextColor: $nord5, - svgSuffix: "-dark.svg", - hrColor: $nord4, - codeColor: $code-color-nord, - ), ); @mixin themify($themes: $themes) { diff --git a/apps/desktop/src/services/biometric-message-handler.service.spec.ts b/apps/desktop/src/services/biometric-message-handler.service.spec.ts index 13b668f6b83..cc82c165219 100644 --- a/apps/desktop/src/services/biometric-message-handler.service.spec.ts +++ b/apps/desktop/src/services/biometric-message-handler.service.spec.ts @@ -2,31 +2,32 @@ import { NgZone } from "@angular/core"; import { mock, MockProxy } from "jest-mock-extended"; import { of } from "rxjs"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { AccountInfo, 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 { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; import { FakeAccountService } from "@bitwarden/common/spec"; +import { CsprngArray } from "@bitwarden/common/types/csprng"; import { UserId } from "@bitwarden/common/types/guid"; -import { DialogService } from "@bitwarden/components"; -import { KeyService, BiometricsService, BiometricStateService } from "@bitwarden/key-management"; +import { DialogService, I18nMockService } from "@bitwarden/components"; +import { + KeyService, + BiometricsService, + BiometricStateService, + BiometricsCommands, +} from "@bitwarden/key-management"; import { DesktopSettingsService } from "../platform/services/desktop-settings.service"; import { BiometricMessageHandlerService } from "./biometric-message-handler.service"; -(global as any).ipc = { - platform: { - reloadProcess: jest.fn(), - }, -}; - const SomeUser = "SomeUser" as UserId; const AnotherUser = "SomeOtherUser" as UserId; -const accounts = { +const accounts: Record = { [SomeUser]: { name: "some user", email: "some.user@example.com", @@ -54,6 +55,7 @@ describe("BiometricMessageHandlerService", () => { let accountService: AccountService; let authService: MockProxy; let ngZone: MockProxy; + let i18nService: MockProxy; beforeEach(() => { cryptoFunctionService = mock(); @@ -69,6 +71,24 @@ describe("BiometricMessageHandlerService", () => { accountService = new FakeAccountService(accounts); authService = mock(); ngZone = mock(); + i18nService = mock(); + + (global as any).ipc = { + platform: { + ephemeralStore: { + listEphemeralValueKeys: jest.fn(), + getEphemeralValue: jest.fn(), + removeEphemeralValue: jest.fn(), + setEphemeralValue: jest.fn(), + }, + nativeMessaging: { + sendMessage: jest.fn(), + }, + reloadProcess: jest.fn(), + }, + }; + cryptoFunctionService.rsaEncrypt.mockResolvedValue(Utils.fromUtf8ToArray("encrypted")); + cryptoFunctionService.randomBytes.mockResolvedValue(new Uint8Array(64) as CsprngArray); service = new BiometricMessageHandlerService( cryptoFunctionService, @@ -83,9 +103,281 @@ describe("BiometricMessageHandlerService", () => { accountService, authService, ngZone, + i18nService, ); }); + describe("setup encryption", () => { + it("should ignore when public key missing in message", async () => { + await service.handleMessage({ + appId: "appId", + message: { + command: "setupEncryption", + messageId: 0, + userId: "unknownUser" as UserId, + }, + }); + expect((global as any).ipc.platform.nativeMessaging.sendMessage).not.toHaveBeenCalled(); + }); + + it("should ignore when user id missing in message", async () => { + await service.handleMessage({ + appId: "appId", + message: { + command: "setupEncryption", + messageId: 0, + publicKey: Utils.fromUtf8ToB64("publicKey"), + }, + }); + expect((global as any).ipc.platform.nativeMessaging.sendMessage).not.toHaveBeenCalled(); + }); + + it("should reject when user is not in app", async () => { + await service.handleMessage({ + appId: "appId", + message: { + command: "setupEncryption", + messageId: 0, + userId: "unknownUser" as UserId, + publicKey: Utils.fromUtf8ToB64("publicKey"), + }, + }); + expect((global as any).ipc.platform.nativeMessaging.sendMessage).toHaveBeenCalledWith({ + appId: "appId", + command: "wrongUserId", + }); + }); + + it("should setup secure communication", async () => { + (global as any).ipc.platform.ephemeralStore.listEphemeralValueKeys.mockResolvedValue([ + "connectedApp_appId", + ]); + (global as any).ipc.platform.ephemeralStore.getEphemeralValue.mockResolvedValue( + JSON.stringify({ + publicKey: Utils.fromUtf8ToB64("publicKey"), + sessionSecret: null, + trusted: false, + }), + ); + await service.handleMessage({ + appId: "appId", + message: { + command: "setupEncryption", + messageId: 0, + userId: SomeUser, + publicKey: Utils.fromUtf8ToB64("publicKey"), + }, + }); + expect((global as any).ipc.platform.nativeMessaging.sendMessage).toHaveBeenCalledWith({ + appId: "appId", + command: "setupEncryption", + messageId: -1, + sharedSecret: Utils.fromUtf8ToB64("encrypted"), + }); + expect((global as any).ipc.platform.ephemeralStore.setEphemeralValue).toHaveBeenCalledWith( + "connectedApp_appId", + JSON.stringify({ + publicKey: Utils.fromUtf8ToB64("publicKey"), + sessionSecret: Utils.fromBufferToB64(new Uint8Array(64)), + trusted: false, + }), + ); + }); + + it("should invalidate encryption if connection is not secured", async () => { + (global as any).ipc.platform.ephemeralStore.listEphemeralValueKeys.mockResolvedValue([ + "connectedApp_appId", + ]); + (global as any).ipc.platform.ephemeralStore.getEphemeralValue.mockResolvedValue( + JSON.stringify({ + publicKey: Utils.fromUtf8ToB64("publicKey"), + sessionSecret: null, + trusted: false, + }), + ); + await service.handleMessage({ + appId: "appId", + message: { + command: "biometricUnlock", + messageId: 0, + userId: SomeUser, + }, + }); + expect((global as any).ipc.platform.nativeMessaging.sendMessage).toHaveBeenCalledWith({ + appId: "appId", + command: "invalidateEncryption", + }); + }); + + it("should show update dialog when legacy unlock is requested with fingerprint active", async () => { + desktopSettingsService.browserIntegrationFingerprintEnabled$ = of(true); + (global as any).ipc.platform.ephemeralStore.listEphemeralValueKeys.mockResolvedValue([ + "connectedApp_appId", + ]); + (global as any).ipc.platform.ephemeralStore.getEphemeralValue.mockResolvedValue( + JSON.stringify({ + publicKey: Utils.fromUtf8ToB64("publicKey"), + sessionSecret: Utils.fromBufferToB64(new Uint8Array(64)), + trusted: false, + }), + ); + encryptService.decryptToUtf8.mockResolvedValue( + JSON.stringify({ + command: "biometricUnlock", + messageId: 0, + timestamp: Date.now(), + userId: SomeUser, + }), + ); + await service.handleMessage({ + appId: "appId", + message: { + command: "biometricUnlock", + messageId: 0, + timestamp: Date.now(), + userId: SomeUser, + }, + }); + expect(dialogService.openSimpleDialog).toHaveBeenCalled(); + }); + + it("should send verify fingerprint when fingerprinting is required on modern unlock, and dialog is accepted, and set to trusted", async () => { + desktopSettingsService.browserIntegrationFingerprintEnabled$ = of(true); + (global as any).ipc.platform.ephemeralStore.listEphemeralValueKeys.mockResolvedValue([ + "connectedApp_appId", + ]); + (global as any).ipc.platform.ephemeralStore.getEphemeralValue.mockResolvedValue( + JSON.stringify({ + publicKey: Utils.fromUtf8ToB64("publicKey"), + sessionSecret: Utils.fromBufferToB64(new Uint8Array(64)), + trusted: false, + }), + ); + ngZone.run.mockReturnValue({ + closed: of(true), + }); + encryptService.decryptToUtf8.mockResolvedValue( + JSON.stringify({ + command: BiometricsCommands.UnlockWithBiometricsForUser, + messageId: 0, + timestamp: Date.now(), + userId: SomeUser, + }), + ); + await service.handleMessage({ + appId: "appId", + message: { + command: BiometricsCommands.UnlockWithBiometricsForUser, + messageId: 0, + timestamp: Date.now(), + userId: SomeUser, + }, + }); + + expect(ipc.platform.nativeMessaging.sendMessage).toHaveBeenCalledWith({ + command: "verifyDesktopIPCFingerprint", + appId: "appId", + }); + expect(ipc.platform.nativeMessaging.sendMessage).toHaveBeenCalledWith({ + command: "verifiedDesktopIPCFingerprint", + appId: "appId", + }); + expect(ipc.platform.ephemeralStore.setEphemeralValue).toHaveBeenCalledWith( + "connectedApp_appId", + JSON.stringify({ + publicKey: Utils.fromUtf8ToB64("publicKey"), + sessionSecret: Utils.fromBufferToB64(new Uint8Array(64)), + trusted: true, + }), + ); + }); + + it("should send reject fingerprint when fingerprinting is required on modern unlock, and dialog is rejected, and it should not set to trusted", async () => { + desktopSettingsService.browserIntegrationFingerprintEnabled$ = of(true); + (global as any).ipc.platform.ephemeralStore.listEphemeralValueKeys.mockResolvedValue([ + "connectedApp_appId", + ]); + (global as any).ipc.platform.ephemeralStore.getEphemeralValue.mockResolvedValue( + JSON.stringify({ + publicKey: Utils.fromUtf8ToB64("publicKey"), + sessionSecret: Utils.fromBufferToB64(new Uint8Array(64)), + trusted: false, + }), + ); + ngZone.run.mockReturnValue({ + closed: of(false), + }); + encryptService.decryptToUtf8.mockResolvedValue( + JSON.stringify({ + command: BiometricsCommands.UnlockWithBiometricsForUser, + messageId: 0, + timestamp: Date.now(), + userId: SomeUser, + }), + ); + await service.handleMessage({ + appId: "appId", + message: { + command: BiometricsCommands.UnlockWithBiometricsForUser, + messageId: 0, + timestamp: Date.now(), + userId: SomeUser, + }, + }); + expect(ipc.platform.nativeMessaging.sendMessage).toHaveBeenCalledWith({ + command: "verifyDesktopIPCFingerprint", + appId: "appId", + }); + expect(ipc.platform.nativeMessaging.sendMessage).toHaveBeenCalledWith({ + command: "rejectedDesktopIPCFingerprint", + appId: "appId", + }); + expect(ipc.platform.ephemeralStore.setEphemeralValue).not.toHaveBeenCalledWith( + "connectedApp_appId", + JSON.stringify({ + publicKey: Utils.fromUtf8ToB64("publicKey"), + sessionSecret: Utils.fromBufferToB64(new Uint8Array(64)), + trusted: true, + }), + ); + }); + + it("should not attempt to verify when the connected app is already trusted", async () => { + desktopSettingsService.browserIntegrationFingerprintEnabled$ = of(true); + (global as any).ipc.platform.ephemeralStore.listEphemeralValueKeys.mockResolvedValue([ + "connectedApp_appId", + ]); + (global as any).ipc.platform.ephemeralStore.getEphemeralValue.mockResolvedValue( + JSON.stringify({ + publicKey: Utils.fromUtf8ToB64("publicKey"), + sessionSecret: Utils.fromBufferToB64(new Uint8Array(64)), + trusted: true, + }), + ); + encryptService.decryptToUtf8.mockResolvedValue( + JSON.stringify({ + command: BiometricsCommands.UnlockWithBiometricsForUser, + messageId: 0, + timestamp: Date.now(), + userId: SomeUser, + }), + ); + await service.handleMessage({ + appId: "appId", + message: { + command: BiometricsCommands.UnlockWithBiometricsForUser, + messageId: 0, + timestamp: Date.now(), + userId: SomeUser, + }, + }); + expect(ipc.platform.nativeMessaging.sendMessage).not.toHaveBeenCalledWith({ + command: "verifyDesktopIPCFingerprint", + appId: "appId", + }); + }); + }); + describe("process reload", () => { const testCases = [ // don't reload when the active user is the requested one and unlocked @@ -95,12 +387,15 @@ describe("BiometricMessageHandlerService", () => { // always reload when another user is active than the requested one [SomeUser, AuthenticationStatus.Unlocked, AnotherUser, false, true], [SomeUser, AuthenticationStatus.Locked, AnotherUser, false, true], + // don't reload when no active user + [null, AuthenticationStatus.Unlocked, AnotherUser, false, false], // don't reload in dev mode [SomeUser, AuthenticationStatus.Unlocked, SomeUser, true, false], [SomeUser, AuthenticationStatus.Locked, SomeUser, true, false], [SomeUser, AuthenticationStatus.Unlocked, AnotherUser, true, false], [SomeUser, AuthenticationStatus.Locked, AnotherUser, true, false], + [null, AuthenticationStatus.Unlocked, AnotherUser, true, false], ]; it.each(testCases)( diff --git a/apps/desktop/src/services/biometric-message-handler.service.ts b/apps/desktop/src/services/biometric-message-handler.service.ts index ea1e7e76c56..7f4adce29d9 100644 --- a/apps/desktop/src/services/biometric-message-handler.service.ts +++ b/apps/desktop/src/services/biometric-message-handler.service.ts @@ -1,13 +1,12 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Injectable, NgZone } from "@angular/core"; -import { firstValueFrom, map } from "rxjs"; +import { combineLatest, concatMap, firstValueFrom, map } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -24,13 +23,56 @@ import { } from "@bitwarden/key-management"; import { BrowserSyncVerificationDialogComponent } from "../app/components/browser-sync-verification-dialog.component"; -import { LegacyMessage } from "../models/native-messaging/legacy-message"; -import { LegacyMessageWrapper } from "../models/native-messaging/legacy-message-wrapper"; +import { LegacyMessage, LegacyMessageWrapper } from "../models/native-messaging"; import { DesktopSettingsService } from "../platform/services/desktop-settings.service"; const MessageValidTimeout = 10 * 1000; const HashAlgorithmForAsymmetricEncryption = "sha1"; +type ConnectedApp = { + publicKey: string; + sessionSecret: string | null; + trusted: boolean; +}; + +const ConnectedAppPrefix = "connectedApp_"; + +class ConnectedApps { + async get(appId: string): Promise { + if (!(await this.has(appId))) { + return null; + } + + return JSON.parse( + await ipc.platform.ephemeralStore.getEphemeralValue(`${ConnectedAppPrefix}${appId}`), + ); + } + + async list(): Promise { + return (await ipc.platform.ephemeralStore.listEphemeralValueKeys()) + .filter((key) => key.startsWith(ConnectedAppPrefix)) + .map((key) => key.replace(ConnectedAppPrefix, "")); + } + + async set(appId: string, value: ConnectedApp) { + await ipc.platform.ephemeralStore.setEphemeralValue( + `${ConnectedAppPrefix}${appId}`, + JSON.stringify(value), + ); + } + + async has(appId: string) { + return (await this.list()).find((id) => id === appId) != null; + } + + async clear() { + const connected = await this.list(); + for (const appId of connected) { + await ipc.platform.ephemeralStore.removeEphemeralValue(`${ConnectedAppPrefix}${appId}`); + } + } +} + @Injectable() export class BiometricMessageHandlerService { constructor( @@ -46,13 +88,33 @@ export class BiometricMessageHandlerService { private accountService: AccountService, private authService: AuthService, private ngZone: NgZone, - ) {} + private i18nService: I18nService, + ) { + combineLatest([ + this.desktopSettingService.browserIntegrationFingerprintEnabled$, + this.desktopSettingService.browserIntegrationEnabled$, + ]) + .pipe( + concatMap(async () => { + await this.connectedApps.clear(); + }), + ) + .subscribe(); + } + + private connectedApps: ConnectedApps = new ConnectedApps(); async handleMessage(msg: LegacyMessageWrapper) { const { appId, message: rawMessage } = msg as LegacyMessageWrapper; // Request to setup secure encryption if ("command" in rawMessage && rawMessage.command === "setupEncryption") { + if (rawMessage.publicKey == null || rawMessage.userId == null) { + this.logService.warning( + "[Native Messaging IPC] Received invalid setupEncryption message. Ignoring.", + ); + return; + } const remotePublicKey = Utils.fromB64ToArray(rawMessage.publicKey); // Validate the UserId to ensure we are logged into the same account. @@ -69,39 +131,26 @@ export class BiometricMessageHandlerService { return; } - if (await firstValueFrom(this.desktopSettingService.browserIntegrationFingerprintEnabled$)) { - this.logService.info("[Native Messaging IPC] Requesting fingerprint verification."); - ipc.platform.nativeMessaging.sendMessage({ - command: "verifyFingerprint", - appId: appId, - }); - - const fingerprint = await this.keyService.getFingerprint( - rawMessage.userId, - remotePublicKey, + if (await this.connectedApps.has(appId)) { + this.logService.info( + "[Native Messaging IPC] Public key for app id changed. Invalidating trust", ); - - this.messagingService.send("setFocus"); - - const dialogRef = this.ngZone.run(() => - BrowserSyncVerificationDialogComponent.open(this.dialogService, { fingerprint }), - ); - - const browserSyncVerified = await firstValueFrom(dialogRef.closed); - - if (browserSyncVerified !== true) { - this.logService.info("[Native Messaging IPC] Fingerprint verification failed."); - return; - } } - await this.secureCommunication(remotePublicKey, appId); + const connectedApp = { + publicKey: Utils.fromBufferToB64(remotePublicKey), + sessionSecret: null, + trusted: false, + } as ConnectedApp; + await this.connectedApps.set(appId, connectedApp); + await this.secureCommunication(connectedApp, remotePublicKey, appId); return; } - if ((await ipc.platform.ephemeralStore.getEphemeralValue(appId)) == null) { + const sessionSecret = (await this.connectedApps.get(appId))?.sessionSecret; + if (sessionSecret == null) { this.logService.info( - "[Native Messaging IPC] Epheremal secret for secure channel is missing. Invalidating encryption...", + "[Native Messaging IPC] Session secret for secure channel is missing. Invalidating encryption...", ); ipc.platform.nativeMessaging.sendMessage({ command: "invalidateEncryption", @@ -113,7 +162,7 @@ export class BiometricMessageHandlerService { const message: LegacyMessage = JSON.parse( await this.encryptService.decryptToUtf8( rawMessage as EncString, - SymmetricCryptoKey.fromString(await ipc.platform.ephemeralStore.getEphemeralValue(appId)), + SymmetricCryptoKey.fromString(sessionSecret), ), ); @@ -129,7 +178,10 @@ export class BiometricMessageHandlerService { return; } - if (Math.abs(message.timestamp - Date.now()) > MessageValidTimeout) { + if ( + message.timestamp == null || + Math.abs(message.timestamp - Date.now()) > MessageValidTimeout + ) { this.logService.info("[Native Messaging IPC] Received a too old message. Ignoring."); return; } @@ -188,7 +240,7 @@ export class BiometricMessageHandlerService { appId, ); } - // TODO: legacy, remove after 2025.01 + // TODO: legacy, remove after 2025.3 case BiometricsCommands.IsAvailable: { const available = (await this.biometricsService.getBiometricsStatus()) == BiometricsStatus.Available; @@ -200,8 +252,20 @@ export class BiometricMessageHandlerService { appId, ); } - // TODO: legacy, remove after 2025.01 + // TODO: legacy, remove after 2025.3 case BiometricsCommands.Unlock: { + if ( + await firstValueFrom(this.desktopSettingService.browserIntegrationFingerprintEnabled$) + ) { + await this.send({ command: "biometricUnlock", response: "not available" }, appId); + await this.dialogService.openSimpleDialog({ + title: this.i18nService.t("updateBrowserOrDisableFingerprintDialogTitle"), + content: this.i18nService.t("updateBrowserOrDisableFingerprintDialogMessage"), + type: "warning", + }); + return; + } + const isTemporarilyDisabled = (await this.biometricStateService.getBiometricUnlockEnabled(message.userId as UserId)) && !((await this.biometricsService.getBiometricsStatus()) == BiometricsStatus.Available); @@ -221,11 +285,11 @@ export class BiometricMessageHandlerService { return this.send({ command: "biometricUnlock", response: "not unlocked" }, appId); } - const biometricUnlockPromise = + const biometricUnlock = message.userId == null - ? firstValueFrom(this.biometricStateService.biometricUnlockEnabled$) - : this.biometricStateService.getBiometricUnlockEnabled(message.userId as UserId); - if (!(await biometricUnlockPromise)) { + ? await firstValueFrom(this.biometricStateService.biometricUnlockEnabled$) + : await this.biometricStateService.getBiometricUnlockEnabled(message.userId as UserId); + if (!biometricUnlock) { await this.send({ command: "biometricUnlock", response: "not enabled" }, appId); return this.ngZone.run(() => @@ -254,17 +318,19 @@ export class BiometricMessageHandlerService { const currentlyActiveAccountId = ( await firstValueFrom(this.accountService.activeAccount$) - ).id; + )?.id; const isCurrentlyActiveAccountUnlocked = (await this.authService.getAuthStatus(userId)) == AuthenticationStatus.Unlocked; // prevent proc reloading an active account, when it is the same as the browser if (currentlyActiveAccountId != message.userId || !isCurrentlyActiveAccountUnlocked) { - await ipc.platform.reloadProcess(); + ipc.platform.reloadProcess(); } } else { await this.send({ command: "biometricUnlock", response: "canceled" }, appId); } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { await this.send({ command: "biometricUnlock", response: "canceled" }, appId); } @@ -279,9 +345,14 @@ export class BiometricMessageHandlerService { private async send(message: any, appId: string) { message.timestamp = Date.now(); + const sessionSecret = (await this.connectedApps.get(appId))?.sessionSecret; + if (sessionSecret == null) { + throw new Error("Session secret is missing"); + } + const encrypted = await this.encryptService.encrypt( JSON.stringify(message), - SymmetricCryptoKey.fromString(await ipc.platform.ephemeralStore.getEphemeralValue(appId)), + SymmetricCryptoKey.fromString(sessionSecret), ); ipc.platform.nativeMessaging.sendMessage({ @@ -291,12 +362,15 @@ export class BiometricMessageHandlerService { }); } - private async secureCommunication(remotePublicKey: Uint8Array, appId: string) { + private async secureCommunication( + connectedApp: ConnectedApp, + remotePublicKey: Uint8Array, + appId: string, + ) { const secret = await this.cryptoFunctionService.randomBytes(64); - await ipc.platform.ephemeralStore.setEphemeralValue( - appId, - new SymmetricCryptoKey(secret).keyB64, - ); + + connectedApp.sessionSecret = new SymmetricCryptoKey(secret).keyB64; + await this.connectedApps.set(appId, connectedApp); this.logService.info("[Native Messaging IPC] Setting up secure channel"); const encryptedSecret = await this.cryptoFunctionService.rsaEncrypt( @@ -318,6 +392,18 @@ export class BiometricMessageHandlerService { appId: string, ) { const messageUserId = message.userId as UserId; + if (!(await this.validateFingerprint(appId))) { + await this.send( + { + command: BiometricsCommands.UnlockWithBiometricsForUser, + messageId, + response: false, + }, + appId, + ); + return; + } + try { const userKey = await this.biometricsService.unlockWithBiometricsForUser(messageUserId); if (userKey != null) { @@ -342,6 +428,8 @@ export class BiometricMessageHandlerService { appId, ); } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { await this.send( { command: BiometricsCommands.UnlockWithBiometricsForUser, messageId, response: false }, @@ -350,11 +438,15 @@ export class BiometricMessageHandlerService { } } - /** A process reload after a biometric unlock should happen if the userkey that was used for biometric unlock is for a different user than the + /** + * A process reload after a biometric unlock should happen if the userkey that was used for biometric unlock is for a different user than the * currently active account. The userkey for the active account was in memory anyways. Further, if the desktop app is locked, a reload should occur (since the userkey was not already in memory). */ async processReloadWhenRequired(messageUserId: UserId) { - const currentlyActiveAccountId = (await firstValueFrom(this.accountService.activeAccount$)).id; + const currentlyActiveAccountId = (await firstValueFrom(this.accountService.activeAccount$))?.id; + if (currentlyActiveAccountId == null) { + return; + } const isCurrentlyActiveAccountUnlocked = (await firstValueFrom(this.authService.authStatusFor$(currentlyActiveAccountId))) == AuthenticationStatus.Unlocked; @@ -365,4 +457,54 @@ export class BiometricMessageHandlerService { } } } + + async validateFingerprint(appId: string): Promise { + if (await firstValueFrom(this.desktopSettingService.browserIntegrationFingerprintEnabled$)) { + const appToValidate = await this.connectedApps.get(appId); + if (appToValidate == null) { + return false; + } + + if (appToValidate.trusted) { + return true; + } + + ipc.platform.nativeMessaging.sendMessage({ + command: "verifyDesktopIPCFingerprint", + appId: appId, + }); + + const fingerprint = await this.keyService.getFingerprint( + appId, + Utils.fromB64ToArray(appToValidate.publicKey), + ); + + this.messagingService.send("setFocus"); + + const dialogRef = this.ngZone.run(() => + BrowserSyncVerificationDialogComponent.open(this.dialogService, { fingerprint }), + ); + + const browserSyncVerified = await firstValueFrom(dialogRef.closed); + if (browserSyncVerified !== true) { + this.logService.info("[Native Messaging IPC] Fingerprint verification failed."); + ipc.platform.nativeMessaging.sendMessage({ + command: "rejectedDesktopIPCFingerprint", + appId: appId, + }); + return false; + } else { + this.logService.info("[Native Messaging IPC] Fingerprint verified."); + ipc.platform.nativeMessaging.sendMessage({ + command: "verifiedDesktopIPCFingerprint", + appId: appId, + }); + } + + appToValidate.trusted = true; + await this.connectedApps.set(appId, appToValidate); + } + + return true; + } } diff --git a/apps/desktop/src/services/duckduckgo-message-handler.service.ts b/apps/desktop/src/services/duckduckgo-message-handler.service.ts index 9a914f238b5..e4474b60741 100644 --- a/apps/desktop/src/services/duckduckgo-message-handler.service.ts +++ b/apps/desktop/src/services/duckduckgo-message-handler.service.ts @@ -4,8 +4,8 @@ import { Injectable } from "@angular/core"; import { firstValueFrom } from "rxjs"; import { NativeMessagingVersion } from "@bitwarden/common/enums"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -130,6 +130,8 @@ export class DuckDuckGoMessageHandlerService { sharedKey: Utils.fromBufferToB64(encryptedSecret), }, }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { this.sendResponse({ messageId: messageId, @@ -153,6 +155,8 @@ export class DuckDuckGoMessageHandlerService { await this.encryptedMessageHandlerService.responseDataForCommand(decryptedCommandData); await this.sendEncryptedResponse(message, { command, payload: responseData }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises diff --git a/apps/desktop/src/services/encrypted-message-handler.service.ts b/apps/desktop/src/services/encrypted-message-handler.service.ts index 09c0fe2a07c..a8a1e738644 100644 --- a/apps/desktop/src/services/encrypted-message-handler.service.ts +++ b/apps/desktop/src/services/encrypted-message-handler.service.ts @@ -1,12 +1,13 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; 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 { 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 { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -65,9 +66,7 @@ export class EncryptedMessageHandlerService { } private async checkUserStatus(userId: string): Promise { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); if (userId !== activeUserId) { return "not-active-user"; @@ -83,9 +82,7 @@ export class EncryptedMessageHandlerService { private async statusCommandHandler(): Promise { const accounts = await firstValueFrom(this.accountService.accounts$); - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); if (!accounts || !Object.keys(accounts)) { return []; @@ -114,16 +111,14 @@ export class EncryptedMessageHandlerService { } const ciphersResponse: CipherResponse[] = []; - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const authStatus = await this.authService.getAuthStatus(activeUserId); if (authStatus !== AuthenticationStatus.Unlocked) { return { error: "locked" }; } - const ciphers = await this.cipherService.getAllDecryptedForUrl(payload.uri); + const ciphers = await this.cipherService.getAllDecryptedForUrl(payload.uri, activeUserId); ciphers.sort((a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b)); ciphers.forEach((c) => { @@ -166,9 +161,7 @@ export class EncryptedMessageHandlerService { cipherView.login.uris[0].uri = credentialCreatePayload.uri; try { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const encrypted = await this.cipherService.encrypt(cipherView, activeUserId); await this.cipherService.createWithServer(encrypted); @@ -178,6 +171,8 @@ export class EncryptedMessageHandlerService { await this.messagingService.send("refreshCiphers"); return { status: "success" }; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { return { status: "failure" }; } @@ -198,13 +193,16 @@ export class EncryptedMessageHandlerService { } try { - const cipher = await this.cipherService.get(credentialUpdatePayload.credentialId); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + + const cipher = await this.cipherService.get( + credentialUpdatePayload.credentialId, + activeUserId, + ); if (cipher === null) { return { status: "failure" }; } - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const cipherView = await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); @@ -222,6 +220,8 @@ export class EncryptedMessageHandlerService { await this.messagingService.send("refreshCiphers"); return { status: "success" }; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { return { status: "failure" }; } diff --git a/apps/desktop/src/types/biometric-message.ts b/apps/desktop/src/types/biometric-message.ts index 7946280e9a6..f7a7ef0c507 100644 --- a/apps/desktop/src/types/biometric-message.ts +++ b/apps/desktop/src/types/biometric-message.ts @@ -15,9 +15,22 @@ export enum BiometricAction { SetShouldAutoprompt = "setShouldAutoprompt", } -export type BiometricMessage = { - action: BiometricAction; - key?: string; - userId?: string; - data?: any; -}; +export type BiometricMessage = + | { + action: BiometricAction.SetClientKeyHalf; + userId: string; + key: string | null; + } + | { + action: BiometricAction.SetKeyForUser; + userId: string; + key: string; + } + | { + action: Exclude< + BiometricAction, + BiometricAction.SetClientKeyHalf | BiometricAction.SetKeyForUser + >; + userId?: string; + data?: any; + }; diff --git a/apps/desktop/src/vault/app/vault/add-edit.component.html b/apps/desktop/src/vault/app/vault/add-edit.component.html index 6244f585bae..d79e15ebe6a 100644 --- a/apps/desktop/src/vault/app/vault/add-edit.component.html +++ b/apps/desktop/src/vault/app/vault/add-edit.component.html @@ -512,6 +512,15 @@ [ngClass]="{ 'bwi-eye': !showPrivateKey, 'bwi-eye-slash': showPrivateKey }" > +
@@ -559,16 +568,6 @@
-
- -
diff --git a/apps/desktop/src/vault/app/vault/add-edit.component.ts b/apps/desktop/src/vault/app/vault/add-edit.component.ts index 02fa8076086..2c8b5a8321a 100644 --- a/apps/desktop/src/vault/app/vault/add-edit.component.ts +++ b/apps/desktop/src/vault/app/vault/add-edit.component.ts @@ -3,8 +3,6 @@ import { DatePipe } from "@angular/common"; import { Component, NgZone, OnChanges, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { NgForm } from "@angular/forms"; -import { sshagent as sshAgent } from "desktop_native/napi"; -import { lastValueFrom } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/vault/components/add-edit.component"; @@ -22,10 +20,10 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { DialogService, ToastService } from "@bitwarden/components"; -import { SshKeyPasswordPromptComponent } from "@bitwarden/importer/ui"; -import { PasswordRepromptService } from "@bitwarden/vault"; +import { PasswordRepromptService, SshImportPromptService } from "@bitwarden/vault"; const BroadcasterSubscriptionId = "AddEditComponent"; @@ -59,6 +57,7 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On toastService: ToastService, cipherAuthorizationService: CipherAuthorizationService, sdkService: SdkService, + sshImportPromptService: SshImportPromptService, ) { super( cipherService, @@ -81,6 +80,7 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On cipherAuthorizationService, toastService, sdkService, + sshImportPromptService, ); } @@ -148,67 +148,14 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On ); } - async importSshKeyFromClipboard(password: string = "") { - const key = await this.platformUtilsService.readFromClipboard(); - const parsedKey = await ipc.platform.sshAgent.importKey(key, password); - if (parsedKey == null) { - this.toastService.showToast({ - variant: "error", - title: "", - message: this.i18nService.t("invalidSshKey"), - }); - return; - } - - switch (parsedKey.status) { - case sshAgent.SshKeyImportStatus.ParsingError: - this.toastService.showToast({ - variant: "error", - title: "", - message: this.i18nService.t("invalidSshKey"), - }); - return; - case sshAgent.SshKeyImportStatus.UnsupportedKeyType: - this.toastService.showToast({ - variant: "error", - title: "", - message: this.i18nService.t("sshKeyTypeUnsupported"), - }); - return; - case sshAgent.SshKeyImportStatus.PasswordRequired: - case sshAgent.SshKeyImportStatus.WrongPassword: - if (password !== "") { - this.toastService.showToast({ - variant: "error", - title: "", - message: this.i18nService.t("sshKeyWrongPassword"), - }); - } else { - password = await this.getSshKeyPassword(); - if (password === "") { - return; - } - await this.importSshKeyFromClipboard(password); - } - return; - default: - this.cipher.sshKey.privateKey = parsedKey.sshKey.privateKey; - this.cipher.sshKey.publicKey = parsedKey.sshKey.publicKey; - this.cipher.sshKey.keyFingerprint = parsedKey.sshKey.keyFingerprint; - this.toastService.showToast({ - variant: "success", - title: "", - message: this.i18nService.t("sshKeyPasted"), - }); - } - } - - async getSshKeyPassword(): Promise { - const dialog = this.dialogService.open(SshKeyPasswordPromptComponent, { - ariaModal: true, - }); - - return await lastValueFrom(dialog.closed); + /** + * Updates the cipher when an attachment is altered. + * Note: This only updates the `attachments` and `revisionDate` + * properties to ensure any in-progress edits are not lost. + */ + patchCipherAttachments(cipher: CipherView) { + this.cipher.attachments = cipher.attachments; + this.cipher.revisionDate = cipher.revisionDate; } truncateString(value: string, length: number) { diff --git a/apps/desktop/src/vault/app/vault/attachments.component.ts b/apps/desktop/src/vault/app/vault/attachments.component.ts index 2b554ba2291..ea4f49b8431 100644 --- a/apps/desktop/src/vault/app/vault/attachments.component.ts +++ b/apps/desktop/src/vault/app/vault/attachments.component.ts @@ -4,7 +4,7 @@ import { AttachmentsComponent as BaseAttachmentsComponent } from "@bitwarden/ang import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; diff --git a/apps/desktop/src/vault/app/vault/collections.component.html b/apps/desktop/src/vault/app/vault/collections.component.html index 113ebe5ff97..13bd3178899 100644 --- a/apps/desktop/src/vault/app/vault/collections.component.html +++ b/apps/desktop/src/vault/app/vault/collections.component.html @@ -21,6 +21,7 @@ type="checkbox" [(ngModel)]="$any(c).checked" name="Collection[{{ i }}].Checked" + [disabled]="!cipher.canAssignToCollections" /> diff --git a/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.html b/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.html index a294ce1f63f..47232dff66d 100644 --- a/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.html +++ b/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.html @@ -4,6 +4,7 @@ - - + diff --git a/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.ts b/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.ts index abf74371912..3ed739cd37d 100644 --- a/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.ts +++ b/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.ts @@ -3,6 +3,7 @@ import { CommonModule } from "@angular/common"; import { Component, Inject } from "@angular/core"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ButtonModule, DialogModule, @@ -14,6 +15,7 @@ import { CredentialGeneratorHistoryDialogComponent, GeneratorModule, } from "@bitwarden/generator-components"; +import { AlgorithmInfo } from "@bitwarden/generator-core"; import { CipherFormGeneratorComponent } from "@bitwarden/vault"; type CredentialGeneratorParams = { @@ -38,12 +40,24 @@ type CredentialGeneratorParams = { }) export class CredentialGeneratorDialogComponent { credentialValue?: string; + buttonLabel?: string; constructor( @Inject(DIALOG_DATA) protected data: CredentialGeneratorParams, private dialogService: DialogService, + private i18nService: I18nService, ) {} + onAlgorithmSelected = (selected?: AlgorithmInfo) => { + if (selected) { + this.buttonLabel = selected.useGeneratedValue; + } else { + // default to email + this.buttonLabel = this.i18nService.t("useThisEmail"); + } + this.credentialValue = undefined; + }; + applyCredentials = () => { this.data.onCredentialGenerated(this.credentialValue); }; diff --git a/apps/desktop/src/vault/app/vault/folder-add-edit.component.ts b/apps/desktop/src/vault/app/vault/folder-add-edit.component.ts index 1cab5a940dd..cdb879693c0 100644 --- a/apps/desktop/src/vault/app/vault/folder-add-edit.component.ts +++ b/apps/desktop/src/vault/app/vault/folder-add-edit.component.ts @@ -8,7 +8,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; @Component({ @@ -26,6 +26,7 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent { logService: LogService, dialogService: DialogService, formBuilder: FormBuilder, + toastService: ToastService, ) { super( folderService, @@ -37,6 +38,7 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent { logService, dialogService, formBuilder, + toastService, ); } } diff --git a/apps/desktop/src/vault/app/vault/password-history.component.ts b/apps/desktop/src/vault/app/vault/password-history.component.ts index 12701ac5527..4a87617d8f4 100644 --- a/apps/desktop/src/vault/app/vault/password-history.component.ts +++ b/apps/desktop/src/vault/app/vault/password-history.component.ts @@ -5,6 +5,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-password-history", @@ -16,7 +17,8 @@ export class PasswordHistoryComponent extends BasePasswordHistoryComponent { platformUtilsService: PlatformUtilsService, i18nService: I18nService, accountService: AccountService, + toastService: ToastService, ) { - super(cipherService, platformUtilsService, i18nService, accountService, window); + super(cipherService, platformUtilsService, i18nService, accountService, window, toastService); } } diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/organization-filter.component.ts b/apps/desktop/src/vault/app/vault/vault-filter/filters/organization-filter.component.ts index 92c75e30417..39f1c0200ea 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/filters/organization-filter.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/organization-filter.component.ts @@ -7,6 +7,7 @@ import { DisplayMode } from "@bitwarden/angular/vault/vault-filter/models/displa import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-organization-filter", @@ -25,6 +26,7 @@ export class OrganizationFilterComponent extends BaseOrganizationFilterComponent constructor( private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, + private toastService: ToastService, ) { super(); } @@ -36,11 +38,11 @@ export class OrganizationFilterComponent extends BaseOrganizationFilterComponent // eslint-disable-next-line @typescript-eslint/no-floating-promises super.applyOrganizationFilter(organization); } else { - this.platformUtilsService.showToast( - "error", - null, - this.i18nService.t("disabledOrganizationFilterError"), - ); + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("disabledOrganizationFilterError"), + }); } } } diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.html b/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.html index 55e10980ad1..c3dcd191dfc 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.html +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.html @@ -82,7 +82,6 @@
  • + {{ totpInfo.totpCodeFormatted }} + + +
    + +
    - + +
    {{ "verificationCodeTotp" | i18n }} diff --git a/apps/desktop/src/vault/app/vault/view.component.ts b/apps/desktop/src/vault/app/vault/view.component.ts index 23b85eceda2..aee1f34437b 100644 --- a/apps/desktop/src/vault/app/vault/view.component.ts +++ b/apps/desktop/src/vault/app/vault/view.component.ts @@ -17,8 +17,8 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -31,7 +31,7 @@ import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folde import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; import { DecryptionFailureDialogComponent, PasswordRepromptService } from "@bitwarden/vault"; @@ -68,6 +68,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro datePipe: DatePipe, billingAccountProfileStateService: BillingAccountProfileStateService, accountService: AccountService, + toastService: ToastService, cipherAuthorizationService: CipherAuthorizationService, ) { super( @@ -94,6 +95,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro datePipe, accountService, billingAccountProfileStateService, + toastService, cipherAuthorizationService, ); } diff --git a/apps/desktop/tailwind.config.js b/apps/desktop/tailwind.config.js index bf3b67c74ad..5b4cab027ba 100644 --- a/apps/desktop/tailwind.config.js +++ b/apps/desktop/tailwind.config.js @@ -1,11 +1,11 @@ -/* eslint-disable no-undef, @typescript-eslint/no-var-requires */ +/* eslint-disable @typescript-eslint/no-require-imports */ const config = require("../../libs/components/tailwind.config.base"); config.content = [ "./src/**/*.{html,ts}", "../../libs/components/src/**/*.{html,ts}", "../../libs/auth/src/**/*.{html,ts}", - "../../libs/key-management/src/**/*.{html,ts}", + "../../libs/key-management-ui/src/**/*.{html,ts}", "../../libs/angular/src/**/*.{html,ts}", "../../libs/vault/src/**/*.{html,ts,mdx}", ]; diff --git a/apps/desktop/test.setup.ts b/apps/desktop/test.setup.ts index 224262ec83e..5c248668a6d 100644 --- a/apps/desktop/test.setup.ts +++ b/apps/desktop/test.setup.ts @@ -1,4 +1,4 @@ -import "jest-preset-angular/setup-jest"; +import "@bitwarden/ui-common/setup-jest"; Object.defineProperty(window, "CSS", { value: null }); Object.defineProperty(window, "getComputedStyle", { diff --git a/apps/desktop/tsconfig.json b/apps/desktop/tsconfig.json index da61ef22dd4..78b3512405e 100644 --- a/apps/desktop/tsconfig.json +++ b/apps/desktop/tsconfig.json @@ -22,18 +22,20 @@ "@bitwarden/generator-history": ["../../libs/tools/generator/extensions/history/src"], "@bitwarden/generator-legacy": ["../../libs/tools/generator/extensions/legacy/src"], "@bitwarden/generator-navigation": ["../../libs/tools/generator/extensions/navigation/src"], - "@bitwarden/vault-export-core": [ - "../../libs/tools/export/vault-export/vault-export-core/src" - ], - "@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-ui/src"], - "@bitwarden/importer/core": ["../../libs/importer/src"], - "@bitwarden/importer/ui": ["../../libs/importer/src/components"], + "@bitwarden/importer-core": ["../../libs/importer/src"], + "@bitwarden/importer-ui": ["../../libs/importer/src/components"], "@bitwarden/key-management": ["../../libs/key-management/src"], - "@bitwarden/key-management/angular": ["../../libs/key-management/src/angular"], + "@bitwarden/key-management-ui": ["../../libs/key-management-ui/src"], "@bitwarden/node/*": ["../../libs/node/src/*"], "@bitwarden/platform": ["../../libs/platform/src"], "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], "@bitwarden/tools-card": ["../../libs/tools/card/src"], + "@bitwarden/ui-common": ["../../libs/ui/common/src"], + "@bitwarden/ui-common/setup-jest": ["../../libs/ui/common/src/setup-jest"], + "@bitwarden/vault-export-core": [ + "../../libs/tools/export/vault-export/vault-export-core/src" + ], + "@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-ui/src"], "@bitwarden/vault": ["../../libs/vault/src"] }, "plugins": [ @@ -44,8 +46,7 @@ "useDefineForClassFields": false }, "angularCompilerOptions": { - "strictTemplates": true, - "preserveWhitespaces": true + "strictTemplates": true }, - "include": ["src", "../../libs/common/src/platform/services/**/*.worker.ts"] + "include": ["src", "../../libs/common/src/key-management/crypto/services/encrypt.worker.ts"] } diff --git a/apps/desktop/webpack.renderer.js b/apps/desktop/webpack.renderer.js index ac990689ae0..e9a44a9e725 100644 --- a/apps/desktop/webpack.renderer.js +++ b/apps/desktop/webpack.renderer.js @@ -121,7 +121,13 @@ const renderer = { loader: MiniCssExtractPlugin.loader, }, "css-loader", - "postcss-loader", + "resolve-url-loader", + { + loader: "postcss-loader", + options: { + sourceMap: true, + }, + }, ], }, { @@ -134,7 +140,13 @@ const renderer = { }, }, "css-loader", - "sass-loader", + "resolve-url-loader", + { + loader: "sass-loader", + options: { + sourceMap: true, + }, + }, ], }, // Hide System.import warnings. ref: https://github.com/angular/angular/issues/21560 diff --git a/apps/web/.eslintrc.json b/apps/web/.eslintrc.json deleted file mode 100644 index 6f606eb0c37..00000000000 --- a/apps/web/.eslintrc.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "env": { - "browser": true - }, - "rules": { - "no-restricted-imports": [ - "error", - { - "patterns": [ - "**/app/core/*", - "**/reports/*", - "**/app/shared/*", - "**/organizations/settings/*", - "**/organizations/policies/*", - "@bitwarden/web-vault/*", - "src/**/*", - "bitwarden_license" - ] - } - ] - } -} diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile index 41be86e584e..09dac7f4c48 100644 --- a/apps/web/Dockerfile +++ b/apps/web/Dockerfile @@ -1,4 +1,4 @@ -FROM bitwarden/server +FROM ghcr.io/bitwarden/server LABEL com.bitwarden.product="bitwarden" diff --git a/apps/web/package.json b/apps/web/package.json index 42432de29a4..2af524c92b7 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,11 +1,11 @@ { "name": "@bitwarden/web-vault", - "version": "2025.1.0", + "version": "2025.3.1", "scripts": { "build:oss": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", "build:bit": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" webpack -c ../../bitwarden_license/bit-web/webpack.config.js", - "build:oss:watch": "webpack serve", - "build:bit:watch": "webpack serve -c ../../bitwarden_license/bit-web/webpack.config.js", + "build:oss:watch": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" webpack serve", + "build:bit:watch": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" webpack serve -c ../../bitwarden_license/bit-web/webpack.config.js", "build:bit:dev": "cross-env ENV=development npm run build:bit", "build:bit:dev:analyze": "cross-env LOGGING=false webpack -c ../../bitwarden_license/bit-web/webpack.config.js --profile --json > stats.json && npx webpack-bundle-analyzer stats.json build/", "build:bit:dev:watch": "cross-env ENV=development NODE_OPTIONS=\"--max-old-space-size=8192\" npm run build:bit:watch", diff --git a/apps/web/postcss.config.js b/apps/web/postcss.config.js index c4513687e89..5657df3afcf 100644 --- a/apps/web/postcss.config.js +++ b/apps/web/postcss.config.js @@ -1,4 +1,9 @@ -/* eslint-disable no-undef */ +/* eslint-disable @typescript-eslint/no-require-imports */ module.exports = { - plugins: [require("tailwindcss"), require("autoprefixer"), require("postcss-nested")], + plugins: [ + require("postcss-import"), + require("postcss-nested"), + require("tailwindcss"), + require("autoprefixer"), + ], }; diff --git a/apps/web/src/app/admin-console/.eslintrc.json b/apps/web/src/app/admin-console/.eslintrc.json deleted file mode 100644 index d55df3899e7..00000000000 --- a/apps/web/src/app/admin-console/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../../../../libs/admin-console/.eslintrc.json" -} diff --git a/apps/web/src/app/admin-console/common/base.events.component.ts b/apps/web/src/app/admin-console/common/base.events.component.ts index 1080880a466..9d06be92eb8 100644 --- a/apps/web/src/app/admin-console/common/base.events.component.ts +++ b/apps/web/src/app/admin-console/common/base.events.component.ts @@ -167,6 +167,8 @@ export abstract class BaseEventsComponent { let dates: string[] = null; try { dates = this.eventService.formatDateFilters(this.start, this.end); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.toastService.showToast({ variant: "error", diff --git a/apps/web/src/app/admin-console/common/base.people.component.ts b/apps/web/src/app/admin-console/common/base.people.component.ts deleted file mode 100644 index 10632582e99..00000000000 --- a/apps/web/src/app/admin-console/common/base.people.component.ts +++ /dev/null @@ -1,427 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Directive, ViewChild, ViewContainerRef } from "@angular/core"; -import { FormControl } from "@angular/forms"; -import { firstValueFrom, concatMap, map, lastValueFrom, startWith, debounceTime } from "rxjs"; - -import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe"; -import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe"; -import { ModalService } from "@bitwarden/angular/services/modal.service"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { SearchService } from "@bitwarden/common/abstractions/search.service"; -import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service"; -import { - OrganizationUserStatusType, - OrganizationUserType, - ProviderUserStatusType, - ProviderUserType, -} from "@bitwarden/common/admin-console/enums"; -import { ProviderUserUserDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user.response"; -import { ListResponse } from "@bitwarden/common/models/response/list.response"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { KeyService } from "@bitwarden/key-management"; - -import { OrganizationUserView } from "../organizations/core/views/organization-user.view"; -import { UserConfirmComponent } from "../organizations/manage/user-confirm.component"; - -type StatusType = OrganizationUserStatusType | ProviderUserStatusType; - -const MaxCheckedCount = 500; - -@Directive() -export abstract class BasePeopleComponent< - UserType extends ProviderUserUserDetailsResponse | OrganizationUserView, -> { - @ViewChild("confirmTemplate", { read: ViewContainerRef, static: true }) - confirmModalRef: ViewContainerRef; - - get allCount() { - return this.activeUsers != null ? this.activeUsers.length : 0; - } - - get invitedCount() { - return this.statusMap.has(this.userStatusType.Invited) - ? this.statusMap.get(this.userStatusType.Invited).length - : 0; - } - - get acceptedCount() { - return this.statusMap.has(this.userStatusType.Accepted) - ? this.statusMap.get(this.userStatusType.Accepted).length - : 0; - } - - get confirmedCount() { - return this.statusMap.has(this.userStatusType.Confirmed) - ? this.statusMap.get(this.userStatusType.Confirmed).length - : 0; - } - - get revokedCount() { - return this.statusMap.has(this.userStatusType.Revoked) - ? this.statusMap.get(this.userStatusType.Revoked).length - : 0; - } - - get showConfirmUsers(): boolean { - return ( - this.activeUsers != null && - this.statusMap != null && - this.activeUsers.length > 1 && - this.confirmedCount > 0 && - this.confirmedCount < 3 && - this.acceptedCount > 0 - ); - } - - get showBulkConfirmUsers(): boolean { - return this.acceptedCount > 0; - } - - abstract userType: typeof OrganizationUserType | typeof ProviderUserType; - abstract userStatusType: typeof OrganizationUserStatusType | typeof ProviderUserStatusType; - - loading = true; - statusMap = new Map(); - status: StatusType; - users: UserType[] = []; - pagedUsers: UserType[] = []; - actionPromise: Promise; - - protected allUsers: UserType[] = []; - protected activeUsers: UserType[] = []; - - protected didScroll = false; - protected pageSize = 100; - - protected searchControl = new FormControl("", { nonNullable: true }); - protected isSearching$ = this.searchControl.valueChanges.pipe( - debounceTime(500), - concatMap((searchText) => this.searchService.isSearchable(searchText)), - startWith(false), - ); - protected isPaging$ = this.isSearching$.pipe( - map((isSearching) => { - if (isSearching && this.didScroll) { - this.resetPaging(); - } - return !isSearching && this.users && this.users.length > this.pageSize; - }), - ); - - private pagedUsersCount = 0; - - constructor( - protected apiService: ApiService, - private searchService: SearchService, - protected i18nService: I18nService, - protected platformUtilsService: PlatformUtilsService, - protected keyService: KeyService, - protected validationService: ValidationService, - protected modalService: ModalService, - private logService: LogService, - private searchPipe: SearchPipe, - protected userNamePipe: UserNamePipe, - protected dialogService: DialogService, - protected organizationManagementPreferencesService: OrganizationManagementPreferencesService, - protected toastService: ToastService, - ) {} - - abstract edit(user: UserType): void; - abstract getUsers(): Promise | UserType[]>; - abstract deleteUser(id: string): Promise; - abstract revokeUser(id: string): Promise; - abstract restoreUser(id: string): Promise; - abstract reinviteUser(id: string): Promise; - abstract confirmUser(user: UserType, publicKey: Uint8Array): Promise; - - async load() { - const response = await this.getUsers(); - this.statusMap.clear(); - this.activeUsers = []; - for (const status of Utils.iterateEnum(this.userStatusType)) { - this.statusMap.set(status, []); - } - - if (response instanceof ListResponse) { - this.allUsers = response.data != null && response.data.length > 0 ? response.data : []; - } else if (Array.isArray(response)) { - this.allUsers = response; - } - - this.allUsers.sort( - Utils.getSortFunction( - this.i18nService, - "email", - ), - ); - this.allUsers.forEach((u) => { - if (!this.statusMap.has(u.status)) { - this.statusMap.set(u.status, [u]); - } else { - this.statusMap.get(u.status).push(u); - } - if (u.status !== this.userStatusType.Revoked) { - this.activeUsers.push(u); - } - }); - this.filter(this.status); - this.loading = false; - } - - filter(status: StatusType) { - this.status = status; - if (this.status != null) { - this.users = this.statusMap.get(this.status); - } else { - this.users = this.activeUsers; - } - // Reset checkbox selecton - this.selectAll(false); - this.resetPaging(); - } - - loadMore() { - if (!this.users || this.users.length <= this.pageSize) { - return; - } - const pagedLength = this.pagedUsers.length; - let pagedSize = this.pageSize; - if (pagedLength === 0 && this.pagedUsersCount > this.pageSize) { - pagedSize = this.pagedUsersCount; - } - if (this.users.length > pagedLength) { - this.pagedUsers = this.pagedUsers.concat( - this.users.slice(pagedLength, pagedLength + pagedSize), - ); - } - this.pagedUsersCount = this.pagedUsers.length; - this.didScroll = this.pagedUsers.length > this.pageSize; - } - - checkUser(user: UserType, select?: boolean) { - (user as any).checked = select == null ? !(user as any).checked : select; - } - - selectAll(select: boolean) { - if (select) { - this.selectAll(false); - } - - const filteredUsers = this.searchPipe.transform( - this.users, - this.searchControl.value, - "name", - "email", - "id", - ); - - const selectCount = - select && filteredUsers.length > MaxCheckedCount ? MaxCheckedCount : filteredUsers.length; - for (let i = 0; i < selectCount; i++) { - this.checkUser(filteredUsers[i], select); - } - } - - resetPaging() { - this.pagedUsers = []; - this.loadMore(); - } - - invite() { - this.edit(null); - } - - protected async removeUserConfirmationDialog(user: UserType) { - return this.dialogService.openSimpleDialog({ - title: this.userNamePipe.transform(user), - content: { key: "removeUserConfirmation" }, - type: "warning", - }); - } - - async remove(user: UserType) { - const confirmed = await this.removeUserConfirmationDialog(user); - if (!confirmed) { - return false; - } - - this.actionPromise = this.deleteUser(user.id); - try { - await this.actionPromise; - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("removedUserId", this.userNamePipe.transform(user)), - }); - this.removeUser(user); - } catch (e) { - this.validationService.showError(e); - } - this.actionPromise = null; - } - - protected async revokeUserConfirmationDialog(user: UserType) { - return this.dialogService.openSimpleDialog({ - title: { key: "revokeAccess", placeholders: [this.userNamePipe.transform(user)] }, - content: this.revokeWarningMessage(), - acceptButtonText: { key: "revokeAccess" }, - type: "warning", - }); - } - - async revoke(user: UserType) { - const confirmed = await this.revokeUserConfirmationDialog(user); - - if (!confirmed) { - return false; - } - - this.actionPromise = this.revokeUser(user.id); - try { - await this.actionPromise; - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("revokedUserId", this.userNamePipe.transform(user)), - }); - await this.load(); - } catch (e) { - this.validationService.showError(e); - } - this.actionPromise = null; - } - - async restore(user: UserType) { - this.actionPromise = this.restoreUser(user.id); - try { - await this.actionPromise; - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("restoredUserId", this.userNamePipe.transform(user)), - }); - await this.load(); - } catch (e) { - this.validationService.showError(e); - } - this.actionPromise = null; - } - - async reinvite(user: UserType) { - if (this.actionPromise != null) { - return; - } - - this.actionPromise = this.reinviteUser(user.id); - try { - await this.actionPromise; - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("hasBeenReinvited", this.userNamePipe.transform(user)), - }); - } catch (e) { - this.validationService.showError(e); - } - this.actionPromise = null; - } - - async confirm(user: UserType) { - function updateUser(self: BasePeopleComponent) { - user.status = self.userStatusType.Confirmed; - const mapIndex = self.statusMap.get(self.userStatusType.Accepted).indexOf(user); - if (mapIndex > -1) { - self.statusMap.get(self.userStatusType.Accepted).splice(mapIndex, 1); - self.statusMap.get(self.userStatusType.Confirmed).push(user); - } - } - - const confirmUser = async (publicKey: Uint8Array) => { - try { - this.actionPromise = this.confirmUser(user, publicKey); - await this.actionPromise; - updateUser(this); - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("hasBeenConfirmed", this.userNamePipe.transform(user)), - }); - } catch (e) { - this.validationService.showError(e); - throw e; - } finally { - this.actionPromise = null; - } - }; - - if (this.actionPromise != null) { - return; - } - - try { - const publicKeyResponse = await this.apiService.getUserPublicKey(user.userId); - const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey); - - const autoConfirm = await firstValueFrom( - this.organizationManagementPreferencesService.autoConfirmFingerPrints.state$, - ); - if (autoConfirm == null || !autoConfirm) { - const dialogRef = UserConfirmComponent.open(this.dialogService, { - data: { - name: this.userNamePipe.transform(user), - userId: user != null ? user.userId : null, - publicKey: publicKey, - confirmUser: () => confirmUser(publicKey), - }, - }); - await lastValueFrom(dialogRef.closed); - - return; - } - - try { - const fingerprint = await this.keyService.getFingerprint(user.userId, publicKey); - this.logService.info(`User's fingerprint: ${fingerprint.join("-")}`); - } catch (e) { - this.logService.error(e); - } - await confirmUser(publicKey); - } catch (e) { - this.logService.error(`Handled exception: ${e}`); - } - } - - protected revokeWarningMessage(): string { - return this.i18nService.t("revokeUserConfirmation"); - } - - protected getCheckedUsers() { - return this.users.filter((u) => (u as any).checked); - } - - protected removeUser(user: UserType) { - let index = this.users.indexOf(user); - if (index > -1) { - this.users.splice(index, 1); - this.resetPaging(); - } - - index = this.allUsers.indexOf(user); - if (index > -1) { - this.allUsers.splice(index, 1); - } - - if (this.statusMap.has(user.status)) { - index = this.statusMap.get(user.status).indexOf(user); - if (index > -1) { - this.statusMap.get(user.status).splice(index, 1); - } - } - } -} diff --git a/apps/web/src/app/vault/org-vault/bulk-collections-dialog/bulk-collections-dialog.component.html b/apps/web/src/app/admin-console/organizations/collections/bulk-collections-dialog/bulk-collections-dialog.component.html similarity index 100% rename from apps/web/src/app/vault/org-vault/bulk-collections-dialog/bulk-collections-dialog.component.html rename to apps/web/src/app/admin-console/organizations/collections/bulk-collections-dialog/bulk-collections-dialog.component.html diff --git a/apps/web/src/app/vault/org-vault/bulk-collections-dialog/bulk-collections-dialog.component.ts b/apps/web/src/app/admin-console/organizations/collections/bulk-collections-dialog/bulk-collections-dialog.component.ts similarity index 80% rename from apps/web/src/app/vault/org-vault/bulk-collections-dialog/bulk-collections-dialog.component.ts rename to apps/web/src/app/admin-console/organizations/collections/bulk-collections-dialog/bulk-collections-dialog.component.ts index 0fc7b6a31aa..dc08b32ce36 100644 --- a/apps/web/src/app/vault/org-vault/bulk-collections-dialog/bulk-collections-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/bulk-collections-dialog/bulk-collections-dialog.component.ts @@ -10,13 +10,18 @@ import { OrganizationUserApiService, CollectionView, } from "@bitwarden/admin-console/common"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; -import { GroupApiService, GroupView } from "../../../admin-console/organizations/core"; +import { SharedModule } from "../../../../shared"; +import { GroupApiService, GroupView } from "../../core"; import { AccessItemType, AccessItemValue, @@ -26,8 +31,7 @@ import { mapGroupToAccessItemView, mapUserToAccessItemView, PermissionMode, -} from "../../../admin-console/organizations/shared/components/access-selector"; -import { SharedModule } from "../../../shared"; +} from "../../shared/components/access-selector"; export interface BulkCollectionsDialogParams { organizationId: string; @@ -63,14 +67,22 @@ export class BulkCollectionsDialogComponent implements OnDestroy { private dialogRef: DialogRef, private formBuilder: FormBuilder, private organizationService: OrganizationService, + private accountService: AccountService, private groupService: GroupApiService, private organizationUserApiService: OrganizationUserApiService, private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, private collectionAdminService: CollectionAdminService, + private toastService: ToastService, ) { this.numCollections = this.params.collections.length; - const organization$ = this.organizationService.get$(this.params.organizationId); + const organization$ = this.accountService.activeAccount$.pipe( + switchMap((account) => + this.organizationService + .organizations$(account?.id) + .pipe(getOrganizationById(this.params.organizationId)), + ), + ); const groups$ = organization$.pipe( switchMap((organization) => { if (!organization.useGroups) { @@ -119,7 +131,11 @@ export class BulkCollectionsDialogComponent implements OnDestroy { groups, ); - this.platformUtilsService.showToast("success", null, this.i18nService.t("editedCollections")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("editedCollections"), + }); this.dialogRef.close(BulkCollectionsDialogResult.Saved); }; diff --git a/apps/web/src/app/vault/org-vault/bulk-collections-dialog/index.ts b/apps/web/src/app/admin-console/organizations/collections/bulk-collections-dialog/index.ts similarity index 100% rename from apps/web/src/app/vault/org-vault/bulk-collections-dialog/index.ts rename to apps/web/src/app/admin-console/organizations/collections/bulk-collections-dialog/index.ts diff --git a/apps/web/src/app/vault/org-vault/collection-access-restricted.component.ts b/apps/web/src/app/admin-console/organizations/collections/collection-access-restricted.component.ts similarity index 96% rename from apps/web/src/app/vault/org-vault/collection-access-restricted.component.ts rename to apps/web/src/app/admin-console/organizations/collections/collection-access-restricted.component.ts index 1e4280626fe..15ba10a0d59 100644 --- a/apps/web/src/app/vault/org-vault/collection-access-restricted.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/collection-access-restricted.component.ts @@ -2,8 +2,8 @@ import { Component, EventEmitter, Input, Output } from "@angular/core"; import { ButtonModule, NoItemsModule, svgIcon } from "@bitwarden/components"; -import { SharedModule } from "../../shared"; -import { CollectionDialogTabType } from "../components/collection-dialog"; +import { SharedModule } from "../../../shared"; +import { CollectionDialogTabType } from "../shared/components/collection-dialog"; const icon = svgIcon` diff --git a/apps/web/src/app/vault/org-vault/collection-badge/collection-name-badge.component.html b/apps/web/src/app/admin-console/organizations/collections/collection-badge/collection-name-badge.component.html similarity index 100% rename from apps/web/src/app/vault/org-vault/collection-badge/collection-name-badge.component.html rename to apps/web/src/app/admin-console/organizations/collections/collection-badge/collection-name-badge.component.html diff --git a/apps/web/src/app/vault/org-vault/collection-badge/collection-name.badge.component.ts b/apps/web/src/app/admin-console/organizations/collections/collection-badge/collection-name.badge.component.ts similarity index 78% rename from apps/web/src/app/vault/org-vault/collection-badge/collection-name.badge.component.ts rename to apps/web/src/app/admin-console/organizations/collections/collection-badge/collection-name.badge.component.ts index 3235797b882..d8ace8acc56 100644 --- a/apps/web/src/app/vault/org-vault/collection-badge/collection-name.badge.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/collection-badge/collection-name.badge.component.ts @@ -4,9 +4,14 @@ import { Component, Input } from "@angular/core"; import { CollectionView } from "@bitwarden/admin-console/common"; +import { SharedModule } from "../../../../shared/shared.module"; +import { GetCollectionNameFromIdPipe } from "../pipes"; + @Component({ selector: "app-collection-badge", templateUrl: "collection-name-badge.component.html", + standalone: true, + imports: [SharedModule, GetCollectionNameFromIdPipe], }) export class CollectionNameBadgeComponent { @Input() collectionIds: string[]; diff --git a/apps/web/src/app/admin-console/organizations/collections/collection-badge/index.ts b/apps/web/src/app/admin-console/organizations/collections/collection-badge/index.ts new file mode 100644 index 00000000000..2cc8f8eb9b6 --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/collections/collection-badge/index.ts @@ -0,0 +1 @@ +export * from "./collection-name.badge.component"; diff --git a/apps/web/src/app/vault/org-vault/group-badge/group-badge.module.ts b/apps/web/src/app/admin-console/organizations/collections/group-badge/group-badge.module.ts similarity index 65% rename from apps/web/src/app/vault/org-vault/group-badge/group-badge.module.ts rename to apps/web/src/app/admin-console/organizations/collections/group-badge/group-badge.module.ts index 26ce689ed86..a209631bb23 100644 --- a/apps/web/src/app/vault/org-vault/group-badge/group-badge.module.ts +++ b/apps/web/src/app/admin-console/organizations/collections/group-badge/group-badge.module.ts @@ -1,7 +1,7 @@ import { NgModule } from "@angular/core"; -import { SharedModule } from "../../../shared/shared.module"; -import { PipesModule } from "../../individual-vault/pipes/pipes.module"; +import { SharedModule } from "../../../../shared/shared.module"; +import { PipesModule } from "../../../../vault/individual-vault/pipes/pipes.module"; import { GroupNameBadgeComponent } from "./group-name-badge.component"; diff --git a/apps/web/src/app/vault/org-vault/group-badge/group-name-badge.component.html b/apps/web/src/app/admin-console/organizations/collections/group-badge/group-name-badge.component.html similarity index 100% rename from apps/web/src/app/vault/org-vault/group-badge/group-name-badge.component.html rename to apps/web/src/app/admin-console/organizations/collections/group-badge/group-name-badge.component.html diff --git a/apps/web/src/app/vault/org-vault/group-badge/group-name-badge.component.ts b/apps/web/src/app/admin-console/organizations/collections/group-badge/group-name-badge.component.ts similarity index 92% rename from apps/web/src/app/vault/org-vault/group-badge/group-name-badge.component.ts rename to apps/web/src/app/admin-console/organizations/collections/group-badge/group-name-badge.component.ts index 4ed145a732f..8e5f261bc26 100644 --- a/apps/web/src/app/vault/org-vault/group-badge/group-name-badge.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/group-badge/group-name-badge.component.ts @@ -5,7 +5,7 @@ import { Component, Input, OnChanges } from "@angular/core"; import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { GroupView } from "../../../admin-console/organizations/core"; +import { GroupView } from "../../core"; @Component({ selector: "app-group-badge", diff --git a/apps/web/src/app/admin-console/organizations/collections/index.ts b/apps/web/src/app/admin-console/organizations/collections/index.ts new file mode 100644 index 00000000000..57f936ab590 --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/collections/index.ts @@ -0,0 +1,2 @@ +export * from "./utils"; +export * from "./collection-badge"; diff --git a/apps/web/src/app/vault/individual-vault/pipes/get-collection-name.pipe.ts b/apps/web/src/app/admin-console/organizations/collections/pipes/get-collection-name.pipe.ts similarity index 94% rename from apps/web/src/app/vault/individual-vault/pipes/get-collection-name.pipe.ts rename to apps/web/src/app/admin-console/organizations/collections/pipes/get-collection-name.pipe.ts index b52719304b8..8833ddfa382 100644 --- a/apps/web/src/app/vault/individual-vault/pipes/get-collection-name.pipe.ts +++ b/apps/web/src/app/admin-console/organizations/collections/pipes/get-collection-name.pipe.ts @@ -5,6 +5,7 @@ import { CollectionView } from "@bitwarden/admin-console/common"; @Pipe({ name: "collectionNameFromId", pure: true, + standalone: true, }) export class GetCollectionNameFromIdPipe implements PipeTransform { transform(value: string, collections: CollectionView[]) { diff --git a/apps/web/src/app/admin-console/organizations/collections/pipes/index.ts b/apps/web/src/app/admin-console/organizations/collections/pipes/index.ts new file mode 100644 index 00000000000..63b1a86cae5 --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/collections/pipes/index.ts @@ -0,0 +1 @@ +export * from "./get-collection-name.pipe"; diff --git a/apps/web/src/app/vault/utils/collection-utils.spec.ts b/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.spec.ts similarity index 100% rename from apps/web/src/app/vault/utils/collection-utils.spec.ts rename to apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.spec.ts diff --git a/apps/web/src/app/vault/utils/collection-utils.ts b/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.ts similarity index 100% rename from apps/web/src/app/vault/utils/collection-utils.ts rename to apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.ts diff --git a/apps/web/src/app/admin-console/organizations/collections/utils/index.ts b/apps/web/src/app/admin-console/organizations/collections/utils/index.ts new file mode 100644 index 00000000000..67ff730806d --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/collections/utils/index.ts @@ -0,0 +1 @@ +export * from "./collection-utils"; diff --git a/apps/web/src/app/vault/org-vault/vault-filter/vault-filter.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts similarity index 86% rename from apps/web/src/app/vault/org-vault/vault-filter/vault-filter.component.ts rename to apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts index 0a8a6c9da94..73973e7ffde 100644 --- a/apps/web/src/app/vault/org-vault/vault-filter/vault-filter.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts @@ -10,20 +10,21 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; -import { VaultFilterComponent as BaseVaultFilterComponent } from "../../individual-vault/vault-filter/components/vault-filter.component"; //../../vault/vault-filter/components/vault-filter.component"; -import { VaultFilterService } from "../../individual-vault/vault-filter/services/abstractions/vault-filter.service"; +import { VaultFilterComponent as BaseVaultFilterComponent } from "../../../../vault/individual-vault/vault-filter/components/vault-filter.component"; +import { VaultFilterService } from "../../../../vault/individual-vault/vault-filter/services/abstractions/vault-filter.service"; import { VaultFilterList, VaultFilterSection, VaultFilterType, -} from "../../individual-vault/vault-filter/shared/models/vault-filter-section.type"; -import { CollectionFilter } from "../../individual-vault/vault-filter/shared/models/vault-filter.type"; +} from "../../../../vault/individual-vault/vault-filter/shared/models/vault-filter-section.type"; +import { CollectionFilter } from "../../../../vault/individual-vault/vault-filter/shared/models/vault-filter.type"; @Component({ selector: "app-organization-vault-filter", - templateUrl: "../../individual-vault/vault-filter/components/vault-filter.component.html", + templateUrl: + "../../../../vault/individual-vault/vault-filter/components/vault-filter.component.html", }) export class VaultFilterComponent extends BaseVaultFilterComponent @@ -43,6 +44,7 @@ export class VaultFilterComponent protected policyService: PolicyService, protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService, + protected toastService: ToastService, protected billingApiService: BillingApiServiceAbstraction, protected dialogService: DialogService, protected configService: ConfigService, @@ -52,6 +54,7 @@ export class VaultFilterComponent policyService, i18nService, platformUtilsService, + toastService, billingApiService, dialogService, configService, diff --git a/apps/web/src/app/vault/org-vault/vault-filter/vault-filter.module.ts b/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.module.ts similarity index 72% rename from apps/web/src/app/vault/org-vault/vault-filter/vault-filter.module.ts rename to apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.module.ts index 13a69796441..a0dba839b22 100644 --- a/apps/web/src/app/vault/org-vault/vault-filter/vault-filter.module.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.module.ts @@ -2,8 +2,8 @@ import { NgModule } from "@angular/core"; import { SearchModule } from "@bitwarden/components"; -import { VaultFilterService as VaultFilterServiceAbstraction } from "../../individual-vault/vault-filter/services/abstractions/vault-filter.service"; -import { VaultFilterSharedModule } from "../../individual-vault/vault-filter/shared/vault-filter-shared.module"; +import { VaultFilterService as VaultFilterServiceAbstraction } from "../../../../vault/individual-vault/vault-filter/services/abstractions/vault-filter.service"; +import { VaultFilterSharedModule } from "../../../../vault/individual-vault/vault-filter/shared/vault-filter-shared.module"; import { VaultFilterComponent } from "./vault-filter.component"; import { VaultFilterService } from "./vault-filter.service"; diff --git a/apps/web/src/app/vault/org-vault/vault-filter/vault-filter.service.ts b/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.service.ts similarity index 91% rename from apps/web/src/app/vault/org-vault/vault-filter/vault-filter.service.ts rename to apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.service.ts index e2d713649f5..f4b6f41fab6 100644 --- a/apps/web/src/app/vault/org-vault/vault-filter/vault-filter.service.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.service.ts @@ -11,8 +11,8 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; -import { VaultFilterService as BaseVaultFilterService } from "../../individual-vault/vault-filter/services/vault-filter.service"; -import { CollectionFilter } from "../../individual-vault/vault-filter/shared/models/vault-filter.type"; +import { VaultFilterService as BaseVaultFilterService } from "../../../../vault/individual-vault/vault-filter/services/vault-filter.service"; +import { CollectionFilter } from "../../../../vault/individual-vault/vault-filter/shared/models/vault-filter.type"; @Injectable() export class VaultFilterService extends BaseVaultFilterService implements OnDestroy { diff --git a/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.html b/apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.html similarity index 58% rename from apps/web/src/app/vault/org-vault/vault-header/vault-header.component.html rename to apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.html index 8178e3b2935..c2e80b7524f 100644 --- a/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.html +++ b/apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.html @@ -84,7 +84,7 @@ @@ -104,8 +104,8 @@ *ngIf="filter.type !== 'trash' && filter.collectionId !== Unassigned && organization" class="tw-shrink-0" > - - + +
    - - - - + + + + + + - +
    - - - -
    - - - - - -
    - - - - - - -
    diff --git a/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.ts similarity index 86% rename from apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts rename to apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.ts index d28148c49dc..7efb79ebdb6 100644 --- a/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.ts @@ -1,7 +1,9 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +// FIXME: rename output bindings and then remove this line +/* eslint-disable @angular-eslint/no-output-on-prefix */ import { CommonModule } from "@angular/common"; -import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; +import { Component, EventEmitter, Input, Output } from "@angular/core"; import { Router } from "@angular/router"; import { firstValueFrom } from "rxjs"; @@ -11,11 +13,8 @@ import { Unassigned, } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { 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 { CipherType } from "@bitwarden/common/vault/enums"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; @@ -27,13 +26,13 @@ import { SimpleDialogOptions, } from "@bitwarden/components"; -import { HeaderModule } from "../../../layouts/header/header.module"; -import { SharedModule } from "../../../shared"; -import { CollectionDialogTabType } from "../../components/collection-dialog"; +import { HeaderModule } from "../../../../layouts/header/header.module"; +import { SharedModule } from "../../../../shared"; import { All, RoutedVaultFilterModel, -} from "../../individual-vault/vault-filter/shared/models/routed-vault-filter.model"; +} from "../../../../vault/individual-vault/vault-filter/shared/models/routed-vault-filter.model"; +import { CollectionDialogTabType } from "../../shared/components/collection-dialog"; @Component({ standalone: true, @@ -49,7 +48,7 @@ import { JslibModule, ], }) -export class VaultHeaderComponent implements OnInit { +export class VaultHeaderComponent { protected All = All; protected Unassigned = Unassigned; @@ -90,31 +89,17 @@ export class VaultHeaderComponent implements OnInit { @Output() searchTextChanged = new EventEmitter(); protected CollectionDialogTabType = CollectionDialogTabType; - protected organizations$ = this.organizationService.organizations$; - - /** - * Whether the extension refresh feature flag is enabled. - */ - protected extensionRefreshEnabled = false; /** The cipher type enum. */ protected CipherType = CipherType; constructor( - private organizationService: OrganizationService, private i18nService: I18nService, private dialogService: DialogService, private collectionAdminService: CollectionAdminService, private router: Router, - private configService: ConfigService, ) {} - async ngOnInit() { - this.extensionRefreshEnabled = await this.configService.getFeatureFlag( - FeatureFlag.ExtensionRefresh, - ); - } - get title() { const headerType = this.i18nService.t("collections").toLowerCase(); diff --git a/apps/web/src/app/vault/org-vault/vault-routing.module.ts b/apps/web/src/app/admin-console/organizations/collections/vault-routing.module.ts similarity index 84% rename from apps/web/src/app/vault/org-vault/vault-routing.module.ts rename to apps/web/src/app/admin-console/organizations/collections/vault-routing.module.ts index 4c2ed048bd1..960ddf4397f 100644 --- a/apps/web/src/app/vault/org-vault/vault-routing.module.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault-routing.module.ts @@ -3,7 +3,7 @@ import { RouterModule, Routes } from "@angular/router"; import { canAccessVaultTab } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { organizationPermissionsGuard } from "../../admin-console/organizations/guards/org-permissions.guard"; +import { organizationPermissionsGuard } from "../guards/org-permissions.guard"; import { VaultComponent } from "./vault.component"; const routes: Routes = [ diff --git a/apps/web/src/app/vault/org-vault/vault.component.html b/apps/web/src/app/admin-console/organizations/collections/vault.component.html similarity index 88% rename from apps/web/src/app/vault/org-vault/vault.component.html rename to apps/web/src/app/admin-console/organizations/collections/vault.component.html index 512f97144de..65cd26bafee 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.html +++ b/apps/web/src/app/admin-console/organizations/collections/vault.component.html @@ -45,22 +45,16 @@ (searchTextChanged)="filterSearchText($event)" > -
    -
    -
    -
    -
    - -
    -
    -
    +
    +
    +
    -
    +
    diff --git a/apps/web/src/app/vault/org-vault/vault.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts similarity index 82% rename from apps/web/src/app/vault/org-vault/vault.component.ts rename to apps/web/src/app/admin-console/organizations/collections/vault.component.ts index 645d81cec18..ec92597dc7b 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts @@ -1,15 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { DialogRef } from "@angular/cdk/dialog"; -import { - ChangeDetectorRef, - Component, - NgZone, - OnDestroy, - OnInit, - ViewChild, - ViewContainerRef, -} from "@angular/core"; +import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute, Params, Router } from "@angular/router"; import { BehaviorSubject, @@ -32,24 +24,24 @@ import { switchMap, takeUntil, tap, - withLatestFrom, + catchError, } from "rxjs/operators"; import { CollectionAdminService, CollectionAdminView, - CollectionService, CollectionView, Unassigned, } from "@bitwarden/admin-console/common"; import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe"; -import { ModalService } from "@bitwarden/angular/services/modal.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; import { EventType } from "@bitwarden/common/enums"; @@ -62,7 +54,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SyncService } from "@bitwarden/common/platform/sync"; -import { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; +import { CipherId, CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -85,56 +77,56 @@ import { PasswordRepromptService, } from "@bitwarden/vault"; -import { GroupApiService, GroupView } from "../../admin-console/organizations/core"; -import { openEntityEventsDialog } from "../../admin-console/organizations/manage/entity-events.component"; +import { BillingNotificationService } from "../../../billing/services/billing-notification.service"; import { ResellerWarning, ResellerWarningService, -} from "../../billing/services/reseller-warning.service"; -import { TrialFlowService } from "../../billing/services/trial-flow.service"; -import { FreeTrial } from "../../core/types/free-trial"; -import { SharedModule } from "../../shared"; -import { VaultFilterService } from "../../vault/individual-vault/vault-filter/services/abstractions/vault-filter.service"; -import { VaultFilter } from "../../vault/individual-vault/vault-filter/shared/models/vault-filter.model"; -import { AssignCollectionsWebComponent } from "../components/assign-collections"; -import { - CollectionDialogAction, - CollectionDialogTabType, - openCollectionDialog, -} from "../components/collection-dialog"; +} from "../../../billing/services/reseller-warning.service"; +import { TrialFlowService } from "../../../billing/services/trial-flow.service"; +import { FreeTrial } from "../../../billing/types/free-trial"; +import { SharedModule } from "../../../shared"; +import { AssignCollectionsWebComponent } from "../../../vault/components/assign-collections"; import { VaultItemDialogComponent, VaultItemDialogMode, VaultItemDialogResult, -} from "../components/vault-item-dialog/vault-item-dialog.component"; -import { VaultItemEvent } from "../components/vault-items/vault-item-event"; -import { VaultItemsModule } from "../components/vault-items/vault-items.module"; +} from "../../../vault/components/vault-item-dialog/vault-item-dialog.component"; +import { VaultItemEvent } from "../../../vault/components/vault-items/vault-item-event"; +import { VaultItemsModule } from "../../../vault/components/vault-items/vault-items.module"; import { AttachmentDialogResult, AttachmentsV2Component, -} from "../individual-vault/attachments-v2.component"; +} from "../../../vault/individual-vault/attachments-v2.component"; import { BulkDeleteDialogResult, openBulkDeleteDialog, -} from "../individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component"; -import { RoutedVaultFilterBridgeService } from "../individual-vault/vault-filter/services/routed-vault-filter-bridge.service"; -import { RoutedVaultFilterService } from "../individual-vault/vault-filter/services/routed-vault-filter.service"; -import { createFilterFunction } from "../individual-vault/vault-filter/shared/models/filter-function"; +} from "../../../vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component"; +import { VaultFilterService } from "../../../vault/individual-vault/vault-filter/services/abstractions/vault-filter.service"; +import { RoutedVaultFilterBridgeService } from "../../../vault/individual-vault/vault-filter/services/routed-vault-filter-bridge.service"; +import { RoutedVaultFilterService } from "../../../vault/individual-vault/vault-filter/services/routed-vault-filter.service"; +import { createFilterFunction } from "../../../vault/individual-vault/vault-filter/shared/models/filter-function"; import { All, RoutedVaultFilterModel, -} from "../individual-vault/vault-filter/shared/models/routed-vault-filter.model"; -import { VaultHeaderComponent } from "../org-vault/vault-header/vault-header.component"; -import { getNestedCollectionTree } from "../utils/collection-utils"; +} from "../../../vault/individual-vault/vault-filter/shared/models/routed-vault-filter.model"; +import { VaultFilter } from "../../../vault/individual-vault/vault-filter/shared/models/vault-filter.model"; +import { AdminConsoleCipherFormConfigService } from "../../../vault/org-vault/services/admin-console-cipher-form-config.service"; +import { GroupApiService, GroupView } from "../core"; +import { openEntityEventsDialog } from "../manage/entity-events.component"; +import { + CollectionDialogAction, + CollectionDialogTabType, + openCollectionDialog, +} from "../shared/components/collection-dialog"; -import { AddEditComponent } from "./add-edit.component"; import { BulkCollectionsDialogComponent, BulkCollectionsDialogResult, } from "./bulk-collections-dialog"; import { CollectionAccessRestrictedComponent } from "./collection-access-restricted.component"; -import { AdminConsoleCipherFormConfigService } from "./services/admin-console-cipher-form-config.service"; +import { getNestedCollectionTree } from "./utils"; import { VaultFilterModule } from "./vault-filter/vault-filter.module"; +import { VaultHeaderComponent } from "./vault-header/vault-header.component"; const BroadcasterSubscriptionId = "OrgVaultComponent"; const SearchTextDebounceInterval = 200; @@ -166,13 +158,6 @@ enum AddAccessStatusType { export class VaultComponent implements OnInit, OnDestroy { protected Unassigned = Unassigned; - @ViewChild("attachments", { read: ViewContainerRef, static: true }) - attachmentsModalRef: ViewContainerRef; - @ViewChild("cipherAddEdit", { read: ViewContainerRef, static: true }) - cipherAddEditModalRef: ViewContainerRef; - @ViewChild("collectionsModal", { read: ViewContainerRef, static: true }) - collectionsModalRef: ViewContainerRef; - trashCleanupWarning: string = null; activeFilter: VaultFilter = new VaultFilter(); @@ -194,6 +179,8 @@ export class VaultComponent implements OnInit, OnDestroy { protected currentSearchText$: Observable; protected freeTrial$: Observable; protected resellerWarning$: Observable; + protected prevCipherId: string | null = null; + protected userId: UserId; /** * A list of collections that the user can assign items to and edit those items within. * @protected @@ -209,23 +196,27 @@ export class VaultComponent implements OnInit, OnDestroy { private refresh$ = new BehaviorSubject(null); private destroy$ = new Subject(); protected addAccessStatus$ = new BehaviorSubject(0); - private extensionRefreshEnabled: boolean; private resellerManagedOrgAlert: boolean; private vaultItemDialogRef?: DialogRef | undefined; - private readonly unpaidSubscriptionDialog$ = this.organizationService.organizations$.pipe( - filter((organizations) => organizations.length === 1), - map(([organization]) => organization), - switchMap((organization) => - from(this.billingApiService.getOrganizationBillingMetadata(organization.id)).pipe( - tap((organizationMetaData) => { - this.hasSubscription$.next(organizationMetaData.hasSubscription); - }), - switchMap((organizationMetaData) => - from( - this.trialFlowService.handleUnpaidSubscriptionDialog( - organization, - organizationMetaData, + private readonly unpaidSubscriptionDialog$ = this.accountService.activeAccount$.pipe( + map((account) => account?.id), + switchMap((id) => + this.organizationService.organizations$(id).pipe( + filter((organizations) => organizations.length === 1), + map(([organization]) => organization), + switchMap((organization) => + from(this.billingApiService.getOrganizationBillingMetadata(organization.id)).pipe( + tap((organizationMetaData) => { + this.hasSubscription$.next(organizationMetaData.hasSubscription); + }), + switchMap((organizationMetaData) => + from( + this.trialFlowService.handleUnpaidSubscriptionDialog( + organization, + organizationMetaData, + ), + ), ), ), ), @@ -243,7 +234,6 @@ export class VaultComponent implements OnInit, OnDestroy { private changeDetectorRef: ChangeDetectorRef, private syncService: SyncService, private i18nService: I18nService, - private modalService: ModalService, private dialogService: DialogService, private messagingService: MessagingService, private broadcasterService: BroadcasterService, @@ -259,7 +249,6 @@ export class VaultComponent implements OnInit, OnDestroy { private eventCollectionService: EventCollectionService, private totpService: TotpService, private apiService: ApiService, - private collectionService: CollectionService, private toastService: ToastService, private configService: ConfigService, private cipherFormConfigService: CipherFormConfigService, @@ -268,12 +257,12 @@ export class VaultComponent implements OnInit, OnDestroy { protected billingApiService: BillingApiServiceAbstraction, private organizationBillingService: OrganizationBillingServiceAbstraction, private resellerWarningService: ResellerWarningService, + private accountService: AccountService, + private billingNotificationService: BillingNotificationService, ) {} async ngOnInit() { - this.extensionRefreshEnabled = await this.configService.getFeatureFlag( - FeatureFlag.ExtensionRefresh, - ); + this.userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); this.resellerManagedOrgAlert = await this.configService.getFeatureFlag( FeatureFlag.ResellerManagedOrgAlert, @@ -292,10 +281,19 @@ export class VaultComponent implements OnInit, OnDestroy { distinctUntilChanged(), ); - const organization$ = organizationId$.pipe( - switchMap((organizationId) => this.organizationService.get$(organizationId)), - takeUntil(this.destroy$), - shareReplay({ refCount: false, bufferSize: 1 }), + const organization$ = this.accountService.activeAccount$.pipe( + map((account) => account?.id), + switchMap((id) => + organizationId$.pipe( + switchMap((organizationId) => + this.organizationService + .organizations$(id) + .pipe(map((organizations) => organizations.find((org) => org.id === organizationId))), + ), + takeUntil(this.destroy$), + shareReplay({ refCount: false, bufferSize: 1 }), + ), + ), ); const firstSetup$ = combineLatest([organization$, this.route.queryParams]).pipe( @@ -409,7 +407,7 @@ export class VaultComponent implements OnInit, OnDestroy { ciphers = await this.cipherService.getManyFromApiForOrganization(organization.id); } - await this.searchService.indexCiphers(ciphers, organization.id); + await this.searchService.indexCiphers(this.userId, ciphers, organization.id); return ciphers; }), shareReplay({ refCount: true, bufferSize: 1 }), @@ -453,7 +451,7 @@ export class VaultComponent implements OnInit, OnDestroy { collectionsToReturn = selectedCollection?.children.map((c) => c.node) ?? []; } - if (await this.searchService.isSearchable(searchText)) { + if (await this.searchService.isSearchable(this.userId, searchText)) { collectionsToReturn = this.searchPipe.transform( collectionsToReturn, searchText, @@ -527,8 +525,13 @@ export class VaultComponent implements OnInit, OnDestroy { const filterFunction = createFilterFunction(filter); - if (await this.searchService.isSearchable(searchText)) { - return await this.searchService.searchCiphers(searchText, [filterFunction], ciphers); + if (await this.searchService.isSearchable(this.userId, searchText)) { + return await this.searchService.searchCiphers( + this.userId, + searchText, + [filterFunction], + ciphers, + ); } return ciphers.filter(filterFunction); @@ -538,25 +541,26 @@ export class VaultComponent implements OnInit, OnDestroy { firstSetup$ .pipe( - switchMap(() => this.route.queryParams), - // Only process the queryParams if the dialog is not open (only when extension refresh is enabled) - filter(() => this.vaultItemDialogRef == undefined || !this.extensionRefreshEnabled), - withLatestFrom(allCipherMap$, allCollections$, organization$), + switchMap(() => combineLatest([this.route.queryParams, allCipherMap$])), + filter(() => this.vaultItemDialogRef == undefined), switchMap(async ([qParams, allCiphersMap]) => { const cipherId = getCipherIdFromParams(qParams); + if (!cipherId) { + this.prevCipherId = null; return; } - const cipher = allCiphersMap[cipherId]; + if (cipherId === this.prevCipherId) { + return; + } + + this.prevCipherId = cipherId; + + const cipher = allCiphersMap[cipherId]; if (cipher) { let action = qParams.action; - // Default to "view" if extension refresh is enabled - if (action == null && this.extensionRefreshEnabled) { - action = "view"; - } - if (action == "showFailedToDecrypt") { DecryptionFailureDialogComponent.open(this.dialogService, { cipherIds: [cipherId as CipherId], @@ -569,10 +573,15 @@ export class VaultComponent implements OnInit, OnDestroy { return; } + // Default to "view" + if (action == null) { + action = "view"; + } + if (action === "view") { await this.viewCipherById(cipher); } else { - await this.editCipherId(cipher, false); + await this.editCipher(cipher, false); } } else { this.toastService.showToast({ @@ -630,12 +639,18 @@ export class VaultComponent implements OnInit, OnDestroy { combineLatest([ of(org), this.organizationApiService.getSubscription(org.id), - this.organizationBillingService.getPaymentSource(org.id), + from(this.organizationBillingService.getPaymentSource(org.id)).pipe( + catchError((error: unknown) => { + this.billingNotificationService.handleError(error); + return of(null); + }), + ), ]), ), - map(([org, sub, paymentSource]) => { - return this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues(org, sub, paymentSource); - }), + map(([org, sub, paymentSource]) => + this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues(org, sub, paymentSource), + ), + filter((result) => result !== null), ); this.resellerWarning$ = organization$.pipe( @@ -814,27 +829,8 @@ export class VaultComponent implements OnInit, OnDestroy { } } + /** Opens the Add/Edit Dialog */ async addCipher(cipherType?: CipherType) { - if (this.extensionRefreshEnabled) { - return this.addCipherV2(cipherType); - } - - let collections: CollectionView[] = []; - - // Admins limited to only adding items to collections they have access to. - collections = await firstValueFrom(this.editableCollections$); - - await this.editCipher(null, false, (comp) => { - comp.type = cipherType || this.activeFilter.cipherType; - comp.collections = collections; - if (this.activeFilter.collectionId) { - comp.collectionIds = [this.activeFilter.collectionId]; - } - }); - } - - /** Opens the Add/Edit Dialog. Only to be used when the BrowserExtension feature flag is active */ - async addCipherV2(cipherType?: CipherType) { const cipherFormConfig = await this.cipherFormConfigService.buildConfig( "add", null, @@ -855,24 +851,8 @@ export class VaultComponent implements OnInit, OnDestroy { * Edit the given cipher or add a new cipher * @param cipherView - When set, the cipher to be edited * @param cloneCipher - `true` when the cipher should be cloned. - * Used in place of the `additionalComponentParameters`, as - * the `editCipherIdV2` method has a differing implementation. - * @param defaultComponentParameters - A method that takes in an instance of - * the `AddEditComponent` to edit methods directly. */ - async editCipher( - cipher: CipherView | null, - cloneCipher: boolean, - additionalComponentParameters?: (comp: AddEditComponent) => void, - ) { - return this.editCipherId(cipher, cloneCipher, additionalComponentParameters); - } - - async editCipherId( - cipher: CipherView | null, - cloneCipher: boolean, - additionalComponentParameters?: (comp: AddEditComponent) => void, - ) { + async editCipher(cipher: CipherView | null, cloneCipher: boolean) { if ( cipher && cipher.reprompt !== 0 && @@ -883,55 +863,6 @@ export class VaultComponent implements OnInit, OnDestroy { return; } - if (this.extensionRefreshEnabled) { - await this.editCipherIdV2(cipher, cloneCipher); - return; - } - - const defaultComponentParameters = (comp: AddEditComponent) => { - comp.organization = this.organization; - comp.organizationId = this.organization.id; - comp.cipherId = cipher?.id; - comp.collectionId = this.activeFilter.collectionId; - comp.onSavedCipher.pipe(takeUntil(this.destroy$)).subscribe(() => { - modal.close(); - this.refresh(); - }); - comp.onDeletedCipher.pipe(takeUntil(this.destroy$)).subscribe(() => { - modal.close(); - this.refresh(); - }); - comp.onRestoredCipher.pipe(takeUntil(this.destroy$)).subscribe(() => { - modal.close(); - this.refresh(); - }); - }; - - const [modal, childComponent] = await this.modalService.openViewRef( - AddEditComponent, - this.cipherAddEditModalRef, - additionalComponentParameters == null - ? defaultComponentParameters - : (comp) => { - defaultComponentParameters(comp); - additionalComponentParameters(comp); - }, - ); - - // 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 - modal.onClosedPromise().then(() => { - this.go({ cipherId: null, itemId: null, action: null }); - }); - - return childComponent; - } - - /** - * Edit a cipher using the new AddEditCipherDialogV2 component. - * Only to be used behind the ExtensionRefresh feature flag. - */ - private async editCipherIdV2(cipher: CipherView | null, cloneCipher: boolean) { const cipherFormConfig = await this.cipherFormConfigService.buildConfig( cloneCipher ? "clone" : "edit", cipher?.id as CipherId | null, @@ -988,6 +919,7 @@ export class VaultComponent implements OnInit, OnDestroy { disableForm, activeCollectionId, isAdminConsoleAction: true, + restore: this.restore, }); const result = await lastValueFrom(this.vaultItemDialogRef.closed); @@ -1015,19 +947,10 @@ export class VaultComponent implements OnInit, OnDestroy { } } - let collections: CollectionView[] = []; - - // Admins limited to only adding items to collections they have access to. - collections = await firstValueFrom(this.editableCollections$); - - await this.editCipher(cipher, true, (comp) => { - comp.cloneMode = true; - comp.collections = collections; - comp.collectionIds = cipher.collectionIds; - }); + await this.editCipher(cipher, true); } - async restore(c: CipherView): Promise { + restore = async (c: CipherView): Promise => { if (!c.isDeleted) { return; } @@ -1047,8 +970,9 @@ export class VaultComponent implements OnInit, OnDestroy { // Allow restore of an Unassigned Item try { + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const asAdmin = this.organization?.canEditAnyCollection || c.isUnassigned; - await this.cipherService.restoreWithServer(c.id, asAdmin); + await this.cipherService.restoreWithServer(c.id, activeUserId, asAdmin); this.toastService.showToast({ variant: "success", title: null, @@ -1058,7 +982,7 @@ export class VaultComponent implements OnInit, OnDestroy { } catch (e) { this.logService.error(e); } - } + }; async bulkRestore(ciphers: CipherView[]) { if ( @@ -1139,7 +1063,8 @@ export class VaultComponent implements OnInit, OnDestroy { } try { - await this.deleteCipherWithServer(c.id, permanent, c.isUnassigned); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + await this.deleteCipherWithServer(c.id, activeUserId, permanent, c.isUnassigned); this.toastService.showToast({ variant: "success", title: null, @@ -1261,7 +1186,8 @@ export class VaultComponent implements OnInit, OnDestroy { typeI18nKey = "password"; } else if (field === "totp") { aType = "TOTP"; - value = await this.totpService.getCode(cipher.login.totp); + const totpResponse = await firstValueFrom(this.totpService.getCode$(cipher.login.totp)); + value = totpResponse?.code; typeI18nKey = "verificationCodeTotp"; } else { this.toastService.showToast({ @@ -1427,11 +1353,16 @@ export class VaultComponent implements OnInit, OnDestroy { }); } - protected deleteCipherWithServer(id: string, permanent: boolean, isUnassigned: boolean) { + protected deleteCipherWithServer( + id: string, + userId: UserId, + permanent: boolean, + isUnassigned: boolean, + ) { const asAdmin = this.organization?.canEditAllCiphers || isUnassigned; return permanent - ? this.cipherService.deleteWithServer(id, asAdmin) - : this.cipherService.softDeleteWithServer(id, asAdmin); + ? this.cipherService.deleteWithServer(id, userId, asAdmin) + : this.cipherService.softDeleteWithServer(id, userId, asAdmin); } protected async repromptCipher(ciphers: CipherView[]) { diff --git a/apps/web/src/app/admin-console/organizations/collections/vault.module.ts b/apps/web/src/app/admin-console/organizations/collections/vault.module.ts new file mode 100644 index 00000000000..037a27cd781 --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/collections/vault.module.ts @@ -0,0 +1,27 @@ +import { NgModule } from "@angular/core"; + +import { LooseComponentsModule } from "../../../shared/loose-components.module"; +import { SharedModule } from "../../../shared/shared.module"; +import { OrganizationBadgeModule } from "../../../vault/individual-vault/organization-badge/organization-badge.module"; +import { ViewComponent } from "../../../vault/individual-vault/view.component"; +import { CollectionDialogComponent } from "../shared/components/collection-dialog"; + +import { CollectionNameBadgeComponent } from "./collection-badge"; +import { GroupBadgeModule } from "./group-badge/group-badge.module"; +import { VaultRoutingModule } from "./vault-routing.module"; +import { VaultComponent } from "./vault.component"; + +@NgModule({ + imports: [ + VaultRoutingModule, + SharedModule, + LooseComponentsModule, + GroupBadgeModule, + CollectionNameBadgeComponent, + OrganizationBadgeModule, + CollectionDialogComponent, + VaultComponent, + ViewComponent, + ], +}) +export class VaultModule {} diff --git a/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.spec.ts b/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.spec.ts index 5d61f7cf25a..f5fce0e5e42 100644 --- a/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.spec.ts +++ b/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.spec.ts @@ -5,11 +5,16 @@ import { TestBed } from "@angular/core/testing"; import { provideRouter } from "@angular/router"; import { RouterTestingHarness } from "@angular/router/testing"; import { MockProxy, any, mock } from "jest-mock-extended"; +import { of } from "rxjs"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationUserType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ProductTierType } from "@bitwarden/common/billing/enums"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { DialogService } from "@bitwarden/components"; import { isEnterpriseOrgGuard } from "./is-enterprise-org.guard"; @@ -44,15 +49,19 @@ describe("Is Enterprise Org Guard", () => { let organizationService: MockProxy; let dialogService: MockProxy; let routerHarness: RouterTestingHarness; + let accountService: FakeAccountService; + const userId = Utils.newGuid() as UserId; beforeEach(async () => { organizationService = mock(); dialogService = mock(); + accountService = mockAccountServiceWith(userId); TestBed.configureTestingModule({ providers: [ { provide: OrganizationService, useValue: organizationService }, { provide: DialogService, useValue: dialogService }, + { provide: AccountService, useValue: accountService }, provideRouter([ { path: "", @@ -82,7 +91,7 @@ describe("Is Enterprise Org Guard", () => { it("redirects to `/` if the organization id provided is not found", async () => { const org = orgFactory(); - organizationService.get.calledWith(org.id).mockResolvedValue(null); + organizationService.organizations$.calledWith(userId).mockReturnValue(of([])); await routerHarness.navigateByUrl(`organizations/${org.id}/enterpriseOrgsOnly`); expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe( "This is the home screen!", @@ -101,7 +110,7 @@ describe("Is Enterprise Org Guard", () => { type: OrganizationUserType.User, productTierType: productTierType, }); - organizationService.get.calledWith(org.id).mockResolvedValue(org); + organizationService.organizations$.calledWith(userId).mockReturnValue(of([org])); await routerHarness.navigateByUrl(`organizations/${org.id}/enterpriseOrgsOnly`); expect(dialogService.openSimpleDialog).toHaveBeenCalled(); expect( @@ -115,7 +124,7 @@ describe("Is Enterprise Org Guard", () => { type: OrganizationUserType.Owner, productTierType: ProductTierType.Teams, }); - organizationService.get.calledWith(org.id).mockResolvedValue(org); + organizationService.organizations$.calledWith(userId).mockReturnValue(of([org])); dialogService.openSimpleDialog.calledWith(any()).mockResolvedValue(true); await routerHarness.navigateByUrl(`organizations/${org.id}/enterpriseOrgsOnly`); expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe( @@ -133,7 +142,7 @@ describe("Is Enterprise Org Guard", () => { type: OrganizationUserType.User, productTierType: productTierType, }); - organizationService.get.calledWith(org.id).mockResolvedValue(org); + organizationService.organizations$.calledWith(userId).mockReturnValue(of([org])); await routerHarness.navigateByUrl(`organizations/${org.id}/enterpriseOrgsOnlyNoError`); expect(dialogService.openSimpleDialog).not.toHaveBeenCalled(); expect( @@ -143,7 +152,7 @@ describe("Is Enterprise Org Guard", () => { it("proceeds with navigation if the organization in question is a enterprise organization", async () => { const org = orgFactory({ productTierType: ProductTierType.Enterprise }); - organizationService.get.calledWith(org.id).mockResolvedValue(org); + organizationService.organizations$.calledWith(userId).mockReturnValue(of([org])); await routerHarness.navigateByUrl(`organizations/${org.id}/enterpriseOrgsOnly`); expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe( "This component can only be accessed by a enterprise organization!", diff --git a/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.ts b/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.ts index e1f3a1b8259..c79abed583c 100644 --- a/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.ts +++ b/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.ts @@ -7,8 +7,13 @@ import { Router, RouterStateSnapshot, } from "@angular/router"; +import { firstValueFrom, map } from "rxjs"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ProductTierType } from "@bitwarden/common/billing/enums"; import { DialogService } from "@bitwarden/components"; @@ -23,9 +28,15 @@ export function isEnterpriseOrgGuard(showError: boolean = true): CanActivateFn { return async (route: ActivatedRouteSnapshot, _state: RouterStateSnapshot) => { const router = inject(Router); const organizationService = inject(OrganizationService); + const accountService = inject(AccountService); const dialogService = inject(DialogService); - const org = await organizationService.get(route.params.organizationId); + const userId = await firstValueFrom(accountService.activeAccount$.pipe(map((a) => a?.id))); + const org = await firstValueFrom( + organizationService + .organizations$(userId) + .pipe(getOrganizationById(route.params.organizationId)), + ); if (org == null) { return router.createUrlTree(["/"]); diff --git a/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.spec.ts b/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.spec.ts index 6572b8aabd6..8efed8cefa2 100644 --- a/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.spec.ts +++ b/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.spec.ts @@ -5,10 +5,15 @@ import { TestBed } from "@angular/core/testing"; import { provideRouter } from "@angular/router"; import { RouterTestingHarness } from "@angular/router/testing"; import { MockProxy, any, mock } from "jest-mock-extended"; +import { of } from "rxjs"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationUserType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { DialogService } from "@bitwarden/components"; import { isPaidOrgGuard } from "./is-paid-org.guard"; @@ -43,15 +48,19 @@ describe("Is Paid Org Guard", () => { let organizationService: MockProxy; let dialogService: MockProxy; let routerHarness: RouterTestingHarness; + let accountService: FakeAccountService; + const userId = Utils.newGuid() as UserId; beforeEach(async () => { organizationService = mock(); dialogService = mock(); + accountService = mockAccountServiceWith(userId); TestBed.configureTestingModule({ providers: [ { provide: OrganizationService, useValue: organizationService }, { provide: DialogService, useValue: dialogService }, + { provide: AccountService, useValue: accountService }, provideRouter([ { path: "", @@ -75,7 +84,7 @@ describe("Is Paid Org Guard", () => { it("redirects to `/` if the organization id provided is not found", async () => { const org = orgFactory(); - organizationService.get.calledWith(org.id).mockResolvedValue(null); + organizationService.organizations$.calledWith(userId).mockReturnValue(of([])); await routerHarness.navigateByUrl(`organizations/${org.id}/paidOrganizationsOnly`); expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe( "This is the home screen!", @@ -86,7 +95,7 @@ describe("Is Paid Org Guard", () => { // `useTotp` is the current indicator of a free org, it is the baseline // feature offered above the free organization level. const org = orgFactory({ type: OrganizationUserType.User, useTotp: false }); - organizationService.get.calledWith(org.id).mockResolvedValue(org); + organizationService.organizations$.calledWith(userId).mockReturnValue(of([org])); await routerHarness.navigateByUrl(`organizations/${org.id}/paidOrganizationsOnly`); expect(dialogService.openSimpleDialog).toHaveBeenCalled(); expect( @@ -98,7 +107,7 @@ describe("Is Paid Org Guard", () => { // `useTotp` is the current indicator of a free org, it is the baseline // feature offered above the free organization level. const org = orgFactory({ type: OrganizationUserType.Owner, useTotp: false }); - organizationService.get.calledWith(org.id).mockResolvedValue(org); + organizationService.organizations$.calledWith(userId).mockReturnValue(of([org])); dialogService.openSimpleDialog.calledWith(any()).mockResolvedValue(true); await routerHarness.navigateByUrl(`organizations/${org.id}/paidOrganizationsOnly`); expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe( @@ -108,7 +117,7 @@ describe("Is Paid Org Guard", () => { it("proceeds with navigation if the organization in question is a paid organization", async () => { const org = orgFactory({ useTotp: true }); - organizationService.get.calledWith(org.id).mockResolvedValue(org); + organizationService.organizations$.calledWith(userId).mockReturnValue(of([org])); await routerHarness.navigateByUrl(`organizations/${org.id}/paidOrganizationsOnly`); expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe( "This component can only be accessed by a paid organization!", diff --git a/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.ts b/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.ts index 0fe95e3d3a0..9a59c884ee7 100644 --- a/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.ts +++ b/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.ts @@ -7,8 +7,13 @@ import { Router, RouterStateSnapshot, } from "@angular/router"; +import { firstValueFrom, map } from "rxjs"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { DialogService } from "@bitwarden/components"; /** @@ -22,9 +27,15 @@ export function isPaidOrgGuard(): CanActivateFn { return async (route: ActivatedRouteSnapshot, _state: RouterStateSnapshot) => { const router = inject(Router); const organizationService = inject(OrganizationService); + const accountService = inject(AccountService); const dialogService = inject(DialogService); - const org = await organizationService.get(route.params.organizationId); + const userId = await firstValueFrom(accountService.activeAccount$.pipe(map((a) => a?.id))); + const org = await firstValueFrom( + organizationService + .organizations$(userId) + .pipe(getOrganizationById(route.params.organizationId)), + ); if (org == null) { return router.createUrlTree(["/"]); diff --git a/apps/web/src/app/admin-console/organizations/guards/org-permissions.guard.spec.ts b/apps/web/src/app/admin-console/organizations/guards/org-permissions.guard.spec.ts index 39f5dc429bc..9afd34ca149 100644 --- a/apps/web/src/app/admin-console/organizations/guards/org-permissions.guard.spec.ts +++ b/apps/web/src/app/admin-console/organizations/guards/org-permissions.guard.spec.ts @@ -8,46 +8,65 @@ import { RouterStateSnapshot, } from "@angular/router"; import { mock, MockProxy } from "jest-mock-extended"; +import { of } from "rxjs"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationUserType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { ToastService } from "@bitwarden/components"; import { organizationPermissionsGuard } from "./org-permissions.guard"; +// Returns a test organization with the specified props. const orgFactory = (props: Partial = {}) => Object.assign( new Organization(), { - id: "myOrgId", enabled: true, type: OrganizationUserType.Admin, }, props, ); +const targetOrgId = "myOrgId"; + +// Returns an array of test organizations with the target organization in the middle. +// This more accurately tests the return value of OrganizationService. +const orgStateFactory = (targetOrgProps: Partial = {}) => [ + orgFactory({ id: "anotherOrg" }), + orgFactory({ id: targetOrgId, ...targetOrgProps }), // target org intentionally nestled in the middle + orgFactory({ id: "andAnotherOrg" }), +]; + describe("Organization Permissions Guard", () => { let router: MockProxy; let organizationService: MockProxy; let state: MockProxy; let route: MockProxy; + let accountService: FakeAccountService; + const userId = Utils.newGuid() as UserId; beforeEach(() => { router = mock(); organizationService = mock(); + accountService = mockAccountServiceWith(userId); state = mock(); route = mock({ params: { - organizationId: orgFactory().id, + organizationId: targetOrgId, }, }); TestBed.configureTestingModule({ providers: [ { provide: Router, useValue: router }, + { provide: AccountService, useValue: accountService }, { provide: OrganizationService, useValue: organizationService }, { provide: ToastService, useValue: mock() }, { provide: I18nService, useValue: mock() }, @@ -57,7 +76,7 @@ describe("Organization Permissions Guard", () => { }); it("blocks navigation if organization does not exist", async () => { - organizationService.get.mockReturnValue(null); + organizationService.organizations$.mockReturnValue(of([])); const actual = await TestBed.runInInjectionContext( async () => await organizationPermissionsGuard()(route, state), @@ -66,82 +85,79 @@ describe("Organization Permissions Guard", () => { expect(actual).not.toBe(true); }); - it("permits navigation if no permissions are specified", async () => { - const org = orgFactory(); - organizationService.get.calledWith(org.id).mockResolvedValue(org); + describe("given an enabled organization", () => { + beforeEach(() => { + organizationService.organizations$.calledWith(userId).mockReturnValue(of(orgStateFactory())); + }); - const actual = await TestBed.runInInjectionContext(async () => - organizationPermissionsGuard()(route, state), - ); + it("permits navigation if no permissions are specified", async () => { + const actual = await TestBed.runInInjectionContext(async () => + organizationPermissionsGuard()(route, state), + ); - expect(actual).toBe(true); - }); + expect(actual).toBe(true); + }); - it("permits navigation if the user has permissions", async () => { - const permissionsCallback = jest.fn(); - permissionsCallback.mockImplementation((_org) => true); - const org = orgFactory(); - organizationService.get.calledWith(org.id).mockResolvedValue(org); - - const actual = await TestBed.runInInjectionContext( - async () => await organizationPermissionsGuard(permissionsCallback)(route, state), - ); - - expect(permissionsCallback).toHaveBeenCalled(); - expect(actual).toBe(true); - }); - - describe("if the user does not have permissions", () => { - it("and there is no Item ID, block navigation", async () => { + it("permits navigation if the user has permissions", async () => { const permissionsCallback = jest.fn(); - permissionsCallback.mockImplementation((_org) => false); - - state = mock({ - root: mock({ - queryParamMap: convertToParamMap({}), - }), - }); - - const org = orgFactory(); - organizationService.get.calledWith(org.id).mockResolvedValue(org); + permissionsCallback.mockImplementation((_org) => true); const actual = await TestBed.runInInjectionContext( async () => await organizationPermissionsGuard(permissionsCallback)(route, state), ); - expect(permissionsCallback).toHaveBeenCalled(); - expect(actual).not.toBe(true); + expect(permissionsCallback).toHaveBeenCalledWith(orgFactory({ id: targetOrgId })); + expect(actual).toBe(true); }); - it("and there is an Item ID, redirect to the item in the individual vault", async () => { - state = mock({ - root: mock({ - queryParamMap: convertToParamMap({ - itemId: "myItemId", + describe("if the user does not have permissions", () => { + it("and there is no Item ID, block navigation", async () => { + const permissionsCallback = jest.fn(); + permissionsCallback.mockImplementation((_org) => false); + + state = mock({ + root: mock({ + queryParamMap: convertToParamMap({}), }), - }), - }); - const org = orgFactory(); - organizationService.get.calledWith(org.id).mockResolvedValue(org); + }); - const actual = await TestBed.runInInjectionContext( - async () => await organizationPermissionsGuard((_org: Organization) => false)(route, state), - ); + const actual = await TestBed.runInInjectionContext( + async () => await organizationPermissionsGuard(permissionsCallback)(route, state), + ); - expect(router.createUrlTree).toHaveBeenCalledWith(["/vault"], { - queryParams: { itemId: "myItemId" }, + expect(permissionsCallback).toHaveBeenCalledWith(orgFactory({ id: targetOrgId })); + expect(actual).not.toBe(true); + }); + + it("and there is an Item ID, redirect to the item in the individual vault", async () => { + state = mock({ + root: mock({ + queryParamMap: convertToParamMap({ + itemId: "myItemId", + }), + }), + }); + + const actual = await TestBed.runInInjectionContext( + async () => + await organizationPermissionsGuard((_org: Organization) => false)(route, state), + ); + + expect(router.createUrlTree).toHaveBeenCalledWith(["/vault"], { + queryParams: { itemId: "myItemId" }, + }); + expect(actual).not.toBe(true); }); - expect(actual).not.toBe(true); }); }); describe("given a disabled organization", () => { it("blocks navigation if user is not an owner", async () => { - const org = orgFactory({ + const orgs = orgStateFactory({ type: OrganizationUserType.Admin, enabled: false, }); - organizationService.get.calledWith(org.id).mockResolvedValue(org); + organizationService.organizations$.calledWith(userId).mockReturnValue(of(orgs)); const actual = await TestBed.runInInjectionContext( async () => await organizationPermissionsGuard()(route, state), @@ -151,11 +167,12 @@ describe("Organization Permissions Guard", () => { }); it("permits navigation if user is an owner", async () => { - const org = orgFactory({ + const orgs = orgStateFactory({ type: OrganizationUserType.Owner, enabled: false, }); - organizationService.get.calledWith(org.id).mockResolvedValue(org); + + organizationService.organizations$.calledWith(userId).mockReturnValue(of(orgs)); const actual = await TestBed.runInInjectionContext( async () => await organizationPermissionsGuard()(route, state), diff --git a/apps/web/src/app/admin-console/organizations/guards/org-permissions.guard.ts b/apps/web/src/app/admin-console/organizations/guards/org-permissions.guard.ts index 6bb881762c8..d399f9c9c05 100644 --- a/apps/web/src/app/admin-console/organizations/guards/org-permissions.guard.ts +++ b/apps/web/src/app/admin-console/organizations/guards/org-permissions.guard.ts @@ -7,13 +7,17 @@ import { Router, RouterStateSnapshot, } from "@angular/router"; +import { firstValueFrom, switchMap } from "rxjs"; import { canAccessOrgAdmin, OrganizationService, } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { getById } from "@bitwarden/common/platform/misc"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { ToastService } from "@bitwarden/components"; @@ -46,13 +50,21 @@ export function organizationPermissionsGuard( const toastService = inject(ToastService); const i18nService = inject(I18nService); const syncService = inject(SyncService); + const accountService = inject(AccountService); // TODO: We need to fix issue once and for all. if ((await syncService.getLastSync()) == null) { await syncService.fullSync(false); } - const org = await organizationService.get(route.params.organizationId); + const org = await firstValueFrom( + accountService.activeAccount$.pipe( + getUserId, + switchMap((userId) => organizationService.organizations$(userId)), + getById(route.params.organizationId), + ), + ); + if (org == null) { return router.createUrlTree(["/"]); } diff --git a/apps/web/src/app/admin-console/organizations/guards/org-redirect.guard.spec.ts b/apps/web/src/app/admin-console/organizations/guards/org-redirect.guard.spec.ts index 264f2c6d4a8..fa348867a86 100644 --- a/apps/web/src/app/admin-console/organizations/guards/org-redirect.guard.spec.ts +++ b/apps/web/src/app/admin-console/organizations/guards/org-redirect.guard.spec.ts @@ -5,10 +5,15 @@ import { TestBed } from "@angular/core/testing"; import { provideRouter } from "@angular/router"; import { RouterTestingHarness } from "@angular/router/testing"; import { MockProxy, mock } from "jest-mock-extended"; +import { of } from "rxjs"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationUserType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { organizationRedirectGuard } from "./org-redirect.guard"; @@ -41,13 +46,17 @@ const orgFactory = (props: Partial = {}) => describe("Organization Redirect Guard", () => { let organizationService: MockProxy; let routerHarness: RouterTestingHarness; + let accountService: FakeAccountService; + const userId = Utils.newGuid() as UserId; beforeEach(async () => { organizationService = mock(); + accountService = mockAccountServiceWith(userId); TestBed.configureTestingModule({ providers: [ { provide: OrganizationService, useValue: organizationService }, + { provide: AccountService, useValue: accountService }, provideRouter([ { path: "", @@ -89,16 +98,16 @@ describe("Organization Redirect Guard", () => { it("redirects to `/` if the organization id provided is not found", async () => { const org = orgFactory(); - organizationService.get.calledWith(org.id).mockResolvedValue(null); + organizationService.organizations$.calledWith(userId).mockReturnValue(of([])); await routerHarness.navigateByUrl(`organizations/${org.id}/noCallback`); expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe( "This is the home screen!", ); }); - it("redirects to `/organizations/{id}` if no custom redirect is supplied but the user can access the admin onsole", async () => { + it("redirects to `/organizations/{id}` if no custom redirect is supplied but the user can access the admin console", async () => { const org = orgFactory(); - organizationService.get.calledWith(org.id).mockResolvedValue(org); + organizationService.organizations$.calledWith(userId).mockReturnValue(of([org])); await routerHarness.navigateByUrl(`organizations/${org.id}/noCallback`); expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe( "This is the admin console!", @@ -107,7 +116,7 @@ describe("Organization Redirect Guard", () => { it("redirects properly when the redirect callback returns a single string", async () => { const org = orgFactory(); - organizationService.get.calledWith(org.id).mockResolvedValue(org); + organizationService.organizations$.calledWith(userId).mockReturnValue(of([org])); await routerHarness.navigateByUrl(`organizations/${org.id}/stringCallback`); expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe( "This is a subroute of the admin console!", @@ -116,7 +125,7 @@ describe("Organization Redirect Guard", () => { it("redirects properly when the redirect callback returns an array of strings", async () => { const org = orgFactory(); - organizationService.get.calledWith(org.id).mockResolvedValue(org); + organizationService.organizations$.calledWith(userId).mockReturnValue(of([org])); await routerHarness.navigateByUrl(`organizations/${org.id}/arrayCallback`); expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe( "This is a subroute of the admin console!", diff --git a/apps/web/src/app/admin-console/organizations/guards/org-redirect.guard.ts b/apps/web/src/app/admin-console/organizations/guards/org-redirect.guard.ts index 1ab73195e4d..d0352586efa 100644 --- a/apps/web/src/app/admin-console/organizations/guards/org-redirect.guard.ts +++ b/apps/web/src/app/admin-console/organizations/guards/org-redirect.guard.ts @@ -5,12 +5,14 @@ import { Router, RouterStateSnapshot, } from "@angular/router"; +import { firstValueFrom, map } from "rxjs"; import { canAccessOrgAdmin, OrganizationService, } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; /** * @@ -25,8 +27,25 @@ export function organizationRedirectGuard( return async (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => { const router = inject(Router); const organizationService = inject(OrganizationService); + const accountService = inject(AccountService); - const org = await organizationService.get(route.params.organizationId); + const userId = await firstValueFrom(accountService.activeAccount$.pipe(map((a) => a?.id))); + + if (!userId) { + return router.createUrlTree(["/"]); + } + + const org = await firstValueFrom( + organizationService + .organizations$(userId) + .pipe( + map((organizations) => organizations.find((o) => o.id === route.params.organizationId)), + ), + ); + + if (!org) { + return router.createUrlTree(["/"]); + } if (customRedirect != null) { let redirectPath = customRedirect(org); diff --git a/apps/web/src/app/admin-console/organizations/integrations/integrations.component.ts b/apps/web/src/app/admin-console/organizations/integrations/integrations.component.ts index e3edb41de76..80c12af8522 100644 --- a/apps/web/src/app/admin-console/organizations/integrations/integrations.component.ts +++ b/apps/web/src/app/admin-console/organizations/integrations/integrations.component.ts @@ -4,8 +4,12 @@ import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { Observable, switchMap } from "rxjs"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { IntegrationType } from "@bitwarden/common/enums"; import { HeaderModule } from "../../../layouts/header/header.module"; @@ -34,13 +38,22 @@ export class AdminConsoleIntegrationsComponent implements OnInit { ngOnInit(): void { this.organization$ = this.route.params.pipe( - switchMap((params) => this.organizationService.get$(params.organizationId)), + switchMap((params) => + this.accountService.activeAccount$.pipe( + switchMap((account) => + this.organizationService + .organizations$(account?.id) + .pipe(getOrganizationById(params.organizationId)), + ), + ), + ), ); } constructor( private route: ActivatedRoute, private organizationService: OrganizationService, + private accountService: AccountService, ) { this.integrationsList = [ { 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 8387c53e5e3..c515722fbef 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 @@ -40,7 +40,10 @@ + + {{ "accountDeprovisioningNotification" | i18n }} + + {{ "learnMore" | i18n }} + + ; enterpriseOrganization$: Observable; + showAccountDeprovisioningBanner$: Observable; + protected isBreadcrumbEventLogsEnabled$: Observable; + constructor( private route: ActivatedRoute, private organizationService: OrganizationService, @@ -66,21 +74,40 @@ export class OrganizationLayoutComponent implements OnInit { private configService: ConfigService, private policyService: PolicyService, private providerService: ProviderService, + protected bannerService: AccountDeprovisioningBannerService, + private accountService: AccountService, ) {} async ngOnInit() { + this.isBreadcrumbEventLogsEnabled$ = this.configService.getFeatureFlag$( + FeatureFlag.PM12276_BreadcrumbEventLogs, + ); document.body.classList.remove("layout_frontend"); this.organization$ = this.route.params.pipe( map((p) => p.organizationId), - switchMap((id) => this.organizationService.organizations$.pipe(getById(id))), + withLatestFrom(this.accountService.activeAccount$.pipe(getUserId)), + switchMap(([orgId, userId]) => + this.organizationService.organizations$(userId).pipe(getById(orgId)), + ), filter((org) => org != null), ); - this.canAccessExport$ = combineLatest([ + this.showAccountDeprovisioningBanner$ = combineLatest([ + this.bannerService.showBanner$, + this.configService.getFeatureFlag$(FeatureFlag.AccountDeprovisioningBanner), this.organization$, - this.configService.getFeatureFlag$(FeatureFlag.PM11360RemoveProviderExportPermission), - ]).pipe(map(([org, removeProviderExport]) => org.canAccessExport(removeProviderExport))); + ]).pipe( + map( + ([dismissedOrgs, featureFlagEnabled, organization]) => + organization.productTierType === ProductTierType.Enterprise && + organization.isAdmin && + !dismissedOrgs?.includes(organization.id) && + featureFlagEnabled, + ), + ); + + this.canAccessExport$ = this.organization$.pipe(map((org) => org.canAccessExport)); this.showPaymentAndHistory$ = this.organization$.pipe( map( @@ -106,10 +133,7 @@ export class OrganizationLayoutComponent implements OnInit { ), ); - this.integrationPageEnabled$ = combineLatest( - this.organization$, - this.configService.getFeatureFlag$(FeatureFlag.PM14505AdminConsoleIntegrationPage), - ).pipe(map(([org, featureFlagEnabled]) => featureFlagEnabled && org.canAccessIntegrations)); + this.integrationPageEnabled$ = this.organization$.pipe(map((org) => org.canAccessIntegrations)); this.domainVerificationNavigationTextKey = (await this.configService.getFeatureFlag( FeatureFlag.AccountDeprovisioning, diff --git a/apps/web/src/app/admin-console/organizations/layouts/services/account-deprovisioning-banner.service.spec.ts b/apps/web/src/app/admin-console/organizations/layouts/services/account-deprovisioning-banner.service.spec.ts new file mode 100644 index 00000000000..414828969df --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/layouts/services/account-deprovisioning-banner.service.spec.ts @@ -0,0 +1,75 @@ +import { firstValueFrom } from "rxjs"; + +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { + FakeAccountService, + FakeStateProvider, + mockAccountServiceWith, +} from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; + +import { AccountDeprovisioningBannerService } from "./account-deprovisioning-banner.service"; + +describe("Account Deprovisioning Banner Service", () => { + const userId = Utils.newGuid() as UserId; + let accountService: FakeAccountService; + let stateProvider: FakeStateProvider; + let bannerService: AccountDeprovisioningBannerService; + + beforeEach(async () => { + accountService = mockAccountServiceWith(userId); + stateProvider = new FakeStateProvider(accountService); + bannerService = new AccountDeprovisioningBannerService(stateProvider); + }); + + it("updates state with single org", async () => { + const fakeOrg = new Organization(); + fakeOrg.id = "123"; + + await bannerService.hideBanner(fakeOrg); + const state = await firstValueFrom(bannerService.showBanner$); + + expect(state).toEqual([fakeOrg.id]); + }); + + it("updates state with multiple orgs", async () => { + const fakeOrg1 = new Organization(); + fakeOrg1.id = "123"; + const fakeOrg2 = new Organization(); + fakeOrg2.id = "234"; + const fakeOrg3 = new Organization(); + fakeOrg3.id = "987"; + + await bannerService.hideBanner(fakeOrg1); + await bannerService.hideBanner(fakeOrg2); + await bannerService.hideBanner(fakeOrg3); + + const state = await firstValueFrom(bannerService.showBanner$); + + expect(state).toContain(fakeOrg1.id); + expect(state).toContain(fakeOrg2.id); + expect(state).toContain(fakeOrg3.id); + }); + + it("does not add the same org id multiple times", async () => { + const fakeOrg = new Organization(); + fakeOrg.id = "123"; + + await bannerService.hideBanner(fakeOrg); + await bannerService.hideBanner(fakeOrg); + + const state = await firstValueFrom(bannerService.showBanner$); + + expect(state).toEqual([fakeOrg.id]); + }); + + it("does not add null to the state", async () => { + await bannerService.hideBanner(null as unknown as Organization); + await bannerService.hideBanner(undefined as unknown as Organization); + + const state = await firstValueFrom(bannerService.showBanner$); + + expect(state).toBeNull(); + }); +}); diff --git a/apps/web/src/app/admin-console/organizations/layouts/services/account-deprovisioning-banner.service.ts b/apps/web/src/app/admin-console/organizations/layouts/services/account-deprovisioning-banner.service.ts new file mode 100644 index 00000000000..86a6b7df3e2 --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/layouts/services/account-deprovisioning-banner.service.ts @@ -0,0 +1,40 @@ +import { Injectable } from "@angular/core"; + +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { + ACCOUNT_DEPROVISIONING_BANNER_DISK, + StateProvider, + UserKeyDefinition, +} from "@bitwarden/common/platform/state"; + +export const SHOW_BANNER_KEY = new UserKeyDefinition( + ACCOUNT_DEPROVISIONING_BANNER_DISK, + "accountDeprovisioningBanner", + { + deserializer: (b) => b, + clearOn: [], + }, +); + +@Injectable({ providedIn: "root" }) +export class AccountDeprovisioningBannerService { + private _showBanner = this.stateProvider.getActive(SHOW_BANNER_KEY); + + showBanner$ = this._showBanner.state$; + + constructor(private stateProvider: StateProvider) {} + + async hideBanner(organization: Organization) { + await this._showBanner.update((state) => { + if (!organization) { + return state; + } + if (!state) { + return [organization.id]; + } else if (!state.includes(organization.id)) { + return [...state, organization.id]; + } + return state; + }); + } +} diff --git a/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts b/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts index 1ba1431cbd5..68ba5830c35 100644 --- a/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts @@ -1,8 +1,10 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { DIALOG_DATA, DialogConfig } from "@angular/cdk/dialog"; -import { Component, Inject, OnInit } from "@angular/core"; +import { Component, Inject, OnDestroy, OnInit } from "@angular/core"; import { FormBuilder } from "@angular/forms"; +import { ActivatedRoute, Router } from "@angular/router"; +import { firstValueFrom, switchMap } from "rxjs"; import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe"; @@ -12,7 +14,6 @@ import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { EventView } from "@bitwarden/common/models/view/event.view"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { DialogService, TableDataSource, ToastService } from "@bitwarden/components"; @@ -34,7 +35,7 @@ export interface EntityEventsDialogParams { templateUrl: "entity-events.component.html", standalone: true, }) -export class EntityEventsComponent implements OnInit { +export class EntityEventsComponent implements OnInit, OnDestroy { loading = true; continuationToken: string; protected dataSource = new TableDataSource(); @@ -59,13 +60,14 @@ export class EntityEventsComponent implements OnInit { private apiService: ApiService, private i18nService: I18nService, private eventService: EventService, - private platformUtilsService: PlatformUtilsService, private userNamePipe: UserNamePipe, private logService: LogService, private organizationUserApiService: OrganizationUserApiService, private formBuilder: FormBuilder, private validationService: ValidationService, private toastService: ToastService, + private router: Router, + private activeRoute: ActivatedRoute, ) {} async ngOnInit() { @@ -77,6 +79,21 @@ export class EntityEventsComponent implements OnInit { await this.load(); } + async ngOnDestroy() { + await firstValueFrom( + this.activeRoute.queryParams.pipe( + switchMap(async (params) => { + await this.router.navigate([], { + queryParams: { + ...params, + viewEvents: null, + }, + }); + }), + ), + ); + } + async load() { try { if (this.showUser) { @@ -113,6 +130,8 @@ export class EntityEventsComponent implements OnInit { this.filterFormGroup.value.start, this.filterFormGroup.value.end, ); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.toastService.showToast({ variant: "error", diff --git a/apps/web/src/app/admin-console/organizations/manage/events.component.html b/apps/web/src/app/admin-console/organizations/manage/events.component.html index 654020ec263..4a48ae1ead7 100644 --- a/apps/web/src/app/admin-console/organizations/manage/events.component.html +++ b/apps/web/src/app/admin-console/organizations/manage/events.component.html @@ -1,5 +1,9 @@ - - +@let usePlaceHolderEvents = !organization?.useEvents && (isBreadcrumbEventLogsEnabled$ | async); + + + {{ "upgrade" | i18n }} + +
    @@ -31,6 +35,7 @@ bitFormButton buttonType="primary" [bitAction]="refreshEvents" + [disabled]="usePlaceHolderEvents" > {{ "update" | i18n }} @@ -42,7 +47,7 @@ bitButton bitFormButton [bitAction]="exportEvents" - [disabled]="dirtyDates" + [disabled]="dirtyDates || usePlaceHolderEvents" > {{ "export" | i18n }} @@ -50,6 +55,13 @@
    + + {{ "upgradeEventLogMessage" | i18n }} + {{ "loading" | i18n }} -

    {{ "noEventsInList" | i18n }}

    - + @let displayedEvents = organization?.useEvents ? events : placeholderEvents; + +

    {{ "noEventsInList" | i18n }}

    + {{ "timestamp" | i18n }} @@ -70,8 +84,10 @@ - - {{ e.date | date: "medium" }} + + + {{ i > 4 && usePlaceHolderEvents ? "******" : (e.date | date: "medium") }} + {{ e.appName }} @@ -92,3 +108,26 @@ {{ "loadMore" | i18n }}
    + + +
    +
    + + +

    + {{ "limitedEventLogs" | i18n: ProductTierType[organization?.productTierType] }} +

    +

    + {{ "upgradeForFullEvents" | i18n }} +

    + + +
    +
    +
    diff --git a/apps/web/src/app/admin-console/organizations/manage/events.component.ts b/apps/web/src/app/admin-console/organizations/manage/events.component.ts index ab7306d9140..737a38ee2ab 100644 --- a/apps/web/src/app/admin-console/organizations/manage/events.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/events.component.ts @@ -2,26 +2,43 @@ // @ts-strict-ignore import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; -import { concatMap, Subject, takeUntil } from "rxjs"; +import { concatMap, firstValueFrom, lastValueFrom, Subject, takeUntil } from "rxjs"; import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; +import { + getOrganizationById, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { ProductTierType } from "@bitwarden/common/billing/enums"; +import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response"; import { EventSystemUser } from "@bitwarden/common/enums"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { EventResponse } from "@bitwarden/common/models/response/event.response"; +import { EventView } from "@bitwarden/common/models/view/event.view"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { ToastService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; +import { + ChangePlanDialogResultType, + openChangePlanDialog, +} from "../../../billing/organizations/change-plan-dialog.component"; import { EventService } from "../../../core"; import { EventExportService } from "../../../tools/event-export"; import { BaseEventsComponent } from "../../common/base.events.component"; +import { placeholderEvents } from "./placeholder-events"; + const EVENT_SYSTEM_USER_TO_TRANSLATION: Record = { [EventSystemUser.SCIM]: null, // SCIM acronym not able to be translated so just display SCIM [EventSystemUser.DomainVerification]: "domainVerification", @@ -36,10 +53,19 @@ export class EventsComponent extends BaseEventsComponent implements OnInit, OnDe exportFileName = "org-events"; organizationId: string; organization: Organization; + organizationSubscription: OrganizationSubscriptionResponse; + + placeholderEvents = placeholderEvents as EventView[]; private orgUsersUserIdMap = new Map(); private destroy$ = new Subject(); + readonly ProductTierType = ProductTierType; + + protected isBreadcrumbEventLogsEnabled$ = this.configService.getFeatureFlag$( + FeatureFlag.PM12276_BreadcrumbEventLogs, + ); + constructor( private apiService: ApiService, private route: ActivatedRoute, @@ -52,9 +78,13 @@ export class EventsComponent extends BaseEventsComponent implements OnInit, OnDe private userNamePipe: UserNamePipe, private organizationService: OrganizationService, private organizationUserApiService: OrganizationUserApiService, + private organizationApiService: OrganizationApiServiceAbstraction, private providerService: ProviderService, fileDownloadService: FileDownloadService, toastService: ToastService, + private accountService: AccountService, + private dialogService: DialogService, + private configService: ConfigService, ) { super( eventService, @@ -68,15 +98,26 @@ export class EventsComponent extends BaseEventsComponent implements OnInit, OnDe } async ngOnInit() { + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); this.route.params .pipe( concatMap(async (params) => { this.organizationId = params.organizationId; - this.organization = await this.organizationService.get(this.organizationId); - if (this.organization == null || !this.organization.useEvents) { - await this.router.navigate(["/organizations", this.organizationId]); - return; + this.organization = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(this.organizationId)), + ); + + if (!this.organization.useEvents) { + this.eventsForm.get("start").disable(); + this.eventsForm.get("end").disable(); + + this.organizationSubscription = await this.organizationApiService.getSubscription( + this.organizationId, + ); } + await this.load(); }), takeUntil(this.destroy$), @@ -115,7 +156,6 @@ export class EventsComponent extends BaseEventsComponent implements OnInit, OnDe this.logService.warning(e); } } - await this.refreshEvents(); this.loaded = true; } @@ -175,6 +215,23 @@ export class EventsComponent extends BaseEventsComponent implements OnInit, OnDe return id?.substring(0, 8); } + async changePlan() { + const reference = openChangePlanDialog(this.dialogService, { + data: { + organizationId: this.organizationId, + subscription: this.organizationSubscription, + productTierType: this.organization.productTierType, + }, + }); + + const result = await lastValueFrom(reference.closed); + + if (result === ChangePlanDialogResultType.Closed) { + return; + } + await this.load(); + } + ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); diff --git a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.html b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.html index 3151c303ec9..9fb8b245f72 100644 --- a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.html +++ b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.html @@ -9,7 +9,7 @@
    diff --git a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts index bbb9af67e3a..330ffe86f0b 100644 --- a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts @@ -13,6 +13,7 @@ import { of, shareReplay, Subject, + switchMap, takeUntil, } from "rxjs"; @@ -22,7 +23,10 @@ import { OrganizationUserApiService, } from "@bitwarden/admin-console/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; @@ -97,9 +101,14 @@ export const openGroupAddEditDialog = ( templateUrl: "group-add-edit.component.html", }) export class GroupAddEditComponent implements OnInit, OnDestroy { - private organization$ = this.organizationService - .get$(this.organizationId) - .pipe(shareReplay({ refCount: true })); + private organization$ = this.accountService.activeAccount$.pipe( + switchMap((account) => + this.organizationService + .organizations$(account?.id) + .pipe(getOrganizationById(this.organizationId)) + .pipe(shareReplay({ refCount: true })), + ), + ); protected PermissionMode = PermissionMode; protected ResultType = GroupAddEditDialogResultType; 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 1254d48cc76..82ad2c36b8c 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 @@ -12,7 +12,7 @@ diff --git a/apps/web/src/app/admin-console/organizations/manage/placeholder-events.ts b/apps/web/src/app/admin-console/organizations/manage/placeholder-events.ts new file mode 100644 index 00000000000..3b13ee060bf --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/manage/placeholder-events.ts @@ -0,0 +1,63 @@ +function getRandomDateTime() { + const now = new Date(); + const past24Hours = new Date(now.getTime() - 24 * 60 * 60 * 1000); + const randomTime = + past24Hours.getTime() + Math.random() * (now.getTime() - past24Hours.getTime()); + const randomDate = new Date(randomTime); + + return randomDate.toLocaleString("en-US", { + month: "short", + day: "numeric", + year: "numeric", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + hour12: true, + }); +} + +const asteriskPlaceholders = new Array(6).fill({ + appName: "***", + userName: "**********", + userEmail: "**********", + message: "**********", +}); + +export const placeholderEvents = [ + { + date: getRandomDateTime(), + appName: "Extension - Firefox", + userName: "Alice", + userEmail: "alice@email.com", + message: "Logged in", + }, + { + date: getRandomDateTime(), + appName: "Mobile - iOS", + userName: "Bob", + message: `Viewed item 000000`, + }, + { + date: getRandomDateTime(), + appName: "Desktop - Linux", + userName: "Carlos", + userEmail: "carlos@email.com", + message: "Login attempt failed with incorrect password", + }, + { + date: getRandomDateTime(), + appName: "Web vault - Chrome", + userName: "Ivan", + userEmail: "ivan@email.com", + message: `Confirmed user 000000`, + }, + { + date: getRandomDateTime(), + appName: "Mobile - Android", + userName: "Franz", + userEmail: "franz@email.com", + message: `Sent item 000000 to trash`, + }, +] + .sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()) + .concat(asteriskPlaceholders); diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/base-bulk-confirm.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/base-bulk-confirm.component.ts index 2396292e9af..05e302f011d 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/base-bulk-confirm.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/base-bulk-confirm.component.ts @@ -8,8 +8,8 @@ import { } from "@bitwarden/admin-console/common"; import { ProviderUserBulkPublicKeyResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user-bulk-public-key.response"; import { ProviderUserBulkResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user-bulk.response"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-confirm-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-confirm-dialog.component.ts index af827fa65fd..d3a8b8a2e71 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-confirm-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-confirm-dialog.component.ts @@ -14,8 +14,8 @@ import { import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums"; import { ProviderUserBulkPublicKeyResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user-bulk-public-key.response"; import { ProviderUserBulkResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user-bulk.response"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { StateProvider } from "@bitwarden/common/platform/state"; diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-delete-dialog.component.html b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-delete-dialog.component.html index bb5294ebf02..f666decae81 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-delete-dialog.component.html +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-delete-dialog.component.html @@ -7,9 +7,9 @@ {{ error }} - -

    {{ "deleteManyOrganizationUsersWarningDesc" | i18n }}

    -
    +

    + {{ "deleteManyOrganizationUsersWarningDesc" | i18n }} +

    diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-delete-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-delete-dialog.component.ts index 704a94b0dd3..9d7752cde84 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-delete-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-delete-dialog.component.ts @@ -2,12 +2,17 @@ // @ts-strict-ignore import { DIALOG_DATA, DialogConfig } from "@angular/cdk/dialog"; import { Component, Inject } from "@angular/core"; +import { firstValueFrom } from "rxjs"; import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { DialogService } from "@bitwarden/components"; +import { DeleteManagedMemberWarningService } from "../../services/delete-managed-member/delete-managed-member-warning.service"; + import { BulkUserDetails } from "./bulk-status.component"; type BulkDeleteDialogParams = { @@ -31,12 +36,20 @@ export class BulkDeleteDialogComponent { @Inject(DIALOG_DATA) protected dialogParams: BulkDeleteDialogParams, protected i18nService: I18nService, private organizationUserApiService: OrganizationUserApiService, + private configService: ConfigService, + private deleteManagedMemberWarningService: DeleteManagedMemberWarningService, ) { this.organizationId = dialogParams.organizationId; this.users = dialogParams.users; } async submit() { + if ( + await firstValueFrom(this.configService.getFeatureFlag$(FeatureFlag.AccountDeprovisioning)) + ) { + await this.deleteManagedMemberWarningService.acknowledgeWarning(this.organizationId); + } + try { this.loading = true; this.error = null; diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.html b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.html index d66c43ac50f..4925c210039 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.html +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.html @@ -3,9 +3,9 @@ *ngIf="{ enabled: accountDeprovisioningEnabled$ | async } as accountDeprovisioning" > -

    {{ bulkMemberTitle }}

    + {{ bulkMemberTitle }} -

    {{ bulkTitle }}

    + {{ bulkTitle }}
    @@ -26,61 +26,44 @@ {{ "nonCompliantMembersError" | i18n }} - - -

    {{ "revokeUsersWarning" | i18n }}

    -
    - - - - - {{ "member" | i18n }} - {{ "details" | i18n }} - - - - - -
    -
    - -
    -
    - {{ user.email }} - {{ user.name }} -
    -
    - - - - - - - - {{ "noMasterPassword" | i18n }} - - - - -
    -
    - - - - - {{ "user" | i18n }} - {{ "details" | i18n }} - - - - - - - - + +
    +
    + +
    + +
    + {{ user.name }} + {{ user.email }} - {{ user.name }} + +
    +
    + +
    {{ user.email }}
    +
    +
    +
    + + +

    {{ "revokeUsersWarning" | i18n }}

    + + + + {{ (accountDeprovisioning.enabled ? "member" : "user") | i18n }} + + {{ "details" | i18n }} + + + + + + + - + - @@ -95,55 +78,21 @@ - + - {{ "member" | i18n }} - {{ "status" | i18n }} + + {{ (accountDeprovisioning.enabled ? "member" : "user") | i18n }} + + {{ "status" | i18n }} - -
    -
    - -
    -
    - {{ user.email }} - {{ user.name }} -
    -
    - - - {{ statuses.get(user.id) }} - - - {{ "bulkFilteredMessage" | i18n }} - - -
    -
    - - - - - {{ "member" | i18n }} - {{ "status" | i18n }} - - - - - -
    -
    - -
    -
    - {{ user.email }} - {{ user.name }} -
    -
    + + {{ statuses.get(user.id) }} @@ -157,7 +106,13 @@
    - -
    +
    + > + {{ "remove" | i18n }} + + > + {{ "delete" | i18n }} +
    diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts index 514e7701e4b..ef7c76b3727 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts @@ -22,7 +22,10 @@ import { OrganizationUserApiService, CollectionView, } from "@bitwarden/admin-console/common"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationUserStatusType, OrganizationUserType, @@ -50,8 +53,10 @@ import { convertToSelectionView, PermissionMode, } from "../../../shared/components/access-selector"; +import { DeleteManagedMemberWarningService } from "../../services/delete-managed-member/delete-managed-member-warning.service"; import { commaSeparatedEmails } from "./validators/comma-separated-emails.validator"; +import { inputEmailLimitValidator } from "./validators/input-email-limit.validator"; import { orgSeatLimitReachedValidator } from "./validators/org-seat-limit-reached.validator"; export enum MemberDialogTab { @@ -60,18 +65,28 @@ export enum MemberDialogTab { Collections = 2, } -export interface MemberDialogParams { - name: string; - organizationId: string; - organizationUserId: string; - allOrganizationUserEmails: string[]; - usesKeyConnector: boolean; +interface CommonMemberDialogParams { isOnSecretsManagerStandalone: boolean; - initialTab?: MemberDialogTab; - numConfirmedMembers: number; - managedByOrganization?: boolean; + organizationId: string; } +export interface AddMemberDialogParams extends CommonMemberDialogParams { + kind: "Add"; + occupiedSeatCount: number; + allOrganizationUserEmails: string[]; +} + +export interface EditMemberDialogParams extends CommonMemberDialogParams { + kind: "Edit"; + name: string; + organizationUserId: string; + usesKeyConnector: boolean; + managedByOrganization?: boolean; + initialTab: MemberDialogTab; +} + +export type MemberDialogParams = EditMemberDialogParams | AddMemberDialogParams; + export enum MemberDialogResult { Saved = "saved", Canceled = "canceled", @@ -95,6 +110,7 @@ export class MemberDialogComponent implements OnDestroy { showNoMasterPasswordWarning = false; isOnSecretsManagerStandalone: boolean; remainingSeats$: Observable; + editParams$: Observable; protected organization$: Observable; protected collectionAccessItems: AccessItemView[] = []; @@ -140,6 +156,12 @@ export class MemberDialogComponent implements OnDestroy { return this.formGroup.value.type === OrganizationUserType.Custom; } + isEditDialogParams( + params: EditMemberDialogParams | AddMemberDialogParams, + ): params is EditMemberDialogParams { + return params.kind === "Edit"; + } + constructor( @Inject(DIALOG_DATA) protected params: MemberDialogParams, private dialogRef: DialogRef, @@ -155,14 +177,35 @@ export class MemberDialogComponent implements OnDestroy { organizationService: OrganizationService, private toastService: ToastService, private configService: ConfigService, + private deleteManagedMemberWarningService: DeleteManagedMemberWarningService, ) { - this.organization$ = organizationService - .get$(this.params.organizationId) - .pipe(shareReplay({ refCount: true, bufferSize: 1 })); + this.organization$ = accountService.activeAccount$.pipe( + switchMap((account) => + organizationService + .organizations$(account?.id) + .pipe(getOrganizationById(this.params.organizationId)) + .pipe(shareReplay({ refCount: true, bufferSize: 1 })), + ), + ); + + let userDetails$; + if (this.isEditDialogParams(this.params)) { + this.editMode = true; + this.title = this.i18nService.t("editMember"); + userDetails$ = this.userService.get( + this.params.organizationId, + this.params.organizationUserId, + ); + this.tabIndex = this.params.initialTab; + this.editParams$ = of(this.params); + } else { + this.editMode = false; + this.title = this.i18nService.t("inviteMember"); + this.editParams$ = of(null); + userDetails$ = of(null); + this.tabIndex = MemberDialogTab.Role; + } - this.editMode = this.params.organizationUserId != null; - this.tabIndex = this.params.initialTab ?? MemberDialogTab.Role; - this.title = this.i18nService.t(this.editMode ? "editMember" : "inviteMember"); this.isOnSecretsManagerStandalone = this.params.isOnSecretsManagerStandalone; if (this.isOnSecretsManagerStandalone) { @@ -179,10 +222,6 @@ export class MemberDialogComponent implements OnDestroy { ), ); - const userDetails$ = this.params.organizationUserId - ? this.userService.get(this.params.organizationId, this.params.organizationUserId) - : of(null); - this.allowAdminAccessToAllCollectionItems$ = this.organization$.pipe( map((organization) => { return organization.allowAdminAccessToAllCollectionItems; @@ -263,18 +302,32 @@ export class MemberDialogComponent implements OnDestroy { }); this.remainingSeats$ = this.organization$.pipe( - map((organization) => organization.seats - this.params.numConfirmedMembers), + map((organization) => { + if (!this.isEditDialogParams(this.params)) { + return organization.seats - this.params.occupiedSeatCount; + } + + return organization.seats; + }), ); } private setFormValidators(organization: Organization) { + if (this.isEditDialogParams(this.params)) { + return; + } + const emailsControlValidators = [ Validators.required, commaSeparatedEmails, + inputEmailLimitValidator(organization, (maxEmailsCount: number) => + this.i18nService.t("tooManyEmails", maxEmailsCount), + ), orgSeatLimitReachedValidator( organization, this.params.allOrganizationUserEmails, this.i18nService.t("subscriptionUpgrade", organization.seats), + this.params.occupiedSeatCount, ), ]; @@ -425,14 +478,25 @@ export class MemberDialogComponent implements OnDestroy { return; } + const userView = await this.getUserView(); + + if (this.isEditDialogParams(this.params)) { + await this.handleEditUser(userView, this.params); + } else { + await this.handleInviteUsers(userView, organization); + } + }; + + private async getUserView(): Promise { const userView = new OrganizationUserAdminView(); - userView.id = this.params.organizationUserId; userView.organizationId = this.params.organizationId; userView.type = this.formGroup.value.type; + userView.permissions = this.setRequestPermissions( userView.permissions ?? new PermissionsApi(), userView.type !== OrganizationUserType.Custom, ); + userView.collections = this.formGroup.value.access .filter((v) => v.type === AccessItemType.Collection) .map(convertToSelectionView); @@ -443,44 +507,40 @@ export class MemberDialogComponent implements OnDestroy { userView.accessSecretsManager = this.formGroup.value.accessSecretsManager; - if (this.editMode) { - await this.userService.save(userView); - } else { - userView.id = this.params.organizationUserId; - const maxEmailsCount = - organization.productTierType === ProductTierType.TeamsStarter ? 10 : 20; - const emails = [...new Set(this.formGroup.value.emails.trim().split(/\s*,\s*/))]; - if (emails.length > maxEmailsCount) { - this.formGroup.controls.emails.setErrors({ - tooManyEmails: { message: this.i18nService.t("tooManyEmails", maxEmailsCount) }, - }); - return; - } - if ( - organization.hasReseller && - this.params.numConfirmedMembers + emails.length > organization.seats - ) { - this.formGroup.controls.emails.setErrors({ - tooManyEmails: { message: this.i18nService.t("seatLimitReachedContactYourProvider") }, - }); - return; - } - await this.userService.invite(emails, userView); - } + return userView; + } + + private async handleEditUser( + userView: OrganizationUserAdminView, + params: EditMemberDialogParams, + ) { + userView.id = params.organizationUserId; + await this.userService.save(userView); this.toastService.showToast({ variant: "success", title: null, - message: this.i18nService.t( - this.editMode ? "editedUserId" : "invitedUsers", - this.params.name, - ), + message: this.i18nService.t("editedUserId", params.name), + }); + + this.close(MemberDialogResult.Saved); + } + + private async handleInviteUsers(userView: OrganizationUserAdminView, organization: Organization) { + const emails = [...new Set(this.formGroup.value.emails.trim().split(/\s*,\s*/))]; + + await this.userService.invite(emails, userView); + + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("invitedUsers"), }); this.close(MemberDialogResult.Saved); - }; + } remove = async () => { - if (!this.editMode) { + if (!this.isEditDialogParams(this.params)) { return; } @@ -499,7 +559,7 @@ export class MemberDialogComponent implements OnDestroy { } if (this.showNoMasterPasswordWarning) { - confirmed = await this.noMasterPasswordConfirmationDialog(); + confirmed = await this.noMasterPasswordConfirmationDialog(this.params.name); if (!confirmed) { return false; @@ -520,7 +580,7 @@ export class MemberDialogComponent implements OnDestroy { }; revoke = async () => { - if (!this.editMode) { + if (!this.isEditDialogParams(this.params)) { return; } @@ -536,7 +596,7 @@ export class MemberDialogComponent implements OnDestroy { } if (this.showNoMasterPasswordWarning) { - confirmed = await this.noMasterPasswordConfirmationDialog(); + confirmed = await this.noMasterPasswordConfirmationDialog(this.params.name); if (!confirmed) { return false; @@ -558,7 +618,7 @@ export class MemberDialogComponent implements OnDestroy { }; restore = async () => { - if (!this.editMode) { + if (!this.isEditDialogParams(this.params)) { return; } @@ -577,10 +637,31 @@ export class MemberDialogComponent implements OnDestroy { }; delete = async () => { - if (!this.editMode) { + if (!this.isEditDialogParams(this.params)) { return; } + const showWarningDialog = combineLatest([ + this.organization$, + this.deleteManagedMemberWarningService.warningAcknowledged(this.params.organizationId), + this.accountDeprovisioningEnabled$, + ]).pipe( + map( + ([organization, acknowledged, featureFlagEnabled]) => + featureFlagEnabled && + organization.canManageUsers && + organization.productTierType === ProductTierType.Enterprise && + !acknowledged, + ), + ); + + if (await firstValueFrom(showWarningDialog)) { + const acknowledged = await this.deleteManagedMemberWarningService.showWarning(); + if (!acknowledged) { + return; + } + } + const confirmed = await this.dialogService.openSimpleDialog({ title: { key: "deleteOrganizationUser", @@ -609,6 +690,10 @@ export class MemberDialogComponent implements OnDestroy { title: null, message: this.i18nService.t("organizationUserDeleted", this.params.name), }); + + if (await firstValueFrom(this.accountDeprovisioningEnabled$)) { + await this.deleteManagedMemberWarningService.acknowledgeWarning(this.params.organizationId); + } this.close(MemberDialogResult.Deleted); }; @@ -625,14 +710,14 @@ export class MemberDialogComponent implements OnDestroy { this.dialogRef.close(result); } - private noMasterPasswordConfirmationDialog() { + private noMasterPasswordConfirmationDialog(username: string) { return this.dialogService.openSimpleDialog({ title: { key: "removeOrgUserNoMasterPasswordTitle", }, content: { key: "removeOrgUserNoMasterPasswordDesc", - placeholders: [this.params.name], + placeholders: [username], }, type: "warning", }); diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/validators/input-email-limit.validator.spec.ts b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/validators/input-email-limit.validator.spec.ts new file mode 100644 index 00000000000..5a9a0e128e7 --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/validators/input-email-limit.validator.spec.ts @@ -0,0 +1,191 @@ +import { AbstractControl, FormControl } from "@angular/forms"; + +import { OrganizationUserType } from "@bitwarden/common/admin-console/enums"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { ProductTierType } from "@bitwarden/common/billing/enums"; + +import { inputEmailLimitValidator } from "./input-email-limit.validator"; + +const orgFactory = (props: Partial = {}) => + Object.assign( + new Organization(), + { + id: "myOrgId", + enabled: true, + type: OrganizationUserType.Admin, + }, + props, + ); + +describe("inputEmailLimitValidator", () => { + const getErrorMessage = (max: number) => `You can only add up to ${max} unique emails.`; + + const createUniqueEmailString = (numberOfEmails: number) => + Array(numberOfEmails) + .fill(null) + .map((_, i) => `email${i}@example.com`) + .join(", "); + + const createIdenticalEmailString = (numberOfEmails: number) => + Array(numberOfEmails) + .fill(null) + .map(() => `email@example.com`) + .join(", "); + + describe("TeamsStarter limit validation", () => { + let teamsStarterOrganization: Organization; + + beforeEach(() => { + teamsStarterOrganization = orgFactory({ + productTierType: ProductTierType.TeamsStarter, + seats: 10, + }); + }); + + it("should return null if unique email count is within the limit", () => { + // Arrange + const control = new FormControl(createUniqueEmailString(3)); + + const validatorFn = inputEmailLimitValidator(teamsStarterOrganization, getErrorMessage); + + // Act + const result = validatorFn(control); + + // Assert + expect(result).toBeNull(); + }); + + it("should return null if unique email count is equal the limit", () => { + // Arrange + const control = new FormControl(createUniqueEmailString(10)); + + const validatorFn = inputEmailLimitValidator(teamsStarterOrganization, getErrorMessage); + + // Act + const result = validatorFn(control); + + // Assert + expect(result).toBeNull(); + }); + + it("should return an error if unique email count exceeds the limit", () => { + // Arrange + const control = new FormControl(createUniqueEmailString(11)); + + const validatorFn = inputEmailLimitValidator(teamsStarterOrganization, getErrorMessage); + + // Act + const result = validatorFn(control); + + // Assert + expect(result).toEqual({ + tooManyEmails: { message: "You can only add up to 10 unique emails." }, + }); + }); + }); + + describe("Non-TeamsStarter limit validation", () => { + let nonTeamsStarterOrganization: Organization; + + beforeEach(() => { + nonTeamsStarterOrganization = orgFactory({ + productTierType: ProductTierType.Enterprise, + seats: 100, + }); + }); + + it("should return null if unique email count is within the limit", () => { + // Arrange + const control = new FormControl(createUniqueEmailString(3)); + + const validatorFn = inputEmailLimitValidator(nonTeamsStarterOrganization, getErrorMessage); + + // Act + const result = validatorFn(control); + + // Assert + expect(result).toBeNull(); + }); + + it("should return null if unique email count is equal the limit", () => { + // Arrange + const control = new FormControl(createUniqueEmailString(10)); + + const validatorFn = inputEmailLimitValidator(nonTeamsStarterOrganization, getErrorMessage); + + // Act + const result = validatorFn(control); + + // Assert + expect(result).toBeNull(); + }); + + it("should return an error if unique email count exceeds the limit", () => { + // Arrange + + const control = new FormControl(createUniqueEmailString(21)); + + const validatorFn = inputEmailLimitValidator(nonTeamsStarterOrganization, getErrorMessage); + + // Act + const result = validatorFn(control); + + // Assert + expect(result).toEqual({ + tooManyEmails: { message: "You can only add up to 20 unique emails." }, + }); + }); + }); + + describe("input email validation", () => { + let organization: Organization; + + beforeEach(() => { + organization = orgFactory({ + productTierType: ProductTierType.Enterprise, + seats: 100, + }); + }); + + it("should ignore duplicate emails and validate only unique ones", () => { + // Arrange + const sixUniqueEmails = createUniqueEmailString(6); + const sixDuplicateEmails = createIdenticalEmailString(6); + + const control = new FormControl(sixUniqueEmails + sixDuplicateEmails); + const validatorFn = inputEmailLimitValidator(organization, getErrorMessage); + + // Act + const result = validatorFn(control); + + // Assert + expect(result).toBeNull(); + }); + + it("should return null if input is null", () => { + // Arrange + const control: AbstractControl = new FormControl(null); + + const validatorFn = inputEmailLimitValidator(organization, getErrorMessage); + + // Act + const result = validatorFn(control); + + // Assert + expect(result).toBeNull(); + }); + + it("should return null if input is empty", () => { + // Arrange + const control: AbstractControl = new FormControl(""); + + const validatorFn = inputEmailLimitValidator(organization, getErrorMessage); + + // Act + const result = validatorFn(control); + + // Assert + expect(result).toBeNull(); + }); + }); +}); diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/validators/input-email-limit.validator.ts b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/validators/input-email-limit.validator.ts new file mode 100644 index 00000000000..f34c2e909aa --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/validators/input-email-limit.validator.ts @@ -0,0 +1,40 @@ +import { AbstractControl, ValidationErrors, ValidatorFn } from "@angular/forms"; + +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { ProductTierType } from "@bitwarden/common/billing/enums"; + +function getUniqueInputEmails(control: AbstractControl): string[] { + const emails: string[] = control.value + .split(",") + .filter((email: string) => email && email.trim() !== ""); + const uniqueEmails: string[] = Array.from(new Set(emails)); + + return uniqueEmails; +} + +/** + * Ensure the number of unique emails in an input does not exceed the allowed maximum. + * @param organization An object representing the organization + * @param getErrorMessage A callback function that generates the error message. It takes the `maxEmailsCount` as a parameter. + * @returns A function that validates an `AbstractControl` and returns `ValidationErrors` or `null` + */ +export function inputEmailLimitValidator( + organization: Organization, + getErrorMessage: (maxEmailsCount: number) => string, +): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + if (!control.value?.trim()) { + return null; + } + + const maxEmailsCount = organization.productTierType === ProductTierType.TeamsStarter ? 10 : 20; + + const uniqueEmails = getUniqueInputEmails(control); + + if (uniqueEmails.length <= maxEmailsCount) { + return null; + } + + return { tooManyEmails: { message: getErrorMessage(maxEmailsCount) } }; + }; +} diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/validators/org-seat-limit-reached.validator.spec.ts b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/validators/org-seat-limit-reached.validator.spec.ts index 6c693ee8f84..a97623d7a38 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/validators/org-seat-limit-reached.validator.spec.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/validators/org-seat-limit-reached.validator.spec.ts @@ -1,10 +1,14 @@ -import { AbstractControl, FormControl, ValidationErrors } from "@angular/forms"; +import { FormControl } from "@angular/forms"; import { OrganizationUserType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { ProductTierType } from "@bitwarden/common/billing/enums"; -import { orgSeatLimitReachedValidator } from "./org-seat-limit-reached.validator"; +import { + orgSeatLimitReachedValidator, + isFixedSeatPlan, + isDynamicSeatPlan, +} from "./org-seat-limit-reached.validator"; const orgFactory = (props: Partial = {}) => Object.assign( @@ -17,20 +21,35 @@ const orgFactory = (props: Partial = {}) => props, ); +const createUniqueEmailString = (numberOfEmails: number) => + Array(numberOfEmails) + .fill(null) + .map((_, i) => `email${i}@example.com`) + .join(", "); + +const createIdenticalEmailString = (numberOfEmails: number) => + Array(numberOfEmails) + .fill(null) + .map(() => `email@example.com`) + .join(", "); + describe("orgSeatLimitReachedValidator", () => { let organization: Organization; let allOrganizationUserEmails: string[]; - let validatorFn: (control: AbstractControl) => ValidationErrors | null; + let occupiedSeatCount: number; beforeEach(() => { - allOrganizationUserEmails = ["user1@example.com"]; + allOrganizationUserEmails = [createUniqueEmailString(1)]; + occupiedSeatCount = 1; + organization = null; }); it("should return null when control value is empty", () => { - validatorFn = orgSeatLimitReachedValidator( + const validatorFn = orgSeatLimitReachedValidator( organization, allOrganizationUserEmails, "You cannot invite more than 2 members without upgrading your plan.", + occupiedSeatCount, ); const control = new FormControl(""); @@ -40,10 +59,11 @@ describe("orgSeatLimitReachedValidator", () => { }); it("should return null when control value is null", () => { - validatorFn = orgSeatLimitReachedValidator( + const validatorFn = orgSeatLimitReachedValidator( organization, allOrganizationUserEmails, "You cannot invite more than 2 members without upgrading your plan.", + occupiedSeatCount, ); const control = new FormControl(null); @@ -52,82 +72,123 @@ describe("orgSeatLimitReachedValidator", () => { expect(result).toBeNull(); }); - it("should return null when max seats are not exceeded on free plan", () => { - organization = orgFactory({ - productTierType: ProductTierType.Free, - seats: 2, - }); - validatorFn = orgSeatLimitReachedValidator( - organization, - allOrganizationUserEmails, - "You cannot invite more than 2 members without upgrading your plan.", - ); - const control = new FormControl("user2@example.com"); - - const result = validatorFn(control); - - expect(result).toBeNull(); - }); - - it("should return null when max seats are not exceeded on teams starter plan", () => { - organization = orgFactory({ - productTierType: ProductTierType.TeamsStarter, - seats: 10, - }); - validatorFn = orgSeatLimitReachedValidator( - organization, - allOrganizationUserEmails, - "You cannot invite more than 10 members without upgrading your plan.", - ); - const control = new FormControl( - "user2@example.com," + - "user3@example.com," + - "user4@example.com," + - "user5@example.com," + - "user6@example.com," + - "user7@example.com," + - "user8@example.com," + - "user9@example.com," + - "user10@example.com", - ); - - const result = validatorFn(control); - - expect(result).toBeNull(); - }); - - it("should return validation error when max seats are exceeded on free plan", () => { - organization = orgFactory({ - productTierType: ProductTierType.Free, - seats: 2, - }); - const errorMessage = "You cannot invite more than 2 members without upgrading your plan."; - validatorFn = orgSeatLimitReachedValidator( - organization, - allOrganizationUserEmails, - "You cannot invite more than 2 members without upgrading your plan.", - ); - const control = new FormControl("user2@example.com,user3@example.com"); - - const result = validatorFn(control); - - expect(result).toStrictEqual({ seatLimitReached: { message: errorMessage } }); - }); - - it("should return null when not on free plan", () => { - const control = new FormControl("user2@example.com,user3@example.com"); - organization = orgFactory({ + it("should return null when on dynamic seat plan", () => { + const control = new FormControl(createUniqueEmailString(1)); + const organization = orgFactory({ productTierType: ProductTierType.Enterprise, seats: 100, }); - validatorFn = orgSeatLimitReachedValidator( + + const validatorFn = orgSeatLimitReachedValidator( organization, allOrganizationUserEmails, - "You cannot invite more than 2 members without upgrading your plan.", + "Enterprise plan dummy error.", + occupiedSeatCount, ); const result = validatorFn(control); expect(result).toBeNull(); }); + + it("should only count unique input email addresses", () => { + const twoUniqueEmails = createUniqueEmailString(2); + const sixDuplicateEmails = createIdenticalEmailString(6); + const control = new FormControl(twoUniqueEmails + sixDuplicateEmails); + const organization = orgFactory({ + productTierType: ProductTierType.Families, + seats: 6, + }); + + const occupiedSeatCount = 3; + const validatorFn = orgSeatLimitReachedValidator( + organization, + allOrganizationUserEmails, + "Family plan dummy error.", + occupiedSeatCount, + ); + + const result = validatorFn(control); + + expect(result).toBeNull(); + }); + + describe("when total occupied seat count is below plan's max count", () => { + test.each([ + [ProductTierType.Free, 2], + [ProductTierType.Families, 6], + [ProductTierType.TeamsStarter, 10], + ])(`should return null on plan %s`, (plan, planSeatCount) => { + const organization = orgFactory({ + productTierType: plan, + seats: planSeatCount, + }); + + const occupiedSeatCount = 0; + + const validatorFn = orgSeatLimitReachedValidator( + organization, + allOrganizationUserEmails, + "Generic error message", + occupiedSeatCount, + ); + + const control = new FormControl(createUniqueEmailString(1)); + + const result = validatorFn(control); + + expect(result).toBeNull(); + }); + }); + + describe("when total occupied seat count is at plan's max count", () => { + test.each([ + [ProductTierType.Free, 2, 1], + [ProductTierType.Families, 6, 5], + [ProductTierType.TeamsStarter, 10, 9], + ])(`should return null on plan %s`, (plan, planSeatCount, newEmailCount) => { + const organization = orgFactory({ + productTierType: plan, + seats: planSeatCount, + }); + + const occupiedSeatCount = 1; + + const validatorFn = orgSeatLimitReachedValidator( + organization, + allOrganizationUserEmails, + "Generic error message", + occupiedSeatCount, + ); + + const control = new FormControl(createUniqueEmailString(newEmailCount)); + + const result = validatorFn(control); + + expect(result).toBeNull(); + }); + }); +}); + +describe("isFixedSeatPlan", () => { + test.each([ + [true, ProductTierType.Free], + [true, ProductTierType.Families], + [true, ProductTierType.TeamsStarter], + [false, ProductTierType.Enterprise], + ])("should return %s for %s", (expected, input) => { + expect(isFixedSeatPlan(input)).toBe(expected); + }); +}); + +describe("isDynamicSeatPlan", () => { + test.each([ + [true, ProductTierType.Enterprise], + [true, ProductTierType.Teams], + [false, ProductTierType.Free], + [false, ProductTierType.Families], + [false, ProductTierType.TeamsStarter], + ])("should return %s for %s", (expected, input) => { + expect(isDynamicSeatPlan(input)).toBe(expected); + }); }); diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/validators/org-seat-limit-reached.validator.ts b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/validators/org-seat-limit-reached.validator.ts index bcd84743918..1990bf7e32f 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/validators/org-seat-limit-reached.validator.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/validators/org-seat-limit-reached.validator.ts @@ -9,41 +9,68 @@ import { ProductTierType } from "@bitwarden/common/billing/enums"; * @param organization An object representing the organization * @param allOrganizationUserEmails An array of strings with existing user email addresses * @param errorMessage A localized string to display if validation fails + * @param occupiedSeatCount The current count of active users occupying the organization's seats. * @returns A function that validates an `AbstractControl` and returns `ValidationErrors` or `null` */ export function orgSeatLimitReachedValidator( organization: Organization, allOrganizationUserEmails: string[], errorMessage: string, + occupiedSeatCount: number, ): ValidatorFn { return (control: AbstractControl): ValidationErrors | null => { - if (control.value === "" || !control.value) { + if (!control.value?.trim()) { return null; } - const newEmailsToAdd = Array.from( - new Set( - control.value - .split(",") - .filter( - (newEmailToAdd: string) => - newEmailToAdd && - newEmailToAdd.trim() !== "" && - !allOrganizationUserEmails.some( - (existingEmail) => existingEmail === newEmailToAdd.trim(), - ), - ), - ), - ); + if (isDynamicSeatPlan(organization.productTierType)) { + return null; + } - const productHasAdditionalSeatsOption = - organization.productTierType !== ProductTierType.Free && - organization.productTierType !== ProductTierType.Families && - organization.productTierType !== ProductTierType.TeamsStarter; + const newTotalUserCount = + occupiedSeatCount + getUniqueNewEmailCount(allOrganizationUserEmails, control); - return !productHasAdditionalSeatsOption && - allOrganizationUserEmails.length + newEmailsToAdd.length > organization.seats - ? { seatLimitReached: { message: errorMessage } } - : null; + if (newTotalUserCount > organization.seats) { + return { seatLimitReached: { message: errorMessage } }; + } + + return null; }; } + +export function isDynamicSeatPlan(productTierType: ProductTierType): boolean { + return !isFixedSeatPlan(productTierType); +} + +export function isFixedSeatPlan(productTierType: ProductTierType): boolean { + switch (productTierType) { + case ProductTierType.Free: + case ProductTierType.Families: + case ProductTierType.TeamsStarter: + return true; + default: + return false; + } +} + +function getUniqueNewEmailCount( + allOrganizationUserEmails: string[], + control: AbstractControl, +): number { + const newEmailsToAdd = Array.from( + new Set( + control.value + .split(",") + .filter( + (newEmailToAdd: string) => + newEmailToAdd && + newEmailToAdd.trim() !== "" && + !allOrganizationUserEmails.some( + (existingEmail) => existingEmail === newEmailToAdd.trim(), + ), + ), + ), + ); + + return newEmailsToAdd.length; +} diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.html b/apps/web/src/app/admin-console/organizations/members/members.component.html index 52315d30177..3856f6f4e28 100644 --- a/apps/web/src/app/admin-console/organizations/members/members.component.html +++ b/apps/web/src/app/admin-console/organizations/members/members.component.html @@ -48,7 +48,7 @@
    @@ -187,7 +187,7 @@ class="tw-mr-3" >
    -
    +
    @@ -196,22 +196,25 @@ class="tw-text-xs" variant="secondary" *ngIf="u.status === userStatusType.Invited" - >{{ "invited" | i18n }} + {{ "invited" | i18n }} + {{ "needsConfirmation" | i18n }} + {{ "needsConfirmation" | i18n }} + {{ "revoked" | i18n }} + {{ "revoked" | i18n }} +
    {{ u.email }} diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.ts b/apps/web/src/app/admin-console/organizations/members/members.component.ts index 3d10a4a07b3..0bfdde8fc97 100644 --- a/apps/web/src/app/admin-console/organizations/members/members.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/members.component.ts @@ -25,10 +25,12 @@ import { CollectionDetailsResponse, } from "@bitwarden/admin-console/common"; import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe"; -import { ModalService } from "@bitwarden/angular/services/modal.service"; 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 { + getOrganizationById, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service"; import { PolicyApiServiceAbstraction as PolicyApiService } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; @@ -40,11 +42,12 @@ import { import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; import { isNotSelfUpgradable, ProductTierType } from "@bitwarden/common/billing/enums"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; @@ -73,10 +76,12 @@ import { MemberDialogTab, openUserAddEditDialog, } from "./components/member-dialog"; +import { isFixedSeatPlan } from "./components/member-dialog/validators/org-seat-limit-reached.validator"; import { ResetPasswordComponent, ResetPasswordDialogResult, } from "./components/reset-password.component"; +import { DeleteManagedMemberWarningService } from "./services/delete-managed-member/delete-managed-member-warning.service"; class MembersTableDataSource extends PeopleTableDataSource { protected statusType = OrganizationUserStatusType; @@ -106,6 +111,10 @@ export class MembersComponent extends BaseMembersComponent protected rowHeight = 69; protected rowHeightClass = `tw-h-[69px]`; + get occupiedSeatCount(): number { + return this.dataSource.activeUserCount; + } + constructor( apiService: ApiService, i18nService: I18nService, @@ -122,14 +131,15 @@ export class MembersComponent extends BaseMembersComponent private route: ActivatedRoute, private syncService: SyncService, private organizationService: OrganizationService, + private accountService: AccountService, private organizationApiService: OrganizationApiServiceAbstraction, private organizationUserApiService: OrganizationUserApiService, private router: Router, private groupService: GroupApiService, private collectionService: CollectionService, private billingApiService: BillingApiServiceAbstraction, - private modalService: ModalService, private configService: ConfigService, + protected deleteManagedMemberWarningService: DeleteManagedMemberWarningService, ) { super( apiService, @@ -144,7 +154,15 @@ export class MembersComponent extends BaseMembersComponent ); const organization$ = this.route.params.pipe( - concatMap((params) => this.organizationService.get$(params.organizationId)), + concatMap((params) => + this.accountService.activeAccount$.pipe( + switchMap((account) => + this.organizationService + .organizations$(account?.id) + .pipe(getOrganizationById(params.organizationId)), + ), + ), + ), shareReplay({ refCount: true, bufferSize: 1 }), ); @@ -464,63 +482,79 @@ export class MembersComponent extends BaseMembersComponent firstValueFrom(simpleDialog.closed).then(this.handleDialogClose.bind(this)); } - async edit(user: OrganizationUserView, initialTab: MemberDialogTab = MemberDialogTab.Role) { - if ( - !user && - this.organization.hasReseller && - this.organization.seats === this.dataSource.confirmedUserCount - ) { + private async handleInviteDialog() { + const dialog = openUserAddEditDialog(this.dialogService, { + data: { + kind: "Add", + organizationId: this.organization.id, + allOrganizationUserEmails: this.dataSource.data?.map((user) => user.email) ?? [], + occupiedSeatCount: this.occupiedSeatCount, + isOnSecretsManagerStandalone: this.orgIsOnSecretsManagerStandalone, + }, + }); + + const result = await lastValueFrom(dialog.closed); + + if (result === MemberDialogResult.Saved) { + await this.load(); + } + } + + private async handleSeatLimitForFixedTiers() { + if (!this.organization.canEditSubscription) { + await this.showSeatLimitReachedDialog(); + return; + } + + const reference = openChangePlanDialog(this.dialogService, { + data: { + organizationId: this.organization.id, + subscription: null, + productTierType: this.organization.productTierType, + }, + }); + + const result = await lastValueFrom(reference.closed); + + if (result === ChangePlanDialogResultType.Submitted) { + await this.load(); + } + } + + async invite() { + if (this.organization.hasReseller && this.organization.seats === this.occupiedSeatCount) { this.toastService.showToast({ variant: "error", title: this.i18nService.t("seatLimitReached"), message: this.i18nService.t("contactYourProvider"), }); + return; } - // Invite User: Add Flow - // Click on user email: Edit Flow - - // User attempting to invite new users in a free org with max users if ( - !user && - this.dataSource.data.length === this.organization.seats && - (this.organization.productTierType === ProductTierType.Free || - this.organization.productTierType === ProductTierType.TeamsStarter || - this.organization.productTierType === ProductTierType.Families) + this.occupiedSeatCount === this.organization.seats && + isFixedSeatPlan(this.organization.productTierType) ) { - if (!this.organization.canEditSubscription) { - await this.showSeatLimitReachedDialog(); - return; - } + await this.handleSeatLimitForFixedTiers(); - const reference = openChangePlanDialog(this.dialogService, { - data: { - organizationId: this.organization.id, - subscription: null, - productTierType: this.organization.productTierType, - }, - }); - - const result = await lastValueFrom(reference.closed); - - if (result === ChangePlanDialogResultType.Submitted) { - await this.load(); - } return; } + await this.handleInviteDialog(); + } + + async edit(user: OrganizationUserView, initialTab: MemberDialogTab = MemberDialogTab.Role) { const dialog = openUserAddEditDialog(this.dialogService, { data: { + kind: "Edit", name: this.userNamePipe.transform(user), organizationId: this.organization.id, - organizationUserId: user != null ? user.id : null, - allOrganizationUserEmails: this.dataSource.data?.map((user) => user.email) ?? [], - usesKeyConnector: user?.usesKeyConnector, + organizationUserId: user.id, + usesKeyConnector: user.usesKeyConnector, isOnSecretsManagerStandalone: this.orgIsOnSecretsManagerStandalone, initialTab: initialTab, - numConfirmedMembers: this.dataSource.confirmedUserCount, - managedByOrganization: user?.managedByOrganization, + managedByOrganization: user.managedByOrganization, }, }); @@ -532,9 +566,7 @@ export class MembersComponent extends BaseMembersComponent case MemberDialogResult.Saved: case MemberDialogResult.Revoked: case MemberDialogResult.Restored: - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.load(); + await this.load(); break; } } @@ -555,6 +587,23 @@ export class MembersComponent extends BaseMembersComponent } async bulkDelete() { + if (this.accountDeprovisioningEnabled) { + const warningAcknowledged = await firstValueFrom( + this.deleteManagedMemberWarningService.warningAcknowledged(this.organization.id), + ); + + if ( + !warningAcknowledged && + this.organization.canManageUsers && + this.organization.productTierType === ProductTierType.Enterprise + ) { + const acknowledged = await this.deleteManagedMemberWarningService.showWarning(); + if (!acknowledged) { + return; + } + } + } + if (this.actionPromise != null) { return; } @@ -614,9 +663,6 @@ export class MembersComponent extends BaseMembersComponent this.organization.id, filteredUsers.map((user) => user.id), ); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - // Bulk Status component open const dialogRef = BulkStatusComponent.open(this.dialogService, { data: { @@ -744,6 +790,23 @@ export class MembersComponent extends BaseMembersComponent } async deleteUser(user: OrganizationUserView) { + if (this.accountDeprovisioningEnabled) { + const warningAcknowledged = await firstValueFrom( + this.deleteManagedMemberWarningService.warningAcknowledged(this.organization.id), + ); + + if ( + !warningAcknowledged && + this.organization.canManageUsers && + this.organization.productTierType === ProductTierType.Enterprise + ) { + const acknowledged = await this.deleteManagedMemberWarningService.showWarning(); + if (!acknowledged) { + return false; + } + } + } + const confirmed = await this.dialogService.openSimpleDialog({ title: { key: "deleteOrganizationUser", @@ -762,6 +825,10 @@ export class MembersComponent extends BaseMembersComponent return false; } + if (this.accountDeprovisioningEnabled) { + await this.deleteManagedMemberWarningService.acknowledgeWarning(this.organization.id); + } + this.actionPromise = this.organizationUserApiService.deleteOrganizationUser( this.organization.id, user.id, diff --git a/apps/web/src/app/admin-console/organizations/members/services/delete-managed-member/delete-managed-member-warning.service.spec.ts b/apps/web/src/app/admin-console/organizations/members/services/delete-managed-member/delete-managed-member-warning.service.spec.ts new file mode 100644 index 00000000000..45271f6f78b --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/members/services/delete-managed-member/delete-managed-member-warning.service.spec.ts @@ -0,0 +1,51 @@ +import { MockProxy, mock } from "jest-mock-extended"; +import { firstValueFrom } from "rxjs"; + +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { + FakeAccountService, + FakeStateProvider, + mockAccountServiceWith, +} from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; +import { DialogService } from "@bitwarden/components"; + +import { DeleteManagedMemberWarningService } from "./delete-managed-member-warning.service"; + +describe("Delete managed member warning service", () => { + const userId = Utils.newGuid() as UserId; + let accountService: FakeAccountService; + let stateProvider: FakeStateProvider; + let dialogService: MockProxy; + let warningService: DeleteManagedMemberWarningService; + + beforeEach(() => { + accountService = mockAccountServiceWith(userId); + stateProvider = new FakeStateProvider(accountService); + dialogService = mock(); + warningService = new DeleteManagedMemberWarningService(stateProvider, dialogService); + }); + + it("warningAcknowledged returns false for ids that have not acknowledged the warning", async () => { + const id = Utils.newGuid(); + const acknowledged = await firstValueFrom(warningService.warningAcknowledged(id)); + + expect(acknowledged).toEqual(false); + }); + + it("warningAcknowledged returns true for ids that have acknowledged the warning", async () => { + const id1 = Utils.newGuid(); + const id2 = Utils.newGuid(); + const id3 = Utils.newGuid(); + await warningService.acknowledgeWarning(id1); + await warningService.acknowledgeWarning(id3); + + const acknowledged1 = await firstValueFrom(warningService.warningAcknowledged(id1)); + const acknowledged2 = await firstValueFrom(warningService.warningAcknowledged(id2)); + const acknowledged3 = await firstValueFrom(warningService.warningAcknowledged(id3)); + + expect(acknowledged1).toEqual(true); + expect(acknowledged2).toEqual(false); + expect(acknowledged3).toEqual(true); + }); +}); diff --git a/apps/web/src/app/admin-console/organizations/members/services/delete-managed-member/delete-managed-member-warning.service.ts b/apps/web/src/app/admin-console/organizations/members/services/delete-managed-member/delete-managed-member-warning.service.ts new file mode 100644 index 00000000000..53ca0bbcaf8 --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/members/services/delete-managed-member/delete-managed-member-warning.service.ts @@ -0,0 +1,70 @@ +import { Injectable } from "@angular/core"; +import { map } from "rxjs"; + +import { + DELETE_MANAGED_USER_WARNING, + StateProvider, + UserKeyDefinition, +} from "@bitwarden/common/platform/state"; +import { DialogService } from "@bitwarden/components"; + +export const SHOW_WARNING_KEY = new UserKeyDefinition( + DELETE_MANAGED_USER_WARNING, + "showDeleteManagedUserWarning", + { + deserializer: (b) => b, + clearOn: [], + }, +); + +@Injectable({ providedIn: "root" }) +export class DeleteManagedMemberWarningService { + private _acknowledged = this.stateProvider.getActive(SHOW_WARNING_KEY); + private acknowledgedState$ = this._acknowledged.state$; + + constructor( + private stateProvider: StateProvider, + private dialogService: DialogService, + ) {} + + async acknowledgeWarning(organizationId: string) { + await this._acknowledged.update((state) => { + if (!organizationId) { + return state; + } + if (!state) { + return [organizationId]; + } else if (!state.includes(organizationId)) { + return [...state, organizationId]; + } + return state; + }); + } + + async showWarning() { + const confirmed = await this.dialogService.openSimpleDialog({ + title: { + key: "deleteManagedUserWarning", + }, + content: { + key: "deleteManagedUserWarningDesc", + }, + type: "danger", + icon: "bwi-exclamation-circle", + acceptButtonText: { key: "continue" }, + cancelButtonText: { key: "cancel" }, + }); + + if (!confirmed) { + return false; + } + + return confirmed; + } + + warningAcknowledged(organizationId: string) { + return this.acknowledgedState$.pipe( + map((acknowledgedIds) => acknowledgedIds?.includes(organizationId) ?? false), + ); + } +} diff --git a/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.spec.ts b/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.spec.ts index da71b61892d..eff417ead32 100644 --- a/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.spec.ts +++ b/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.spec.ts @@ -1,6 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { mock, MockProxy } from "jest-mock-extended"; +import { of } from "rxjs"; import { OrganizationUserApiService, @@ -10,7 +11,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { OrganizationKeysResponse } from "@bitwarden/common/admin-console/models/response/organization-keys.response"; import { OrganizationApiService } from "@bitwarden/common/admin-console/services/organization/organization-api.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { EncryptionType } from "@bitwarden/common/platform/enums"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; @@ -164,10 +165,9 @@ describe("OrganizationUserResetPasswordService", () => { describe("getRotatedData", () => { beforeEach(() => { - organizationService.getAll.mockResolvedValue([ - createOrganization("1", "org1"), - createOrganization("2", "org2"), - ]); + organizationService.organizations$.mockReturnValue( + of([createOrganization("1", "org1"), createOrganization("2", "org2")]), + ); organizationApiService.getKeys.mockResolvedValue( new OrganizationKeysResponse({ privateKey: "test-private-key", diff --git a/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts b/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts index 77d5ad29ccd..8e583d3106c 100644 --- a/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts +++ b/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts @@ -1,6 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Injectable } from "@angular/core"; +import { firstValueFrom } from "rxjs"; import { OrganizationUserApiService, @@ -9,7 +10,7 @@ import { } from "@bitwarden/admin-console/common"; 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 { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string"; @@ -154,7 +155,7 @@ export class OrganizationUserResetPasswordService throw new Error("New user key is required for rotation."); } - const allOrgs = await this.organizationService.getAll(); + const allOrgs = await firstValueFrom(this.organizationService.organizations$(userId)); if (!allOrgs) { return; diff --git a/apps/web/src/app/admin-console/organizations/organization-routing.module.ts b/apps/web/src/app/admin-console/organizations/organization-routing.module.ts index f7720598284..e5c68b73546 100644 --- a/apps/web/src/app/admin-console/organizations/organization-routing.module.ts +++ b/apps/web/src/app/admin-console/organizations/organization-routing.module.ts @@ -4,7 +4,6 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; import { authGuard } from "@bitwarden/angular/auth/guards"; -import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard"; import { canAccessOrgAdmin, canAccessGroupsTab, @@ -14,16 +13,15 @@ import { canAccessSettingsTab, } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { organizationPermissionsGuard } from "../../admin-console/organizations/guards/org-permissions.guard"; -import { organizationRedirectGuard } from "../../admin-console/organizations/guards/org-redirect.guard"; -import { OrganizationLayoutComponent } from "../../admin-console/organizations/layouts/organization-layout.component"; import { deepLinkGuard } from "../../auth/guards/deep-link.guard"; -import { VaultModule } from "../../vault/org-vault/vault.module"; +import { VaultModule } from "./collections/vault.module"; import { isEnterpriseOrgGuard } from "./guards/is-enterprise-org.guard"; +import { organizationPermissionsGuard } from "./guards/org-permissions.guard"; +import { organizationRedirectGuard } from "./guards/org-redirect.guard"; import { AdminConsoleIntegrationsComponent } from "./integrations/integrations.component"; +import { OrganizationLayoutComponent } from "./layouts/organization-layout.component"; import { GroupsComponent } from "./manage/groups.component"; const routes: Routes = [ @@ -45,7 +43,6 @@ const routes: Routes = [ { path: "integrations", canActivate: [ - canAccessFeature(FeatureFlag.PM14505AdminConsoleIntegrationPage), isEnterpriseOrgGuard(false), organizationPermissionsGuard(canAccessIntegrations), ], diff --git a/apps/web/src/app/admin-console/organizations/policies/base-policy.component.ts b/apps/web/src/app/admin-console/organizations/policies/base-policy.component.ts index 96d8d8b24a7..01c45264236 100644 --- a/apps/web/src/app/admin-console/organizations/policies/base-policy.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/base-policy.component.ts @@ -35,18 +35,6 @@ export abstract class BasePolicyComponent implements OnInit { } } - loadData() { - this.data.patchValue(this.policyResponse.data ?? {}); - } - - buildRequestData() { - if (this.data != null) { - return this.data.value; - } - - return null; - } - buildRequest() { const request = new PolicyRequest(); request.enabled = this.enabled.value; @@ -55,4 +43,16 @@ export abstract class BasePolicyComponent implements OnInit { return Promise.resolve(request); } + + protected loadData() { + this.data.patchValue(this.policyResponse.data ?? {}); + } + + protected buildRequestData() { + if (this.data != null) { + return this.data.value; + } + + return null; + } } 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 1fb554d55a1..20137105993 100644 --- a/apps/web/src/app/admin-console/organizations/policies/index.ts +++ b/apps/web/src/app/admin-console/organizations/policies/index.ts @@ -10,3 +10,4 @@ export { SendOptionsPolicy } from "./send-options.component"; export { SingleOrgPolicy } from "./single-org.component"; export { TwoFactorAuthenticationPolicy } from "./two-factor-authentication.component"; export { PoliciesComponent } from "./policies.component"; +export { RemoveUnlockWithPinPolicy } from "./remove-unlock-with-pin.component"; diff --git a/apps/web/src/app/admin-console/organizations/policies/master-password.component.ts b/apps/web/src/app/admin-console/organizations/policies/master-password.component.ts index a0001060688..328989df66b 100644 --- a/apps/web/src/app/admin-console/organizations/policies/master-password.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/master-password.component.ts @@ -2,11 +2,17 @@ // @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { firstValueFrom } from "rxjs"; import { ControlsOf } from "@bitwarden/angular/types/controls-of"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -43,6 +49,7 @@ export class MasterPasswordPolicyComponent extends BasePolicyComponent implement private formBuilder: FormBuilder, i18nService: I18nService, private organizationService: OrganizationService, + private accountService: AccountService, ) { super(); @@ -58,7 +65,12 @@ export class MasterPasswordPolicyComponent extends BasePolicyComponent implement async ngOnInit() { super.ngOnInit(); - const organization = await this.organizationService.get(this.policyResponse.organizationId); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + const organization = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(this.policyResponse.organizationId)), + ); this.showKeyConnectorInfo = organization.keyConnectorEnabled; } } diff --git a/apps/web/src/app/admin-console/organizations/policies/password-generator.component.html b/apps/web/src/app/admin-console/organizations/policies/password-generator.component.html index fcf03d27acc..1300acee471 100644 --- a/apps/web/src/app/admin-console/organizations/policies/password-generator.component.html +++ b/apps/web/src/app/admin-console/organizations/policies/password-generator.component.html @@ -68,7 +68,11 @@ - {{ "specialCharactersLabel" | i18n }} + + {{ "!@#$%^&*" }}
    diff --git a/apps/web/src/app/admin-console/organizations/policies/policies.component.html b/apps/web/src/app/admin-console/organizations/policies/policies.component.html index 8f1e925034f..24021bb765f 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policies.component.html +++ b/apps/web/src/app/admin-console/organizations/policies/policies.component.html @@ -12,7 +12,7 @@ - + {{ "on" | i18n diff --git a/apps/web/src/app/admin-console/organizations/policies/policies.component.ts b/apps/web/src/app/admin-console/organizations/policies/policies.component.ts index 9e96215a6c2..52cb4da107a 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policies.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policies.component.ts @@ -2,14 +2,18 @@ // @ts-strict-ignore import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; -import { lastValueFrom } from "rxjs"; -import { first } from "rxjs/operators"; +import { firstValueFrom, lastValueFrom } from "rxjs"; +import { first, map } from "rxjs/operators"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { DialogService } from "@bitwarden/components"; import { PolicyListService } from "../../core/policy-list.service"; @@ -21,7 +25,6 @@ import { PolicyEditComponent, PolicyEditDialogResult } from "./policy-edit.compo selector: "app-org-policies", templateUrl: "policies.component.html", }) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil export class PoliciesComponent implements OnInit { @ViewChild("editTemplate", { read: ViewContainerRef, static: true }) editModalRef: ViewContainerRef; @@ -37,6 +40,7 @@ export class PoliciesComponent implements OnInit { constructor( private route: ActivatedRoute, private organizationService: OrganizationService, + private accountService: AccountService, private policyApiService: PolicyApiServiceAbstraction, private policyListService: PolicyListService, private dialogService: DialogService, @@ -46,7 +50,14 @@ export class PoliciesComponent implements OnInit { // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe this.route.parent.parent.params.subscribe(async (params) => { this.organizationId = params.organizationId; - this.organization = await this.organizationService.get(this.organizationId); + const userId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + this.organization = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(this.organizationId)), + ); this.policies = this.policyListService.getPolicies(); await this.load(); diff --git a/apps/web/src/app/admin-console/organizations/policies/policies.module.ts b/apps/web/src/app/admin-console/organizations/policies/policies.module.ts index c6b1fbe4bff..1b8ec5089f9 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policies.module.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policies.module.ts @@ -8,6 +8,7 @@ import { PasswordGeneratorPolicyComponent } from "./password-generator.component import { PersonalOwnershipPolicyComponent } from "./personal-ownership.component"; import { PoliciesComponent } from "./policies.component"; import { PolicyEditComponent } from "./policy-edit.component"; +import { RemoveUnlockWithPinPolicyComponent } from "./remove-unlock-with-pin.component"; import { RequireSsoPolicyComponent } from "./require-sso.component"; import { ResetPasswordPolicyComponent } from "./reset-password.component"; import { SendOptionsPolicyComponent } from "./send-options.component"; @@ -28,6 +29,7 @@ import { TwoFactorAuthenticationPolicyComponent } from "./two-factor-authenticat TwoFactorAuthenticationPolicyComponent, PoliciesComponent, PolicyEditComponent, + RemoveUnlockWithPinPolicyComponent, ], exports: [ DisableSendPolicyComponent, @@ -41,6 +43,7 @@ import { TwoFactorAuthenticationPolicyComponent } from "./two-factor-authenticat TwoFactorAuthenticationPolicyComponent, PoliciesComponent, PolicyEditComponent, + RemoveUnlockWithPinPolicyComponent, ], }) export class PoliciesModule {} diff --git a/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.html b/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.html new file mode 100644 index 00000000000..b0dd1b688bc --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.html @@ -0,0 +1,4 @@ + + + {{ "turnOn" | i18n }} + diff --git a/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.spec.ts b/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.spec.ts new file mode 100644 index 00000000000..9058cd22f3e --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.spec.ts @@ -0,0 +1,110 @@ +import { CommonModule } from "@angular/common"; +import { NO_ERRORS_SCHEMA } from "@angular/core"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { ReactiveFormsModule } from "@angular/forms"; +import { By } from "@angular/platform-browser"; +import { mock } from "jest-mock-extended"; + +import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; + +import { + RemoveUnlockWithPinPolicy, + RemoveUnlockWithPinPolicyComponent, +} from "./remove-unlock-with-pin.component"; + +describe("RemoveUnlockWithPinPolicy", () => { + const policy = new RemoveUnlockWithPinPolicy(); + + it("should have correct attributes", () => { + expect(policy.name).toEqual("removeUnlockWithPinPolicyTitle"); + expect(policy.description).toEqual("removeUnlockWithPinPolicyDesc"); + expect(policy.type).toEqual(PolicyType.RemoveUnlockWithPin); + expect(policy.component).toEqual(RemoveUnlockWithPinPolicyComponent); + }); +}); + +describe("RemoveUnlockWithPinPolicyComponent", () => { + let component: RemoveUnlockWithPinPolicyComponent; + let fixture: ComponentFixture; + const i18nService = mock(); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [CommonModule, ReactiveFormsModule], + declarations: [RemoveUnlockWithPinPolicyComponent, I18nPipe], + providers: [ + { provide: I18nService, useValue: mock() }, + { provide: I18nService, useValue: i18nService }, + ], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + + fixture = TestBed.createComponent(RemoveUnlockWithPinPolicyComponent); + component = fixture.componentInstance; + }); + + it("input selected on load when policy enabled", async () => { + component.policyResponse = new PolicyResponse({ + id: "policy1", + organizationId: "org1", + type: PolicyType.RemoveUnlockWithPin, + enabled: true, + }); + + component.ngOnInit(); + fixture.detectChanges(); + + expect(component.enabled.value).toBe(true); + const inputElement = fixture.debugElement.query(By.css("input")); + expect(inputElement).not.toBeNull(); + expect(inputElement.properties).toMatchObject({ + id: "enabled", + type: "checkbox", + checked: true, + }); + }); + + it("input not selected on load when policy disabled", async () => { + component.policyResponse = new PolicyResponse({ + id: "policy1", + organizationId: "org1", + type: PolicyType.RemoveUnlockWithPin, + enabled: false, + }); + + component.ngOnInit(); + fixture.detectChanges(); + + expect(component.enabled.value).toBe(false); + const inputElement = fixture.debugElement.query(By.css("input")); + expect(inputElement).not.toBeNull(); + expect(inputElement.properties).toMatchObject({ + id: "enabled", + type: "checkbox", + checked: false, + }); + }); + + it("turn on message label", async () => { + component.policyResponse = new PolicyResponse({ + id: "policy1", + organizationId: "org1", + type: PolicyType.RemoveUnlockWithPin, + enabled: false, + }); + i18nService.t.mockReturnValue("Turn on"); + + component.ngOnInit(); + fixture.detectChanges(); + + const bitLabelElement = fixture.debugElement.query(By.css("bit-label")); + expect(bitLabelElement).not.toBeNull(); + const textNodes = bitLabelElement.childNodes + .filter((node) => node.nativeNode.nodeType === Node.TEXT_NODE) + .map((node) => node.nativeNode.wholeText?.trim()); + expect(textNodes).toContain("Turn on"); + }); +}); diff --git a/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.ts b/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.ts new file mode 100644 index 00000000000..b737c803b5f --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.ts @@ -0,0 +1,18 @@ +import { Component } from "@angular/core"; + +import { PolicyType } from "@bitwarden/common/admin-console/enums"; + +import { BasePolicy, BasePolicyComponent } from "./base-policy.component"; + +export class RemoveUnlockWithPinPolicy extends BasePolicy { + name = "removeUnlockWithPinPolicyTitle"; + description = "removeUnlockWithPinPolicyDesc"; + type = PolicyType.RemoveUnlockWithPin; + component = RemoveUnlockWithPinPolicyComponent; +} + +@Component({ + selector: "remove-unlock-with-pin", + templateUrl: "remove-unlock-with-pin.component.html", +}) +export class RemoveUnlockWithPinPolicyComponent extends BasePolicyComponent {} diff --git a/apps/web/src/app/admin-console/organizations/policies/reset-password.component.ts b/apps/web/src/app/admin-console/organizations/policies/reset-password.component.ts index 6307ee13fa4..80e4e5254ff 100644 --- a/apps/web/src/app/admin-console/organizations/policies/reset-password.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/reset-password.component.ts @@ -1,9 +1,15 @@ import { Component, OnInit } from "@angular/core"; import { FormBuilder } from "@angular/forms"; +import { firstValueFrom } from "rxjs"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { BasePolicy, BasePolicyComponent } from "./base-policy.component"; @@ -31,13 +37,29 @@ export class ResetPasswordPolicyComponent extends BasePolicyComponent implements constructor( private formBuilder: FormBuilder, private organizationService: OrganizationService, + private accountService: AccountService, ) { super(); } async ngOnInit() { super.ngOnInit(); - const organization = await this.organizationService.get(this.policyResponse.organizationId); + + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + + if (!userId) { + throw new Error("No user found."); + } + + const organization = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(this.policyResponse.organizationId)), + ); + + if (!organization) { + throw new Error("No organization found."); + } this.showKeyConnectorInfo = organization.keyConnectorEnabled; } } diff --git a/apps/web/src/app/admin-console/organizations/reporting/organization-reporting-routing.module.ts b/apps/web/src/app/admin-console/organizations/reporting/organization-reporting-routing.module.ts index d6c7bdd97cd..635053dd1e2 100644 --- a/apps/web/src/app/admin-console/organizations/reporting/organization-reporting-routing.module.ts +++ b/apps/web/src/app/admin-console/organizations/reporting/organization-reporting-routing.module.ts @@ -1,16 +1,20 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { NgModule } from "@angular/core"; -import { RouterModule, Routes } from "@angular/router"; +import { inject, NgModule } from "@angular/core"; +import { CanMatchFn, RouterModule, Routes } from "@angular/router"; +import { map } from "rxjs"; import { canAccessReportingTab } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { ExposedPasswordsReportComponent } from "../../../admin-console/organizations/tools/exposed-passwords-report.component"; -import { InactiveTwoFactorReportComponent } from "../../../admin-console/organizations/tools/inactive-two-factor-report.component"; -import { ReusedPasswordsReportComponent } from "../../../admin-console/organizations/tools/reused-passwords-report.component"; -import { UnsecuredWebsitesReportComponent } from "../../../admin-console/organizations/tools/unsecured-websites-report.component"; -import { WeakPasswordsReportComponent } from "../../../admin-console/organizations/tools/weak-passwords-report.component"; +import { ExposedPasswordsReportComponent } from "../../../tools/reports/pages/organizations/exposed-passwords-report.component"; +import { InactiveTwoFactorReportComponent } from "../../../tools/reports/pages/organizations/inactive-two-factor-report.component"; +import { ReusedPasswordsReportComponent } from "../../../tools/reports/pages/organizations/reused-passwords-report.component"; +import { UnsecuredWebsitesReportComponent } from "../../../tools/reports/pages/organizations/unsecured-websites-report.component"; +import { WeakPasswordsReportComponent } from "../../../tools/reports/pages/organizations/weak-passwords-report.component"; +/* eslint no-restricted-imports: "error" */ import { isPaidOrgGuard } from "../guards/is-paid-org.guard"; import { organizationPermissionsGuard } from "../guards/org-permissions.guard"; import { organizationRedirectGuard } from "../guards/org-redirect.guard"; @@ -18,6 +22,11 @@ import { EventsComponent } from "../manage/events.component"; import { ReportsHomeComponent } from "./reports-home.component"; +const breadcrumbEventLogsPermission$: CanMatchFn = () => + inject(ConfigService) + .getFeatureFlag$(FeatureFlag.PM12276_BreadcrumbEventLogs) + .pipe(map((breadcrumbEventLogs) => breadcrumbEventLogs === true)); + const routes: Routes = [ { path: "", @@ -79,6 +88,20 @@ const routes: Routes = [ }, ], }, + // Event routing is temporarily duplicated + { + path: "events", + component: EventsComponent, + canMatch: [breadcrumbEventLogsPermission$], // if this matches, the flag is ON + canActivate: [ + organizationPermissionsGuard( + (org) => (org.canAccessEventLogs && org.useEvents) || org.isOwner, + ), + ], + data: { + titleId: "eventLogs", + }, + }, { path: "events", component: EventsComponent, diff --git a/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.ts b/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.ts index 2b2e1f7049b..6090396293f 100644 --- a/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.ts +++ b/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.ts @@ -2,9 +2,14 @@ // @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { ActivatedRoute, NavigationEnd, Router } from "@angular/router"; -import { filter, map, Observable, startWith, concatMap } from "rxjs"; +import { filter, map, Observable, startWith, concatMap, firstValueFrom } from "rxjs"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { ProductTierType } from "@bitwarden/common/billing/enums"; import { ReportVariant, reports, ReportType, ReportEntry } from "../../../tools/reports"; @@ -20,6 +25,7 @@ export class ReportsHomeComponent implements OnInit { constructor( private route: ActivatedRoute, private organizationService: OrganizationService, + private accountService: AccountService, private router: Router, ) {} @@ -30,8 +36,14 @@ export class ReportsHomeComponent implements OnInit { startWith(this.isReportsHomepageRouteUrl(this.router.url)), ); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + this.reports$ = this.route.params.pipe( - concatMap((params) => this.organizationService.get$(params.organizationId)), + concatMap((params) => + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(params.organizationId)), + ), map((org) => this.buildReports(org.productTierType)), ); } diff --git a/apps/web/src/app/admin-console/organizations/settings/account.component.html b/apps/web/src/app/admin-console/organizations/settings/account.component.html index b3fac56c0bb..ae1ecb6f3bb 100644 --- a/apps/web/src/app/admin-console/organizations/settings/account.component.html +++ b/apps/web/src/app/admin-console/organizations/settings/account.component.html @@ -68,6 +68,10 @@ {{ "limitCollectionDeletionDesc" | i18n }} + + {{ "limitItemDeletionDesc" | 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 b3c046d1fef..fdad689d982 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 @@ -3,7 +3,7 @@ import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; -import { lastValueFrom, Observable, Subject } from "rxjs"; +import { firstValueFrom, lastValueFrom, Observable, Subject } from "rxjs"; import { first, map, takeUntil } from "rxjs/operators"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; @@ -12,6 +12,8 @@ import { OrganizationUserType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { OrganizationSponsorshipRedeemRequest } from "@bitwarden/common/admin-console/models/request/organization/organization-sponsorship-redeem.request"; import { PreValidateSponsorshipResponse } from "@bitwarden/common/admin-console/models/response/pre-validate-sponsorship.response"; +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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -48,19 +50,18 @@ export class FamiliesForEnterpriseSetupComponent implements OnInit, OnDestroy { loading = true; badToken = false; - token: string; - existingFamilyOrganizations: Organization[]; - existingFamilyOrganizations$: Observable; + token!: string; + existingFamilyOrganizations$!: Observable; showNewOrganization = false; - _organizationPlansComponent: OrganizationPlansComponent; - preValidateSponsorshipResponse: PreValidateSponsorshipResponse; + preValidateSponsorshipResponse!: PreValidateSponsorshipResponse; _selectedFamilyOrganizationId = ""; private _destroy = new Subject(); formGroup = this.formBuilder.group({ selectedFamilyOrganizationId: ["", Validators.required], }); + constructor( private router: Router, private platformUtilsService: PlatformUtilsService, @@ -70,6 +71,7 @@ export class FamiliesForEnterpriseSetupComponent implements OnInit, OnDestroy { private syncService: SyncService, private validationService: ValidationService, private organizationService: OrganizationService, + private accountService: AccountService, private dialogService: DialogService, private formBuilder: FormBuilder, private toastService: ToastService, @@ -116,14 +118,18 @@ export class FamiliesForEnterpriseSetupComponent implements OnInit, OnDestroy { this.loading = false; }); - this.existingFamilyOrganizations$ = this.organizationService.organizations$.pipe( - map((orgs) => - orgs.filter( - (o) => - o.productTierType === ProductTierType.Families && o.type === OrganizationUserType.Owner, + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + this.existingFamilyOrganizations$ = this.organizationService + .organizations$(userId) + .pipe( + map((orgs) => + orgs.filter( + (o) => + o.productTierType === ProductTierType.Families && + o.type === OrganizationUserType.Owner, + ), ), - ), - ); + ); this.existingFamilyOrganizations$.pipe(takeUntil(this._destroy)).subscribe((orgs) => { if (orgs.length === 0) { @@ -170,6 +176,8 @@ export class FamiliesForEnterpriseSetupComponent implements OnInit, OnDestroy { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises this.router.navigate(["/"]); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { if (this.showNewOrganization) { const dialog = openDeleteOrganizationDialog(this.dialogService, { diff --git a/apps/web/src/app/admin-console/organizations/tools/exposed-passwords-report.component.ts b/apps/web/src/app/admin-console/organizations/tools/exposed-passwords-report.component.ts deleted file mode 100644 index baa49b8b13d..00000000000 --- a/apps/web/src/app/admin-console/organizations/tools/exposed-passwords-report.component.ts +++ /dev/null @@ -1,67 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, OnInit } from "@angular/core"; -import { ActivatedRoute } from "@angular/router"; - -import { ModalService } from "@bitwarden/angular/services/modal.service"; -import { AuditService } from "@bitwarden/common/abstractions/audit.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { PasswordRepromptService } from "@bitwarden/vault"; - -// eslint-disable-next-line no-restricted-imports -import { ExposedPasswordsReportComponent as BaseExposedPasswordsReportComponent } from "../../../tools/reports/pages/exposed-passwords-report.component"; - -@Component({ - selector: "app-org-exposed-passwords-report", - templateUrl: "../../../tools/reports/pages/exposed-passwords-report.component.html", -}) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class ExposedPasswordsReportComponent - extends BaseExposedPasswordsReportComponent - implements OnInit -{ - manageableCiphers: Cipher[]; - - constructor( - cipherService: CipherService, - auditService: AuditService, - modalService: ModalService, - organizationService: OrganizationService, - private route: ActivatedRoute, - passwordRepromptService: PasswordRepromptService, - i18nService: I18nService, - syncService: SyncService, - ) { - super( - cipherService, - auditService, - organizationService, - modalService, - passwordRepromptService, - i18nService, - syncService, - ); - } - - async ngOnInit() { - this.isAdminConsoleActive = true; - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.parent.parent.params.subscribe(async (params) => { - this.organization = await this.organizationService.get(params.organizationId); - this.manageableCiphers = await this.cipherService.getAll(); - }); - } - - getAllCiphers(): Promise { - return this.cipherService.getAllFromApiForOrganization(this.organization.id); - } - - canManageCipher(c: CipherView): boolean { - return this.manageableCiphers.some((x) => x.id === c.id); - } -} diff --git a/apps/web/src/app/admin-console/organizations/tools/inactive-two-factor-report.component.ts b/apps/web/src/app/admin-console/organizations/tools/inactive-two-factor-report.component.ts deleted file mode 100644 index 461e7691faa..00000000000 --- a/apps/web/src/app/admin-console/organizations/tools/inactive-two-factor-report.component.ts +++ /dev/null @@ -1,60 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, OnInit } from "@angular/core"; -import { ActivatedRoute } from "@angular/router"; - -import { ModalService } from "@bitwarden/angular/services/modal.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { PasswordRepromptService } from "@bitwarden/vault"; - -// eslint-disable-next-line no-restricted-imports -import { InactiveTwoFactorReportComponent as BaseInactiveTwoFactorReportComponent } from "../../../tools/reports/pages/inactive-two-factor-report.component"; - -@Component({ - selector: "app-inactive-two-factor-report", - templateUrl: "../../../tools/reports/pages/inactive-two-factor-report.component.html", -}) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class InactiveTwoFactorReportComponent - extends BaseInactiveTwoFactorReportComponent - implements OnInit -{ - constructor( - cipherService: CipherService, - modalService: ModalService, - private route: ActivatedRoute, - logService: LogService, - passwordRepromptService: PasswordRepromptService, - organizationService: OrganizationService, - i18nService: I18nService, - syncService: SyncService, - ) { - super( - cipherService, - organizationService, - modalService, - logService, - passwordRepromptService, - i18nService, - syncService, - ); - } - - async ngOnInit() { - this.isAdminConsoleActive = true; - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.parent.parent.params.subscribe(async (params) => { - this.organization = await this.organizationService.get(params.organizationId); - await super.ngOnInit(); - }); - } - - getAllCiphers(): Promise { - return this.cipherService.getAllFromApiForOrganization(this.organization.id); - } -} diff --git a/apps/web/src/app/admin-console/organizations/tools/reused-passwords-report.component.ts b/apps/web/src/app/admin-console/organizations/tools/reused-passwords-report.component.ts deleted file mode 100644 index 176fad24d24..00000000000 --- a/apps/web/src/app/admin-console/organizations/tools/reused-passwords-report.component.ts +++ /dev/null @@ -1,65 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, OnInit } from "@angular/core"; -import { ActivatedRoute } from "@angular/router"; - -import { ModalService } from "@bitwarden/angular/services/modal.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { PasswordRepromptService } from "@bitwarden/vault"; - -// eslint-disable-next-line no-restricted-imports -import { ReusedPasswordsReportComponent as BaseReusedPasswordsReportComponent } from "../../../tools/reports/pages/reused-passwords-report.component"; - -@Component({ - selector: "app-reused-passwords-report", - templateUrl: "../../../tools/reports/pages/reused-passwords-report.component.html", -}) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class ReusedPasswordsReportComponent - extends BaseReusedPasswordsReportComponent - implements OnInit -{ - manageableCiphers: Cipher[]; - - constructor( - cipherService: CipherService, - modalService: ModalService, - private route: ActivatedRoute, - organizationService: OrganizationService, - passwordRepromptService: PasswordRepromptService, - i18nService: I18nService, - syncService: SyncService, - ) { - super( - cipherService, - organizationService, - modalService, - passwordRepromptService, - i18nService, - syncService, - ); - } - - async ngOnInit() { - this.isAdminConsoleActive = true; - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.parent.parent.params.subscribe(async (params) => { - this.organization = await this.organizationService.get(params.organizationId); - this.manageableCiphers = await this.cipherService.getAll(); - await super.ngOnInit(); - }); - } - - getAllCiphers(): Promise { - return this.cipherService.getAllFromApiForOrganization(this.organization.id); - } - - canManageCipher(c: CipherView): boolean { - return this.manageableCiphers.some((x) => x.id === c.id); - } -} diff --git a/apps/web/src/app/admin-console/organizations/tools/unsecured-websites-report.component.ts b/apps/web/src/app/admin-console/organizations/tools/unsecured-websites-report.component.ts deleted file mode 100644 index 631890a9767..00000000000 --- a/apps/web/src/app/admin-console/organizations/tools/unsecured-websites-report.component.ts +++ /dev/null @@ -1,60 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, OnInit } from "@angular/core"; -import { ActivatedRoute } from "@angular/router"; - -import { CollectionService } from "@bitwarden/admin-console/common"; -import { ModalService } from "@bitwarden/angular/services/modal.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { PasswordRepromptService } from "@bitwarden/vault"; - -// eslint-disable-next-line no-restricted-imports -import { UnsecuredWebsitesReportComponent as BaseUnsecuredWebsitesReportComponent } from "../../../tools/reports/pages/unsecured-websites-report.component"; - -@Component({ - selector: "app-unsecured-websites-report", - templateUrl: "../../../tools/reports/pages/unsecured-websites-report.component.html", -}) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class UnsecuredWebsitesReportComponent - extends BaseUnsecuredWebsitesReportComponent - implements OnInit -{ - constructor( - cipherService: CipherService, - modalService: ModalService, - private route: ActivatedRoute, - organizationService: OrganizationService, - passwordRepromptService: PasswordRepromptService, - i18nService: I18nService, - syncService: SyncService, - collectionService: CollectionService, - ) { - super( - cipherService, - organizationService, - modalService, - passwordRepromptService, - i18nService, - syncService, - collectionService, - ); - } - - async ngOnInit() { - this.isAdminConsoleActive = true; - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.parent.parent.params.subscribe(async (params) => { - this.organization = await this.organizationService.get(params.organizationId); - await super.ngOnInit(); - }); - } - - getAllCiphers(): Promise { - return this.cipherService.getAllFromApiForOrganization(this.organization.id); - } -} diff --git a/apps/web/src/app/admin-console/organizations/tools/weak-passwords-report.component.ts b/apps/web/src/app/admin-console/organizations/tools/weak-passwords-report.component.ts deleted file mode 100644 index d65682d6623..00000000000 --- a/apps/web/src/app/admin-console/organizations/tools/weak-passwords-report.component.ts +++ /dev/null @@ -1,68 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, OnInit } from "@angular/core"; -import { ActivatedRoute } from "@angular/router"; - -import { ModalService } from "@bitwarden/angular/services/modal.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { PasswordRepromptService } from "@bitwarden/vault"; - -// eslint-disable-next-line no-restricted-imports -import { WeakPasswordsReportComponent as BaseWeakPasswordsReportComponent } from "../../../tools/reports/pages/weak-passwords-report.component"; - -@Component({ - selector: "app-weak-passwords-report", - templateUrl: "../../../tools/reports/pages/weak-passwords-report.component.html", -}) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class WeakPasswordsReportComponent - extends BaseWeakPasswordsReportComponent - implements OnInit -{ - manageableCiphers: Cipher[]; - - constructor( - cipherService: CipherService, - passwordStrengthService: PasswordStrengthServiceAbstraction, - modalService: ModalService, - private route: ActivatedRoute, - organizationService: OrganizationService, - passwordRepromptService: PasswordRepromptService, - i18nService: I18nService, - syncService: SyncService, - ) { - super( - cipherService, - passwordStrengthService, - organizationService, - modalService, - passwordRepromptService, - i18nService, - syncService, - ); - } - - async ngOnInit() { - this.isAdminConsoleActive = true; - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.parent.parent.params.subscribe(async (params) => { - this.organization = await this.organizationService.get(params.organizationId); - this.manageableCiphers = await this.cipherService.getAll(); - await super.ngOnInit(); - }); - } - - getAllCiphers(): Promise { - return this.cipherService.getAllFromApiForOrganization(this.organization.id); - } - - canManageCipher(c: CipherView): boolean { - return this.manageableCiphers.some((x) => x.id === c.id); - } -} diff --git a/apps/web/src/app/admin-console/settings/create-organization.component.html b/apps/web/src/app/admin-console/settings/create-organization.component.html index 105a3e6a251..a5acc62dfa9 100644 --- a/apps/web/src/app/admin-console/settings/create-organization.component.html +++ b/apps/web/src/app/admin-console/settings/create-organization.component.html @@ -2,5 +2,9 @@

    {{ "newOrganizationDesc" | i18n }}

    - +
    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 47cf1c61c5b..7a20826086d 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,10 +1,11 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Component, OnInit, ViewChild } from "@angular/core"; +import { Component } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ActivatedRoute } from "@angular/router"; import { first } from "rxjs/operators"; -import { PlanType, ProductTierType } from "@bitwarden/common/billing/enums"; +import { PlanType, ProductTierType, ProductType } from "@bitwarden/common/billing/enums"; import { OrganizationPlansComponent } from "../../billing"; import { HeaderModule } from "../../layouts/header/header.module"; @@ -15,29 +16,34 @@ import { SharedModule } from "../../shared"; standalone: true, imports: [SharedModule, OrganizationPlansComponent, HeaderModule], }) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class CreateOrganizationComponent implements OnInit { - @ViewChild(OrganizationPlansComponent, { static: true }) - orgPlansComponent: OrganizationPlansComponent; +export class CreateOrganizationComponent { + protected secretsManager = false; + protected plan: PlanType = PlanType.Free; + protected productTier: ProductTierType = ProductTierType.Free; - constructor(private route: ActivatedRoute) {} - - ngOnInit() { - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.queryParams.pipe(first()).subscribe(async (qParams) => { - if (qParams.plan === "families") { - this.orgPlansComponent.plan = PlanType.FamiliesAnnually; - this.orgPlansComponent.productTier = ProductTierType.Families; - } else if (qParams.plan === "teams") { - this.orgPlansComponent.plan = PlanType.TeamsAnnually; - this.orgPlansComponent.productTier = ProductTierType.Teams; - } else if (qParams.plan === "teamsStarter") { - this.orgPlansComponent.plan = PlanType.TeamsStarter; - this.orgPlansComponent.productTier = ProductTierType.TeamsStarter; - } else if (qParams.plan === "enterprise") { - this.orgPlansComponent.plan = PlanType.EnterpriseAnnually; - this.orgPlansComponent.productTier = ProductTierType.Enterprise; + constructor(private route: ActivatedRoute) { + this.route.queryParams.pipe(first(), takeUntilDestroyed()).subscribe((qParams) => { + if (qParams.plan === "families" || qParams.productTier == ProductTierType.Families) { + this.plan = PlanType.FamiliesAnnually; + this.productTier = ProductTierType.Families; + } else if (qParams.plan === "teams" || qParams.productTier == ProductTierType.Teams) { + this.plan = PlanType.TeamsAnnually; + this.productTier = ProductTierType.Teams; + } else if ( + qParams.plan === "teamsStarter" || + qParams.productTier == ProductTierType.TeamsStarter + ) { + this.plan = PlanType.TeamsStarter; + this.productTier = ProductTierType.TeamsStarter; + } else if ( + qParams.plan === "enterprise" || + qParams.productTier == ProductTierType.Enterprise + ) { + this.plan = PlanType.EnterpriseAnnually; + this.productTier = ProductTierType.Enterprise; } + + this.secretsManager = qParams.product == ProductType.SecretsManager; }); } } diff --git a/apps/web/src/app/app.component.html b/apps/web/src/app/app.component.html index a39c045e0e3..471d89be1c1 100644 --- a/apps/web/src/app/app.component.html +++ b/apps/web/src/app/app.component.html @@ -17,3 +17,5 @@ + + diff --git a/apps/web/src/app/app.component.ts b/apps/web/src/app/app.component.ts index ee9f87bc2cd..55e2595e0f7 100644 --- a/apps/web/src/app/app.component.ts +++ b/apps/web/src/app/app.component.ts @@ -2,27 +2,32 @@ // @ts-strict-ignore import { DOCUMENT } from "@angular/common"; import { Component, Inject, NgZone, OnDestroy, OnInit } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { NavigationEnd, Router } from "@angular/router"; import * as jq from "jquery"; import { Subject, filter, firstValueFrom, map, takeUntil, timeout } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; +import { DeviceTrustToastService } from "@bitwarden/angular/auth/services/device-trust-toast.service.abstraction"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { EventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service"; -import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; -import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service"; +import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; +import { VaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout"; +import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; +import { NotificationsService } from "@bitwarden/common/platform/notifications"; import { StateEventRunnerService } from "@bitwarden/common/platform/state"; import { SyncService } from "@bitwarden/common/platform/sync"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -42,6 +47,7 @@ import { SendOptionsPolicy, SingleOrgPolicy, TwoFactorAuthenticationPolicy, + RemoveUnlockWithPinPolicy, } from "./admin-console/organizations/policies"; const BroadcasterSubscriptionId = "AppComponent"; @@ -88,8 +94,13 @@ export class AppComponent implements OnDestroy, OnInit { private stateEventRunnerService: StateEventRunnerService, private organizationService: InternalOrganizationServiceAbstraction, private accountService: AccountService, + private apiService: ApiService, + private appIdService: AppIdService, private processReloadService: ProcessReloadServiceAbstraction, - ) {} + private deviceTrustToastService: DeviceTrustToastService, + ) { + this.deviceTrustToastService.setupListeners$.pipe(takeUntilDestroyed()).subscribe(); + } ngOnInit() { this.i18nService.locale$.pipe(takeUntil(this.destroy$)).subscribe((locale) => { @@ -116,24 +127,6 @@ export class AppComponent implements OnDestroy, OnInit { // eslint-disable-next-line @typescript-eslint/no-floating-promises this.ngZone.run(async () => { switch (message.command) { - case "loggedIn": - // 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.notificationsService.updateConnection(false); - break; - case "loggedOut": - if ( - message.userId == null || - message.userId === (await firstValueFrom(this.accountService.activeAccount$)) - ) { - await this.notificationsService.updateConnection(false); - } - break; - case "unlocked": - // 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.notificationsService.updateConnection(false); - 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. // eslint-disable-next-line @typescript-eslint/no-floating-promises @@ -147,10 +140,6 @@ export class AppComponent implements OnDestroy, OnInit { await this.vaultTimeoutService.lock(); break; case "locked": - // 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.notificationsService.updateConnection(false); - await this.processReloadService.startProcessReload(this.authService); break; case "lockedUrl": @@ -189,8 +178,6 @@ export class AppComponent implements OnDestroy, OnInit { type: "success", }); if (premiumConfirmed) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises await this.router.navigate(["settings/subscription/premium"]); } break; @@ -219,7 +206,10 @@ export class AppComponent implements OnDestroy, OnInit { break; case "syncOrganizationStatusChanged": { const { organizationId, enabled } = message; - const organizations = await firstValueFrom(this.organizationService.organizations$); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + const organizations = await firstValueFrom( + this.organizationService.organizations$(userId), + ); const organization = organizations.find((org) => org.id === organizationId); if (organization) { @@ -227,21 +217,27 @@ export class AppComponent implements OnDestroy, OnInit { ...organization, enabled: enabled, }; - await this.organizationService.upsert(updatedOrganization); + await this.organizationService.upsert(updatedOrganization, userId); } break; } case "syncOrganizationCollectionSettingChanged": { const { organizationId, limitCollectionCreation, limitCollectionDeletion } = message; - const organizations = await firstValueFrom(this.organizationService.organizations$); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + const organizations = await firstValueFrom( + this.organizationService.organizations$(userId), + ); const organization = organizations.find((org) => org.id === organizationId); if (organization) { - await this.organizationService.upsert({ - ...organization, - limitCollectionCreation: limitCollectionCreation, - limitCollectionDeletion: limitCollectionDeletion, - }); + await this.organizationService.upsert( + { + ...organization, + limitCollectionCreation: limitCollectionCreation, + limitCollectionDeletion: limitCollectionDeletion, + }, + userId, + ); } break; } @@ -263,6 +259,7 @@ export class AppComponent implements OnDestroy, OnInit { this.policyListService.addPolicies([ new TwoFactorAuthenticationPolicy(), new MasterPasswordPolicy(), + new RemoveUnlockWithPinPolicy(), new ResetPasswordPolicy(), new PasswordGeneratorPolicy(), new SingleOrgPolicy(), @@ -291,7 +288,7 @@ export class AppComponent implements OnDestroy, OnInit { // will prevent any toasts from being displayed long enough to be read await this.eventUploadService.uploadEvents(); - const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id))); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const logoutPromise = firstValueFrom( this.authService.authStatusFor$(userId).pipe( @@ -315,7 +312,7 @@ export class AppComponent implements OnDestroy, OnInit { await this.stateEventRunnerService.handleEvent("logout", userId); - await this.searchService.clearIndex(); + await this.searchService.clearIndex(userId); this.authService.logOut(async () => { await this.stateService.clean({ userId: userId }); await this.accountService.clean(userId); @@ -365,12 +362,8 @@ export class AppComponent implements OnDestroy, OnInit { private idleStateChanged() { if (this.isIdle) { - // 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.notificationsService.disconnectFromInactivity(); } else { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises this.notificationsService.reconnectFromActivity(); } } diff --git a/apps/web/src/app/app.module.ts b/apps/web/src/app/app.module.ts index 22fd745eab8..ed48e4a7343 100644 --- a/apps/web/src/app/app.module.ts +++ b/apps/web/src/app/app.module.ts @@ -3,7 +3,6 @@ import { LayoutModule } from "@angular/cdk/layout"; import { NgModule } from "@angular/core"; import { FormsModule } from "@angular/forms"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; -import { InfiniteScrollDirective } from "ngx-infinite-scroll"; import { AppComponent } from "./app.component"; import { CoreModule } from "./core"; @@ -23,7 +22,6 @@ import { WildcardRoutingModule } from "./wildcard-routing.module"; BrowserAnimationsModule, FormsModule, CoreModule, - InfiniteScrollDirective, DragDropModule, LayoutModule, OssRoutingModule, diff --git a/apps/web/src/app/auth/core/services/index.ts b/apps/web/src/app/auth/core/services/index.ts index 6275ad4f4f3..1e8eec759b1 100644 --- a/apps/web/src/app/auth/core/services/index.ts +++ b/apps/web/src/app/auth/core/services/index.ts @@ -3,3 +3,4 @@ export * from "./login-decryption-options"; export * from "./webauthn-login"; export * from "./set-password-jit"; export * from "./registration"; +export * from "./two-factor-auth"; diff --git a/apps/web/src/app/auth/core/services/login/web-login-component.service.spec.ts b/apps/web/src/app/auth/core/services/login/web-login-component.service.spec.ts index db5f72ac312..5d5770e2325 100644 --- a/apps/web/src/app/auth/core/services/login/web-login-component.service.spec.ts +++ b/apps/web/src/app/auth/core/services/login/web-login-component.service.spec.ts @@ -15,6 +15,8 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { RouterService } from "../../../../../../../../apps/web/src/app/core"; import { AcceptOrganizationInviteService } from "../../../organization-invite/accept-organization.service"; @@ -72,10 +74,10 @@ describe("WebLoginComponentService", () => { expect(service).toBeTruthy(); }); - describe("getOrgPolicies", () => { + describe("getOrgPoliciesFromOrgInvite", () => { it("returns undefined if organization invite is null", async () => { acceptOrganizationInviteService.getOrganizationInvite.mockResolvedValue(null); - const result = await service.getOrgPolicies(); + const result = await service.getOrgPoliciesFromOrgInvite(); expect(result).toBeUndefined(); }); @@ -92,7 +94,7 @@ describe("WebLoginComponentService", () => { organizationName: "org-name", }); policyApiService.getPoliciesByToken.mockRejectedValue(error); - await service.getOrgPolicies(); + await service.getOrgPoliciesFromOrgInvite(); expect(logService.error).toHaveBeenCalledWith(error); }); @@ -128,7 +130,7 @@ describe("WebLoginComponentService", () => { of(masterPasswordPolicyOptions), ); - const result = await service.getOrgPolicies(); + const result = await service.getOrgPoliciesFromOrgInvite(); expect(result).toEqual({ policies: policies, diff --git a/apps/web/src/app/auth/core/services/login/web-login-component.service.ts b/apps/web/src/app/auth/core/services/login/web-login-component.service.ts index ce1bce40e39..29f2f237ec1 100644 --- a/apps/web/src/app/auth/core/services/login/web-login-component.service.ts +++ b/apps/web/src/app/auth/core/services/login/web-login-component.service.ts @@ -1,6 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Injectable } from "@angular/core"; +import { Router } from "@angular/router"; import { firstValueFrom } from "rxjs"; import { @@ -37,6 +38,7 @@ export class WebLoginComponentService passwordGenerationService: PasswordGenerationServiceAbstraction, platformUtilsService: PlatformUtilsService, ssoLoginService: SsoLoginServiceAbstraction, + private router: Router, ) { super( cryptoFunctionService, @@ -45,10 +47,23 @@ export class WebLoginComponentService platformUtilsService, ssoLoginService, ); - this.clientType = this.platformUtilsService.getClientType(); } - async getOrgPolicies(): Promise { + /** + * For the web client, redirecting to the SSO component is done via the router. + * We do not need to provide email, state, or code challenge since those are set in state + * or generated on the SSO component. + */ + protected override async redirectToSso( + email: string, + state: string, + codeChallenge: string, + ): Promise { + await this.router.navigate(["/sso"]); + return; + } + + async getOrgPoliciesFromOrgInvite(): Promise { const orgInvite = await this.acceptOrganizationInviteService.getOrganizationInvite(); if (orgInvite != null) { diff --git a/apps/web/src/app/auth/core/services/rotateable-key-set.service.spec.ts b/apps/web/src/app/auth/core/services/rotateable-key-set.service.spec.ts index e032f198291..1241ea88fe9 100644 --- a/apps/web/src/app/auth/core/services/rotateable-key-set.service.spec.ts +++ b/apps/web/src/app/auth/core/services/rotateable-key-set.service.spec.ts @@ -1,7 +1,7 @@ import { TestBed } from "@angular/core/testing"; import { mock, MockProxy } from "jest-mock-extended"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { KeyService } from "@bitwarden/key-management"; diff --git a/apps/web/src/app/auth/core/services/rotateable-key-set.service.ts b/apps/web/src/app/auth/core/services/rotateable-key-set.service.ts index 044f140c53c..64a8bdfbfb4 100644 --- a/apps/web/src/app/auth/core/services/rotateable-key-set.service.ts +++ b/apps/web/src/app/auth/core/services/rotateable-key-set.service.ts @@ -1,7 +1,7 @@ import { inject, Injectable } from "@angular/core"; import { RotateableKeySet } from "@bitwarden/auth/common"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { KeyService } from "@bitwarden/key-management"; @@ -56,6 +56,9 @@ export class RotateableKeySetService { keySet.encryptedPublicKey, oldUserKey, ); + if (publicKey == null) { + throw new Error("failed to rotate key set: could not decrypt public key"); + } const newEncryptedPublicKey = await this.encryptService.encrypt(publicKey, newUserKey); const newEncryptedUserKey = await this.encryptService.rsaEncrypt(newUserKey.key, publicKey); diff --git a/apps/web/src/app/auth/core/services/two-factor-auth/index.ts b/apps/web/src/app/auth/core/services/two-factor-auth/index.ts new file mode 100644 index 00000000000..ba2697fdee4 --- /dev/null +++ b/apps/web/src/app/auth/core/services/two-factor-auth/index.ts @@ -0,0 +1,2 @@ +export * from "./web-two-factor-auth-component.service"; +export * from "./web-two-factor-auth-duo-component.service"; diff --git a/apps/web/src/app/auth/core/services/two-factor-auth/web-two-factor-auth-component.service.ts b/apps/web/src/app/auth/core/services/two-factor-auth/web-two-factor-auth-component.service.ts new file mode 100644 index 00000000000..451cec57ddd --- /dev/null +++ b/apps/web/src/app/auth/core/services/two-factor-auth/web-two-factor-auth-component.service.ts @@ -0,0 +1,14 @@ +import { + DefaultTwoFactorAuthComponentService, + TwoFactorAuthComponentService, + LegacyKeyMigrationAction, +} from "@bitwarden/auth/angular"; + +export class WebTwoFactorAuthComponentService + extends DefaultTwoFactorAuthComponentService + implements TwoFactorAuthComponentService +{ + override determineLegacyKeyMigrationAction(): LegacyKeyMigrationAction { + return LegacyKeyMigrationAction.NAVIGATE_TO_MIGRATION_COMPONENT; + } +} diff --git a/apps/web/src/app/auth/core/services/two-factor-auth/web-two-factor-auth-duo-component.service.spec.ts b/apps/web/src/app/auth/core/services/two-factor-auth/web-two-factor-auth-duo-component.service.spec.ts new file mode 100644 index 00000000000..8cd92e73267 --- /dev/null +++ b/apps/web/src/app/auth/core/services/two-factor-auth/web-two-factor-auth-duo-component.service.spec.ts @@ -0,0 +1,83 @@ +import { MockProxy, mock } from "jest-mock-extended"; + +import { Duo2faResult } from "@bitwarden/auth/angular"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; + +import { WebTwoFactorAuthDuoComponentService } from "./web-two-factor-auth-duo-component.service"; + +describe("WebTwoFactorAuthDuoComponentService", () => { + let webTwoFactorAuthDuoComponentService: WebTwoFactorAuthDuoComponentService; + + let platformUtilsService: MockProxy; + + let mockBroadcastChannel: jest.Mocked; + let eventTarget: EventTarget; + + beforeEach(() => { + jest.clearAllMocks(); + + platformUtilsService = mock(); + + eventTarget = new EventTarget(); + + mockBroadcastChannel = { + name: "duoResult", + postMessage: jest.fn(), + close: jest.fn(), + onmessage: jest.fn(), + onmessageerror: jest.fn(), + addEventListener: jest.fn().mockImplementation((type, listener) => { + eventTarget.addEventListener(type, listener); + }), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), + }; + + global.BroadcastChannel = jest.fn(() => mockBroadcastChannel); + + webTwoFactorAuthDuoComponentService = new WebTwoFactorAuthDuoComponentService( + platformUtilsService, + ); + }); + + afterEach(() => { + // reset global object + jest.restoreAllMocks(); + }); + + describe("listenForDuo2faResult$", () => { + it("should return an observable that emits a duo 2FA result when a duo result message is received", (done) => { + const expectedResult: Duo2faResult = { + code: "123456", + state: "verified", + token: "123456|verified", + }; + const mockMessageEvent = new MessageEvent("message", { + data: { + code: "123456", + state: "verified", + }, + lastEventId: "", + origin: "", + ports: [], + source: null, + }); + webTwoFactorAuthDuoComponentService.listenForDuo2faResult$().subscribe((result) => { + expect(result).toEqual(expectedResult); + done(); + }); + + // Trigger the message event + eventTarget.dispatchEvent(mockMessageEvent); + }); + }); + + describe("launchDuoFrameless", () => { + it("should launch the duo frameless URL", async () => { + const duoFramelessUrl = "https://duo.com/frameless"; + await webTwoFactorAuthDuoComponentService.launchDuoFrameless(duoFramelessUrl); + + expect(platformUtilsService.launchUri).toHaveBeenCalledWith(duoFramelessUrl); + }); + }); +}); diff --git a/apps/web/src/app/auth/core/services/two-factor-auth/web-two-factor-auth-duo-component.service.ts b/apps/web/src/app/auth/core/services/two-factor-auth/web-two-factor-auth-duo-component.service.ts new file mode 100644 index 00000000000..ac8eccb5198 --- /dev/null +++ b/apps/web/src/app/auth/core/services/two-factor-auth/web-two-factor-auth-duo-component.service.ts @@ -0,0 +1,31 @@ +import { fromEvent, map, Observable, share } from "rxjs"; + +import { TwoFactorAuthDuoComponentService, Duo2faResult } from "@bitwarden/auth/angular"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; + +export class WebTwoFactorAuthDuoComponentService implements TwoFactorAuthDuoComponentService { + private duo2faResult$: Observable; + + constructor(private platformUtilsService: PlatformUtilsService) { + const duoResultChannel: BroadcastChannel = new BroadcastChannel("duoResult"); + + this.duo2faResult$ = fromEvent(duoResultChannel, "message").pipe( + map((msg: MessageEvent) => { + return { + code: msg.data.code, + state: msg.data.state, + token: `${msg.data.code}|${msg.data.state}`, + } as Duo2faResult; + }), + // share the observable so that multiple subscribers can listen to the same event + share(), + ); + } + listenForDuo2faResult$(): Observable { + return this.duo2faResult$; + } + + async launchDuoFrameless(duoFramelessUrl: string): Promise { + this.platformUtilsService.launchUri(duoFramelessUrl); + } +} diff --git a/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.ts b/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.ts index 9d4bf654bf1..b3f635aee92 100644 --- a/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.ts +++ b/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.ts @@ -2,9 +2,7 @@ // @ts-strict-ignore import { Component } from "@angular/core"; import { ActivatedRoute, Params, Router } from "@angular/router"; -import { firstValueFrom } from "rxjs"; -import { RegisterRouteService } from "@bitwarden/auth/common"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -34,10 +32,9 @@ export class AcceptEmergencyComponent extends BaseAcceptComponent { i18nService: I18nService, route: ActivatedRoute, authService: AuthService, - registerRouteService: RegisterRouteService, private emergencyAccessService: EmergencyAccessService, ) { - super(router, platformUtilsService, i18nService, route, authService, registerRouteService); + super(router, platformUtilsService, i18nService, route, authService); } async authedHandler(qParams: Params): Promise { @@ -71,25 +68,14 @@ export class AcceptEmergencyComponent extends BaseAcceptComponent { } async register() { - let queryParams: Params; - let registerRoute = await firstValueFrom(this.registerRoute$); - if (registerRoute === "/register") { - queryParams = { - email: this.email, - }; - } else if (registerRoute === "/signup") { - // We have to override the base component route as we don't need users to - // complete email verification if they are coming directly an emailed invite. - registerRoute = "/finish-signup"; - queryParams = { + // We don't need users to complete email verification if they are coming directly from an emailed invite. + // Therefore, we skip /signup and navigate directly to /finish-signup. + await this.router.navigate(["/finish-signup"], { + queryParams: { email: this.email, acceptEmergencyAccessInviteToken: this.acceptEmergencyAccessInviteToken, emergencyAccessId: this.emergencyAccessId, - }; - } - - await this.router.navigate([registerRoute], { - queryParams: queryParams, + }, }); } } diff --git a/apps/web/src/app/auth/emergency-access/services/emergency-access.service.spec.ts b/apps/web/src/app/auth/emergency-access/services/emergency-access.service.spec.ts index 1c7d870175d..dfeb53f0c19 100644 --- a/apps/web/src/app/auth/emergency-access/services/emergency-access.service.spec.ts +++ b/apps/web/src/app/auth/emergency-access/services/emergency-access.service.spec.ts @@ -4,11 +4,11 @@ import { MockProxy } from "jest-mock-extended"; import mock from "jest-mock-extended/lib/Mock"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { BulkEncryptService } from "@bitwarden/common/key-management/crypto/abstractions/bulk-encrypt.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { UserKeyResponse } from "@bitwarden/common/models/response/user-key.response"; -import { BulkEncryptService } from "@bitwarden/common/platform/abstractions/bulk-encrypt.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { EncryptionType } from "@bitwarden/common/platform/enums"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; diff --git a/apps/web/src/app/auth/emergency-access/services/emergency-access.service.ts b/apps/web/src/app/auth/emergency-access/services/emergency-access.service.ts index 62a59da2995..7ac2f21a223 100644 --- a/apps/web/src/app/auth/emergency-access/services/emergency-access.service.ts +++ b/apps/web/src/app/auth/emergency-access/services/emergency-access.service.ts @@ -6,9 +6,9 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyData } from "@bitwarden/common/admin-console/models/data/policy.data"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { BulkEncryptService } from "@bitwarden/common/platform/abstractions/bulk-encrypt.service"; +import { BulkEncryptService } from "@bitwarden/common/key-management/crypto/abstractions/bulk-encrypt.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string"; diff --git a/apps/web/src/app/auth/hint.component.html b/apps/web/src/app/auth/hint.component.html deleted file mode 100644 index 9f4d76d8405..00000000000 --- a/apps/web/src/app/auth/hint.component.html +++ /dev/null @@ -1,23 +0,0 @@ -
    - - {{ "emailAddress" | i18n }} - - {{ "enterEmailToGetHint" | i18n }} - -
    -
    -
    diff --git a/apps/web/src/app/auth/hint.component.ts b/apps/web/src/app/auth/hint.component.ts deleted file mode 100644 index 5c180063084..00000000000 --- a/apps/web/src/app/auth/hint.component.ts +++ /dev/null @@ -1,64 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, OnInit } from "@angular/core"; -import { FormBuilder, Validators } from "@angular/forms"; -import { Router } from "@angular/router"; - -import { HintComponent as BaseHintComponent } from "@bitwarden/angular/auth/components/hint.component"; -import { LoginEmailServiceAbstraction } from "@bitwarden/auth/common"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { ToastService } from "@bitwarden/components"; - -@Component({ - selector: "app-hint", - templateUrl: "hint.component.html", -}) -export class HintComponent extends BaseHintComponent implements OnInit { - formGroup = this.formBuilder.group({ - email: ["", [Validators.email, Validators.required]], - }); - - get emailFormControl() { - return this.formGroup.controls.email; - } - - constructor( - router: Router, - i18nService: I18nService, - apiService: ApiService, - platformUtilsService: PlatformUtilsService, - logService: LogService, - loginEmailService: LoginEmailServiceAbstraction, - private formBuilder: FormBuilder, - protected toastService: ToastService, - ) { - super( - router, - i18nService, - apiService, - platformUtilsService, - logService, - loginEmailService, - toastService, - ); - } - - async ngOnInit(): Promise { - await super.ngOnInit(); - this.emailFormControl.setValue(this.email); - } - - // Wrapper method to call super.submit() since properties (e.g., submit) cannot use super directly - // This is because properties are assigned per type and generally don't have access to the prototype - async superSubmit() { - await super.submit(); - } - - submit = async () => { - this.email = this.emailFormControl.value; - await this.superSubmit(); - }; -} diff --git a/apps/web/src/app/auth/login/login-decryption-options/login-decryption-options-v1.component.html b/apps/web/src/app/auth/login/login-decryption-options/login-decryption-options-v1.component.html deleted file mode 100644 index 615edb82d0c..00000000000 --- a/apps/web/src/app/auth/login/login-decryption-options/login-decryption-options-v1.component.html +++ /dev/null @@ -1,105 +0,0 @@ -
    -
    -
    - -
    - - -

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

    -
    - -
    - -

    {{ "loginInitiated" | i18n }}

    - -

    - {{ "deviceApprovalRequired" | i18n }} -

    - -
    - - - {{ "rememberThisDevice" | i18n }} - {{ "uncheckIfPublicDevice" | i18n }} - -
    - -
    - - - - - -
    -
    - - -

    {{ "loggedInExclamation" | i18n }}

    - -
    - - - {{ "rememberThisDevice" | i18n }} - {{ "uncheckIfPublicDevice" | i18n }} - -
    - - -
    - -
    - -
    -

    {{ "loggingInAs" | i18n }} {{ data.userEmail }}

    - {{ "notYou" | i18n }} -
    -
    -
    -
    diff --git a/apps/web/src/app/auth/login/login-decryption-options/login-decryption-options-v1.component.ts b/apps/web/src/app/auth/login/login-decryption-options/login-decryption-options-v1.component.ts deleted file mode 100644 index 5eb72503b90..00000000000 --- a/apps/web/src/app/auth/login/login-decryption-options/login-decryption-options-v1.component.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Component, inject } from "@angular/core"; - -import { BaseLoginDecryptionOptionsComponentV1 } from "@bitwarden/angular/auth/components/base-login-decryption-options-v1.component"; - -import { RouterService } from "../../../core"; -import { AcceptOrganizationInviteService } from "../../organization-invite/accept-organization.service"; -@Component({ - selector: "web-login-decryption-options", - templateUrl: "login-decryption-options-v1.component.html", -}) -export class LoginDecryptionOptionsComponentV1 extends BaseLoginDecryptionOptionsComponentV1 { - protected routerService = inject(RouterService); - protected acceptOrganizationInviteService = inject(AcceptOrganizationInviteService); - - override async createUser(): Promise { - try { - await super.createUser(); - - // Invites from TDE orgs go through here, but the invite is - // accepted while being enrolled in admin recovery. So we need to clear - // the redirect and stored org invite. - await this.routerService.getAndClearLoginRedirectUrl(); - await this.acceptOrganizationInviteService.clearOrganizationInvitation(); - - await this.router.navigate(["/vault"]); - } catch (error) { - this.validationService.showError(error); - } - } - - createUserAction = async (): Promise => { - return this.createUser(); - }; -} diff --git a/apps/web/src/app/auth/login/login-v1.component.html b/apps/web/src/app/auth/login/login-v1.component.html deleted file mode 100644 index 5b3c2a99424..00000000000 --- a/apps/web/src/app/auth/login/login-v1.component.html +++ /dev/null @@ -1,128 +0,0 @@ -
    - -
    - - {{ "emailAddress" | i18n }} - - -
    - -
    - - - {{ "rememberEmail" | i18n }} - -
    - -
    - -
    - -
    -

    {{ "or" | i18n }}

    - - - {{ "logInWithPasskey" | i18n }} - -
    - -
    - -

    - {{ "newAroundHere" | i18n }} - - {{ "createAccount" | i18n }} -

    -
    - -
    -
    - - {{ "masterPass" | i18n }} - - - - {{ "getMasterPasswordHint" | i18n }} -
    - -
    - -
    - -
    - -
    - -
    - -
    - - - -
    - -
    -

    {{ "loggingInAs" | i18n }} {{ loggedEmail }}

    - {{ "notYou" | i18n }} -
    -
    -
    diff --git a/apps/web/src/app/auth/login/login-v1.component.ts b/apps/web/src/app/auth/login/login-v1.component.ts deleted file mode 100644 index 4a72612fd6e..00000000000 --- a/apps/web/src/app/auth/login/login-v1.component.ts +++ /dev/null @@ -1,234 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, NgZone, OnInit } from "@angular/core"; -import { FormBuilder } from "@angular/forms"; -import { ActivatedRoute, Router } from "@angular/router"; -import { firstValueFrom, takeUntil } from "rxjs"; -import { first } from "rxjs/operators"; - -import { LoginComponentV1 as BaseLoginComponent } from "@bitwarden/angular/auth/components/login-v1.component"; -import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service"; -import { - LoginStrategyServiceAbstraction, - LoginEmailServiceAbstraction, - RegisterRouteService, -} from "@bitwarden/auth/common"; -import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; -import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { PolicyData } from "@bitwarden/common/admin-console/models/data/policy.data"; -import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; -import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; -import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; -import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; -import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction"; -import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; -import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; -import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; -import { UserId } from "@bitwarden/common/types/guid"; -import { ToastService } from "@bitwarden/components"; -import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; - -import { RouterService } from "../../core"; -import { AcceptOrganizationInviteService } from "../organization-invite/accept-organization.service"; -import { OrganizationInvite } from "../organization-invite/organization-invite"; - -@Component({ - selector: "app-login", - templateUrl: "login-v1.component.html", -}) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class LoginComponentV1 extends BaseLoginComponent implements OnInit { - showResetPasswordAutoEnrollWarning = false; - enforcedPasswordPolicyOptions: MasterPasswordPolicyOptions; - policies: Policy[]; - - constructor( - private acceptOrganizationInviteService: AcceptOrganizationInviteService, - devicesApiService: DevicesApiServiceAbstraction, - appIdService: AppIdService, - loginStrategyService: LoginStrategyServiceAbstraction, - router: Router, - i18nService: I18nService, - route: ActivatedRoute, - platformUtilsService: PlatformUtilsService, - environmentService: EnvironmentService, - passwordGenerationService: PasswordGenerationServiceAbstraction, - private passwordStrengthService: PasswordStrengthServiceAbstraction, - cryptoFunctionService: CryptoFunctionService, - private policyApiService: PolicyApiServiceAbstraction, - private policyService: InternalPolicyService, - logService: LogService, - ngZone: NgZone, - protected stateService: StateService, - private routerService: RouterService, - formBuilder: FormBuilder, - formValidationErrorService: FormValidationErrorsService, - loginEmailService: LoginEmailServiceAbstraction, - ssoLoginService: SsoLoginServiceAbstraction, - webAuthnLoginService: WebAuthnLoginServiceAbstraction, - registerRouteService: RegisterRouteService, - toastService: ToastService, - ) { - super( - devicesApiService, - appIdService, - loginStrategyService, - router, - platformUtilsService, - i18nService, - stateService, - environmentService, - passwordGenerationService, - cryptoFunctionService, - logService, - ngZone, - formBuilder, - formValidationErrorService, - route, - loginEmailService, - ssoLoginService, - webAuthnLoginService, - registerRouteService, - toastService, - ); - this.onSuccessfulLoginNavigate = this.goAfterLogIn; - } - - submitForm = async (showToast = true) => { - return await this.submitFormHelper(showToast); - }; - - private async submitFormHelper(showToast: boolean) { - await super.submit(showToast); - } - - async ngOnInit() { - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.queryParams.pipe(first()).subscribe(async (qParams) => { - // If there is an query parameter called 'org', set previousUrl to `/create-organization?org=paramValue` - if (qParams.org != null) { - const route = this.router.createUrlTree(["create-organization"], { - queryParams: { plan: qParams.org }, - }); - this.routerService.setPreviousUrl(route.toString()); - } - - /** - * If there is a query parameter called 'sponsorshipToken', that means they are coming - * from an email for sponsoring a families organization. If so, then set the prevousUrl - * to `/setup/families-for-enterprise?token=paramValue` - */ - if (qParams.sponsorshipToken != null) { - const route = this.router.createUrlTree(["setup/families-for-enterprise"], { - queryParams: { token: qParams.sponsorshipToken }, - }); - this.routerService.setPreviousUrl(route.toString()); - } - - await super.ngOnInit(); - }); - - // If there's an existing org invite, use it to get the password policies - const orgInvite = await this.acceptOrganizationInviteService.getOrganizationInvite(); - if (orgInvite != null) { - await this.initPasswordPolicies(orgInvite); - } - } - - async goAfterLogIn(userId: UserId) { - const masterPassword = this.formGroup.value.masterPassword; - - // Check master password against policy - if (this.enforcedPasswordPolicyOptions != null) { - const strengthResult = this.passwordStrengthService.getPasswordStrength( - masterPassword, - this.formGroup.value.email, - ); - const masterPasswordScore = strengthResult == null ? null : strengthResult.score; - - // If invalid, save policies and require update - if ( - !this.policyService.evaluateMasterPassword( - masterPasswordScore, - masterPassword, - this.enforcedPasswordPolicyOptions, - ) - ) { - const policiesData: { [id: string]: PolicyData } = {}; - this.policies.map((p) => (policiesData[p.id] = PolicyData.fromPolicy(p))); - await this.policyService.replace(policiesData, userId); - await this.router.navigate(["update-password"]); - return; - } - } - - this.loginEmailService.clearValues(); - await this.router.navigate([this.successRoute]); - } - - async goToHint() { - await this.saveEmailSettings(); - await this.router.navigateByUrl("/hint"); - } - - async goToRegister() { - // TODO: remove when email verification flag is removed - const registerRoute = await firstValueFrom(this.registerRoute$); - - if (this.emailFormControl.valid) { - await this.router.navigate([registerRoute], { - queryParams: { email: this.emailFormControl.value }, - }); - return; - } - - await this.router.navigate([registerRoute]); - } - - protected override async handleMigrateEncryptionKey(result: AuthResult): Promise { - if (!result.requiresEncryptionKeyMigration) { - return false; - } - await this.router.navigate(["migrate-legacy-encryption"]); - return true; - } - - private async initPasswordPolicies(invite: OrganizationInvite): Promise { - try { - this.policies = await this.policyApiService.getPoliciesByToken( - invite.organizationId, - invite.token, - invite.email, - invite.organizationUserId, - ); - } catch (e) { - this.logService.error(e); - } - - if (this.policies == null) { - return; - } - - const resetPasswordPolicy = this.policyService.getResetPasswordPolicyOptions( - this.policies, - invite.organizationId, - ); - - // Set to true if policy enabled and auto-enroll enabled - this.showResetPasswordAutoEnrollWarning = - resetPasswordPolicy[1] && resetPasswordPolicy[0].autoEnrollEnabled; - - this.policyService - .masterPasswordPolicyOptions$(this.policies) - .pipe(takeUntil(this.destroy$)) - .subscribe((enforcedPasswordPolicyOptions) => { - this.enforcedPasswordPolicyOptions = enforcedPasswordPolicyOptions; - }); - } -} diff --git a/apps/web/src/app/auth/login/login-via-auth-request-v1.component.html b/apps/web/src/app/auth/login/login-via-auth-request-v1.component.html deleted file mode 100644 index 69777950a78..00000000000 --- a/apps/web/src/app/auth/login/login-via-auth-request-v1.component.html +++ /dev/null @@ -1,76 +0,0 @@ - - -
    -
    - - - -

    - {{ "loginOrCreateNewAccount" | i18n }} -

    - -
    -

    {{ "loginInitiated" | i18n }}

    - -
    -

    {{ "notificationSentDevice" | i18n }}

    - -

    - {{ "fingerprintMatchInfo" | i18n }} -

    -
    - -
    -

    {{ "fingerprintPhraseHeader" | i18n }}

    -

    - {{ fingerprintPhrase }} -

    -
    - - - -
    - -
    - {{ "loginWithDeviceEnabledNote" | i18n }} - {{ "viewAllLoginOptions" | i18n }} -
    -
    -
    - - -
    -

    {{ "adminApprovalRequested" | i18n }}

    - -
    -

    {{ "adminApprovalRequestSentToAdmins" | i18n }}

    -

    {{ "youWillBeNotifiedOnceApproved" | i18n }}

    -
    - -
    -

    {{ "fingerprintPhraseHeader" | i18n }}

    -

    - {{ fingerprintPhrase }} -

    -
    - -
    - -
    - {{ "troubleLoggingIn" | i18n }} - {{ "viewAllLoginOptions" | i18n }} -
    -
    -
    -
    -
    diff --git a/apps/web/src/app/auth/login/login-via-auth-request-v1.component.ts b/apps/web/src/app/auth/login/login-via-auth-request-v1.component.ts deleted file mode 100644 index 8a8883e035b..00000000000 --- a/apps/web/src/app/auth/login/login-via-auth-request-v1.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component } from "@angular/core"; - -import { LoginViaAuthRequestComponentV1 as BaseLoginViaAuthRequestComponentV1 } from "@bitwarden/angular/auth/components/login-via-auth-request-v1.component"; - -@Component({ - selector: "app-login-via-auth-request", - templateUrl: "login-via-auth-request-v1.component.html", -}) -export class LoginViaAuthRequestComponentV1 extends BaseLoginViaAuthRequestComponentV1 {} diff --git a/apps/web/src/app/auth/login/login.module.ts b/apps/web/src/app/auth/login/login.module.ts index a33a6b8a5a8..9a99c84f727 100644 --- a/apps/web/src/app/auth/login/login.module.ts +++ b/apps/web/src/app/auth/login/login.module.ts @@ -4,24 +4,11 @@ import { CheckboxModule } from "@bitwarden/components"; import { SharedModule } from "../../../app/shared"; -import { LoginDecryptionOptionsComponentV1 } from "./login-decryption-options/login-decryption-options-v1.component"; -import { LoginComponentV1 } from "./login-v1.component"; -import { LoginViaAuthRequestComponentV1 } from "./login-via-auth-request-v1.component"; import { LoginViaWebAuthnComponent } from "./login-via-webauthn/login-via-webauthn.component"; @NgModule({ imports: [SharedModule, CheckboxModule], - declarations: [ - LoginComponentV1, - LoginViaAuthRequestComponentV1, - LoginDecryptionOptionsComponentV1, - LoginViaWebAuthnComponent, - ], - exports: [ - LoginComponentV1, - LoginViaAuthRequestComponentV1, - LoginDecryptionOptionsComponentV1, - LoginViaWebAuthnComponent, - ], + declarations: [LoginViaWebAuthnComponent], + exports: [LoginViaWebAuthnComponent], }) export class LoginModule {} diff --git a/apps/web/src/app/auth/organization-invite/accept-organization.component.ts b/apps/web/src/app/auth/organization-invite/accept-organization.component.ts index 660b2759988..197b4031998 100644 --- a/apps/web/src/app/auth/organization-invite/accept-organization.component.ts +++ b/apps/web/src/app/auth/organization-invite/accept-organization.component.ts @@ -2,9 +2,7 @@ // @ts-strict-ignore import { Component } from "@angular/core"; import { ActivatedRoute, Params, Router } from "@angular/router"; -import { firstValueFrom } from "rxjs"; -import { RegisterRouteService } from "@bitwarden/auth/common"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -27,10 +25,9 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent { i18nService: I18nService, route: ActivatedRoute, authService: AuthService, - registerRouteService: RegisterRouteService, private acceptOrganizationInviteService: AcceptOrganizationInviteService, ) { - super(router, platformUtilsService, i18nService, route, authService, registerRouteService); + super(router, platformUtilsService, i18nService, route, authService); } async authedHandler(qParams: Params): Promise { @@ -55,6 +52,7 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent { async unauthedHandler(qParams: Params): Promise { const invite = OrganizationInvite.fromParams(qParams); + await this.acceptOrganizationInviteService.setOrganizationInvitation(invite); await this.navigateInviteAcceptance(invite); } @@ -86,25 +84,12 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent { // if SSO is disabled OR if sso is enabled but the SSO login required policy is not enabled // then send user to create account - // TODO: update logic when email verification flag is removed - let queryParams: Params; - let registerRoute = await firstValueFrom(this.registerRoute$); - if (registerRoute === "/register") { - queryParams = { - fromOrgInvite: "true", + // We don't need users to complete email verification if they are coming directly from an emailed invite. + // Therefore, we skip /signup and navigate directly to /finish-signup. + await this.router.navigate(["/finish-signup"], { + queryParams: { email: invite.email, - }; - } else if (registerRoute === "/signup") { - // We have to override the base component route as we don't need users to complete email verification - // if they are coming directly from an emailed org invite. - registerRoute = "/finish-signup"; - queryParams = { - email: invite.email, - }; - } - - await this.router.navigate([registerRoute], { - queryParams: queryParams, + }, }); return; } diff --git a/apps/web/src/app/auth/organization-invite/accept-organization.service.spec.ts b/apps/web/src/app/auth/organization-invite/accept-organization.service.spec.ts index b3709a15882..f613bdc7ddc 100644 --- a/apps/web/src/app/auth/organization-invite/accept-organization.service.spec.ts +++ b/apps/web/src/app/auth/organization-invite/accept-organization.service.spec.ts @@ -12,7 +12,7 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { ResetPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/reset-password-policy-options"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { FakeGlobalState } from "@bitwarden/common/spec/fake-state"; diff --git a/apps/web/src/app/auth/organization-invite/accept-organization.service.ts b/apps/web/src/app/auth/organization-invite/accept-organization.service.ts index a964d676159..22a79ef3696 100644 --- a/apps/web/src/app/auth/organization-invite/accept-organization.service.ts +++ b/apps/web/src/app/auth/organization-invite/accept-organization.service.ts @@ -16,7 +16,7 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; diff --git a/apps/web/src/app/auth/recover-two-factor.component.html b/apps/web/src/app/auth/recover-two-factor.component.html index e3641765800..dee3bec1520 100644 --- a/apps/web/src/app/auth/recover-two-factor.component.html +++ b/apps/web/src/app/auth/recover-two-factor.component.html @@ -1,6 +1,6 @@

    - {{ "recoverAccountTwoStepDesc" | i18n }} + {{ recoveryCodeMessage }} { + let component: RecoverTwoFactorComponent; + let fixture: ComponentFixture; + + // Mock Services + let mockRouter: MockProxy; + let mockApiService: MockProxy; + let mockPlatformUtilsService: MockProxy; + let mockI18nService: MockProxy; + let mockKeyService: MockProxy; + let mockLoginStrategyService: MockProxy; + let mockToastService: MockProxy; + let mockConfigService: MockProxy; + let mockLoginSuccessHandlerService: MockProxy; + let mockLogService: MockProxy; + let mockNewDeviceVerificationNoticeService: MockProxy; + + beforeEach(() => { + mockRouter = mock(); + mockApiService = mock(); + mockPlatformUtilsService = mock(); + mockI18nService = mock(); + mockKeyService = mock(); + mockLoginStrategyService = mock(); + mockToastService = mock(); + mockConfigService = mock(); + mockLoginSuccessHandlerService = mock(); + mockLogService = mock(); + mockNewDeviceVerificationNoticeService = mock(); + + TestBed.configureTestingModule({ + declarations: [RecoverTwoFactorComponent], + providers: [ + { provide: Router, useValue: mockRouter }, + { provide: ApiService, useValue: mockApiService }, + { provide: PlatformUtilsService, mockPlatformUtilsService }, + { provide: I18nService, useValue: mockI18nService }, + { provide: KeyService, useValue: mockKeyService }, + { provide: LoginStrategyServiceAbstraction, useValue: mockLoginStrategyService }, + { provide: ToastService, useValue: mockToastService }, + { provide: ConfigService, useValue: mockConfigService }, + { provide: LoginSuccessHandlerService, useValue: mockLoginSuccessHandlerService }, + { provide: LogService, useValue: mockLogService }, + { + provide: NewDeviceVerificationNoticeService, + useValue: mockNewDeviceVerificationNoticeService, + }, + ], + imports: [I18nPipe], + // FIXME(PM-18598): Replace unknownElements and unknownProperties with actual imports + errorOnUnknownElements: false, + }); + + fixture = TestBed.createComponent(RecoverTwoFactorComponent); + component = fixture.componentInstance; + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe("handleRecoveryLogin", () => { + it("should log in successfully and navigate to the two-factor settings page", async () => { + // Arrange + const email = "test@example.com"; + const recoveryCode = "testRecoveryCode"; + + const authResult = new AuthResult(); + mockLoginStrategyService.logIn.mockResolvedValue(authResult); + + // Act + await component["loginWithRecoveryCode"](email, recoveryCode); + + // Assert + expect(mockLoginStrategyService.logIn).toHaveBeenCalledWith( + expect.any(PasswordLoginCredentials), + ); + expect(mockToastService.showToast).toHaveBeenCalledWith({ + variant: "success", + title: "", + message: mockI18nService.t("youHaveBeenLoggedIn"), + }); + expect( + mockNewDeviceVerificationNoticeService.updateNewDeviceVerificationSkipNoticeState, + ).toHaveBeenCalledWith(authResult.userId, true); + expect(mockRouter.navigate).toHaveBeenCalledWith(["/settings/security/two-factor"]); + }); + + it("should handle login errors and redirect to login page", async () => { + // Arrange + const email = "test@example.com"; + const recoveryCode = "testRecoveryCode"; + + const error = new Error("Login failed"); + mockLoginStrategyService.logIn.mockRejectedValue(error); + + // Act + await component["loginWithRecoveryCode"](email, recoveryCode); + + // Assert + expect(mockLogService.error).toHaveBeenCalledWith( + "Error logging in automatically: ", + error.message, + ); + expect(mockRouter.navigate).toHaveBeenCalledWith(["/login"], { + queryParams: { email: email }, + }); + }); + }); +}); 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 a10413b9bd2..35aa1aab7c1 100644 --- a/apps/web/src/app/auth/recover-two-factor.component.ts +++ b/apps/web/src/app/auth/recover-two-factor.component.ts @@ -1,67 +1,121 @@ -// 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 { FormControl, FormGroup, Validators } from "@angular/forms"; import { Router } from "@angular/router"; -import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { TwoFactorRecoveryRequest } from "@bitwarden/common/auth/models/request/two-factor-recovery.request"; +import { + LoginStrategyServiceAbstraction, + PasswordLoginCredentials, + LoginSuccessHandlerService, +} from "@bitwarden/auth/common"; +import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; +import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { ToastService } from "@bitwarden/components"; -import { KeyService } from "@bitwarden/key-management"; +import { NewDeviceVerificationNoticeService } from "@bitwarden/vault"; @Component({ selector: "app-recover-two-factor", templateUrl: "recover-two-factor.component.html", }) -export class RecoverTwoFactorComponent { +export class RecoverTwoFactorComponent implements OnInit { protected formGroup = new FormGroup({ - email: new FormControl(null, [Validators.required]), - masterPassword: new FormControl(null, [Validators.required]), - recoveryCode: new FormControl(null, [Validators.required]), + email: new FormControl("", [Validators.required]), + masterPassword: new FormControl("", [Validators.required]), + recoveryCode: new FormControl("", [Validators.required]), }); + /** + * Message to display to the user about the recovery code + */ + recoveryCodeMessage = ""; + constructor( private router: Router, - private apiService: ApiService, - private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, - private keyService: KeyService, private loginStrategyService: LoginStrategyServiceAbstraction, private toastService: ToastService, + private loginSuccessHandlerService: LoginSuccessHandlerService, + private logService: LogService, + private newDeviceVerificationNoticeService: NewDeviceVerificationNoticeService, ) {} + async ngOnInit() { + this.recoveryCodeMessage = this.i18nService.t("logInBelowUsingYourSingleUseRecoveryCode"); + } + get email(): string { - return this.formGroup.value.email; + return this.formGroup.get("email")?.value ?? ""; } get masterPassword(): string { - return this.formGroup.value.masterPassword; + return this.formGroup.get("masterPassword")?.value ?? ""; } get recoveryCode(): string { - return this.formGroup.value.recoveryCode; + return this.formGroup.get("recoveryCode")?.value ?? ""; } + /** + * Handles the submission of the recovery code form. + */ submit = async () => { this.formGroup.markAllAsTouched(); if (this.formGroup.invalid) { return; } - const request = new TwoFactorRecoveryRequest(); - request.recoveryCode = this.recoveryCode.replace(/\s/g, "").toLowerCase(); - request.email = this.email.trim().toLowerCase(); - const key = await this.loginStrategyService.makePreloginKey(this.masterPassword, request.email); - request.masterPasswordHash = await this.keyService.hashMasterKey(this.masterPassword, key); - await this.apiService.postTwoFactorRecover(request); - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("twoStepRecoverDisabled"), - }); - await this.router.navigate(["/"]); + const email = this.email.trim().toLowerCase(); + const recoveryCode = this.recoveryCode.replace(/\s/g, "").toLowerCase(); + + await this.loginWithRecoveryCode(email, recoveryCode); }; + + /** + * Handles the login process after a successful account recovery. + */ + private async loginWithRecoveryCode(email: string, recoveryCode: string) { + // Build two-factor request to pass into PasswordLoginCredentials request using the 2FA recovery code and RecoveryCode type + const twoFactorRequest: TokenTwoFactorRequest = { + provider: TwoFactorProviderType.RecoveryCode, + token: recoveryCode, + remember: false, + }; + + const credentials = new PasswordLoginCredentials( + email, + this.masterPassword, + "", + twoFactorRequest, + ); + + try { + const authResult = await this.loginStrategyService.logIn(credentials); + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("youHaveBeenLoggedIn"), + }); + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("twoStepRecoverDisabled"), + }); + + await this.loginSuccessHandlerService.run(authResult.userId); + + // Before routing, set the state to skip the new device notification. This is a temporary + // fix and will be cleaned up in PM-18485. + await this.newDeviceVerificationNoticeService.updateNewDeviceVerificationSkipNoticeState( + authResult.userId, + true, + ); + + await this.router.navigate(["/settings/security/two-factor"]); + } catch (error) { + // If login errors, redirect to login page per product. Don't show error + this.logService.error("Error logging in automatically: ", (error as Error).message); + await this.router.navigate(["/login"], { queryParams: { email: email } }); + } + } } diff --git a/apps/web/src/app/auth/register-form/register-form.component.html b/apps/web/src/app/auth/register-form/register-form.component.html deleted file mode 100644 index 19a7a95b298..00000000000 --- a/apps/web/src/app/auth/register-form/register-form.component.html +++ /dev/null @@ -1,158 +0,0 @@ - - - -

    -
    diff --git a/apps/web/src/app/auth/register-form/register-form.component.ts b/apps/web/src/app/auth/register-form/register-form.component.ts deleted file mode 100644 index f497dffa7ac..00000000000 --- a/apps/web/src/app/auth/register-form/register-form.component.ts +++ /dev/null @@ -1,124 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, Input, OnInit } from "@angular/core"; -import { UntypedFormBuilder } from "@angular/forms"; -import { Router } from "@angular/router"; - -import { RegisterComponent as BaseRegisterComponent } from "@bitwarden/angular/auth/components/register.component"; -import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service"; -import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { AuditService } from "@bitwarden/common/abstractions/audit.service"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; -import { ReferenceEventRequest } from "@bitwarden/common/models/request/reference-event.request"; -import { RegisterRequest } from "@bitwarden/common/models/request/register.request"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; -import { KeyService } from "@bitwarden/key-management"; - -import { AcceptOrganizationInviteService } from "../organization-invite/accept-organization.service"; - -@Component({ - selector: "app-register-form", - templateUrl: "./register-form.component.html", -}) -export class RegisterFormComponent extends BaseRegisterComponent implements OnInit { - @Input() queryParamEmail: string; - @Input() queryParamFromOrgInvite: boolean; - @Input() enforcedPolicyOptions: MasterPasswordPolicyOptions; - @Input() referenceDataValue: ReferenceEventRequest; - - showErrorSummary = false; - characterMinimumMessage: string; - - constructor( - formValidationErrorService: FormValidationErrorsService, - formBuilder: UntypedFormBuilder, - loginStrategyService: LoginStrategyServiceAbstraction, - router: Router, - i18nService: I18nService, - keyService: KeyService, - apiService: ApiService, - stateService: StateService, - platformUtilsService: PlatformUtilsService, - passwordGenerationService: PasswordGenerationServiceAbstraction, - private policyService: PolicyService, - environmentService: EnvironmentService, - logService: LogService, - auditService: AuditService, - dialogService: DialogService, - acceptOrgInviteService: AcceptOrganizationInviteService, - toastService: ToastService, - configService: ConfigService, - ) { - super( - formValidationErrorService, - formBuilder, - loginStrategyService, - router, - i18nService, - keyService, - apiService, - stateService, - platformUtilsService, - passwordGenerationService, - environmentService, - logService, - auditService, - dialogService, - toastService, - configService, - ); - this.modifyRegisterRequest = async (request: RegisterRequest) => { - // Org invites are deep linked. Non-existent accounts are redirected to the register page. - // Org user id and token are included here only for validation and two factor purposes. - const orgInvite = await acceptOrgInviteService.getOrganizationInvite(); - if (orgInvite != null) { - request.organizationUserId = orgInvite.organizationUserId; - request.token = orgInvite.token; - } - // Invite is accepted after login (on deep link redirect). - }; - } - - async ngOnInit() { - await super.ngOnInit(); - this.referenceData = this.referenceDataValue; - if (this.queryParamEmail) { - this.formGroup.get("email")?.setValue(this.queryParamEmail); - } - - if (this.enforcedPolicyOptions != null && this.enforcedPolicyOptions.minLength > 0) { - this.characterMinimumMessage = ""; - } else { - this.characterMinimumMessage = this.i18nService.t("characterMinimum", this.minimumLength); - } - } - - async submit() { - if ( - this.enforcedPolicyOptions != null && - !this.policyService.evaluateMasterPassword( - this.passwordStrengthResult.score, - this.formGroup.value.masterPassword, - this.enforcedPolicyOptions, - ) - ) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("masterPasswordPolicyRequirementsNotMet"), - }); - return; - } - - await super.submit(false); - } -} diff --git a/apps/web/src/app/auth/register-form/register-form.module.ts b/apps/web/src/app/auth/register-form/register-form.module.ts deleted file mode 100644 index b63cb18506d..00000000000 --- a/apps/web/src/app/auth/register-form/register-form.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { NgModule } from "@angular/core"; - -import { PasswordCalloutComponent } from "@bitwarden/auth/angular"; - -import { SharedModule } from "../../shared"; - -import { RegisterFormComponent } from "./register-form.component"; - -@NgModule({ - imports: [SharedModule, PasswordCalloutComponent], - declarations: [RegisterFormComponent], - exports: [RegisterFormComponent], -}) -export class RegisterFormModule {} diff --git a/apps/web/src/app/auth/settings/account/account.component.html b/apps/web/src/app/auth/settings/account/account.component.html index 4055f14219c..c5edc021614 100644 --- a/apps/web/src/app/auth/settings/account/account.component.html +++ b/apps/web/src/app/auth/settings/account/account.component.html @@ -9,6 +9,26 @@
    + + + + @@ -32,7 +52,6 @@ - diff --git a/apps/web/src/app/auth/settings/account/account.component.ts b/apps/web/src/app/auth/settings/account/account.component.ts index 7e1be937a22..37bf535062f 100644 --- a/apps/web/src/app/auth/settings/account/account.component.ts +++ b/apps/web/src/app/auth/settings/account/account.component.ts @@ -1,11 +1,19 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; -import { combineLatest, from, lastValueFrom, map, Observable } from "rxjs"; +import { Component, OnInit, OnDestroy } from "@angular/core"; +import { + combineLatest, + firstValueFrom, + from, + lastValueFrom, + map, + Observable, + Subject, + takeUntil, +} from "rxjs"; -import { ModalService } from "@bitwarden/angular/services/modal.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { DialogService } from "@bitwarden/components"; @@ -14,21 +22,22 @@ import { PurgeVaultComponent } from "../../../vault/settings/purge-vault.compone import { DeauthorizeSessionsComponent } from "./deauthorize-sessions.component"; import { DeleteAccountDialogComponent } from "./delete-account-dialog.component"; +import { SetAccountVerifyDevicesDialogComponent } from "./set-account-verify-devices-dialog.component"; @Component({ selector: "app-account", templateUrl: "account.component.html", }) -export class AccountComponent implements OnInit { - @ViewChild("deauthorizeSessionsTemplate", { read: ViewContainerRef, static: true }) - deauthModalRef: ViewContainerRef; +export class AccountComponent implements OnInit, OnDestroy { + private destroy$ = new Subject(); - showChangeEmail$: Observable; - showPurgeVault$: Observable; - showDeleteAccount$: Observable; + showChangeEmail$: Observable = new Observable(); + showPurgeVault$: Observable = new Observable(); + showDeleteAccount$: Observable = new Observable(); + verifyNewDeviceLogin: boolean = true; constructor( - private modalService: ModalService, + private accountService: AccountService, private dialogService: DialogService, private userVerificationService: UserVerificationService, private configService: ConfigService, @@ -36,26 +45,21 @@ export class AccountComponent implements OnInit { ) {} async ngOnInit() { + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + const isAccountDeprovisioningEnabled$ = this.configService.getFeatureFlag$( FeatureFlag.AccountDeprovisioning, ); - const userIsManagedByOrganization$ = this.organizationService.organizations$.pipe( - map((organizations) => organizations.some((o) => o.userIsManagedByOrganization === true)), - ); + const userIsManagedByOrganization$ = this.organizationService + .organizations$(userId) + .pipe( + map((organizations) => organizations.some((o) => o.userIsManagedByOrganization === true)), + ); const hasMasterPassword$ = from(this.userVerificationService.hasMasterPassword()); - this.showChangeEmail$ = combineLatest([ - hasMasterPassword$, - isAccountDeprovisioningEnabled$, - userIsManagedByOrganization$, - ]).pipe( - map( - ([hasMasterPassword, isAccountDeprovisioningEnabled, userIsManagedByOrganization]) => - hasMasterPassword && (!isAccountDeprovisioningEnabled || !userIsManagedByOrganization), - ), - ); + this.showChangeEmail$ = hasMasterPassword$; this.showPurgeVault$ = combineLatest([ isAccountDeprovisioningEnabled$, @@ -76,11 +80,17 @@ export class AccountComponent implements OnInit { !isAccountDeprovisioningEnabled || !userIsManagedByOrganization, ), ); + this.accountService.accountVerifyNewDeviceLogin$ + .pipe(takeUntil(this.destroy$)) + .subscribe((verifyDevices) => { + this.verifyNewDeviceLogin = verifyDevices; + }); } - async deauthorizeSessions() { - await this.modalService.openViewRef(DeauthorizeSessionsComponent, this.deauthModalRef); - } + deauthorizeSessions = async () => { + const dialogRef = DeauthorizeSessionsComponent.open(this.dialogService); + await lastValueFrom(dialogRef.closed); + }; purgeVault = async () => { const dialogRef = PurgeVaultComponent.open(this.dialogService); @@ -91,4 +101,14 @@ export class AccountComponent implements OnInit { const dialogRef = DeleteAccountDialogComponent.open(this.dialogService); await lastValueFrom(dialogRef.closed); }; + + setNewDeviceLoginProtection = async () => { + const dialogRef = SetAccountVerifyDevicesDialogComponent.open(this.dialogService); + await lastValueFrom(dialogRef.closed); + }; + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } } diff --git a/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.html b/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.html index 34e9f734fc0..7e8b69a0c48 100644 --- a/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.html +++ b/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.html @@ -28,16 +28,16 @@ '!tw-outline-[3px] tw-outline-primary-600 hover:tw-outline-[3px] hover:tw-outline-primary-600': customColorSelected, }" - class="tw-relative tw-flex tw-h-24 tw-w-24 tw-cursor-pointer tw-place-content-center tw-content-center tw-justify-center tw-rounded-full tw-border tw-border-solid tw-border-secondary-600 tw-outline tw-outline-0 tw-outline-offset-1 hover:tw-outline-1 hover:tw-outline-primary-300 focus:tw-outline-2 focus:tw-outline-primary-600" + class="tw-relative tw-flex tw-size-24 tw-cursor-pointer tw-place-content-center tw-rounded-full tw-border tw-border-solid tw-border-secondary-600 tw-outline tw-outline-0 tw-outline-offset-1 hover:tw-outline-1 hover:tw-outline-primary-300 focus:tw-outline-2 focus:tw-outline-primary-600" [style.background-color]="customColor$ | async" > {{ "dangerZone" | i18n }}
    -

    - {{ - (accountDeprovisioningEnabled$ | async) && content.children.length === 1 - ? ("dangerZoneDescSingular" | i18n) - : ("dangerZoneDesc" | i18n) - }} -

    -
    diff --git a/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.html b/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.html index 3867e9d1ca7..ecadacfeed2 100644 --- a/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.html +++ b/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.html @@ -1,38 +1,21 @@ - +
    + + +

    {{ "deauthorizeSessionsDesc" | i18n }}

    + {{ "deauthorizeSessionsWarning" | i18n }} + +
    + + + + +
    +
    diff --git a/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.ts b/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.ts index 57ca0e0ecfc..a7c466d4ffc 100644 --- a/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.ts +++ b/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.ts @@ -1,6 +1,5 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Component } from "@angular/core"; +import { FormBuilder } from "@angular/forms"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; @@ -8,33 +7,33 @@ import { Verification } from "@bitwarden/common/auth/types/verification"; 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 { ToastService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; @Component({ selector: "app-deauthorize-sessions", templateUrl: "deauthorize-sessions.component.html", }) export class DeauthorizeSessionsComponent { - masterPassword: Verification; - formPromise: Promise; + deauthForm = this.formBuilder.group({ + verification: undefined as Verification | undefined, + }); + invalidSecret: boolean = false; constructor( private apiService: ApiService, private i18nService: I18nService, - private platformUtilsService: PlatformUtilsService, + private formBuilder: FormBuilder, private userVerificationService: UserVerificationService, private messagingService: MessagingService, private logService: LogService, private toastService: ToastService, ) {} - async submit() { + submit = async () => { try { - this.formPromise = this.userVerificationService - .buildRequest(this.masterPassword) - .then((request) => this.apiService.postSecurityStamp(request)); - await this.formPromise; + const verification: Verification = this.deauthForm.value.verification!; + const request = await this.userVerificationService.buildRequest(verification); + await this.apiService.postSecurityStamp(request); this.toastService.showToast({ variant: "success", title: this.i18nService.t("sessionsDeauthorized"), @@ -44,5 +43,9 @@ export class DeauthorizeSessionsComponent { } catch (e) { this.logService.error(e); } + }; + + static open(dialogService: DialogService) { + return dialogService.open(DeauthorizeSessionsComponent); } } diff --git a/apps/web/src/app/auth/settings/account/delete-account-dialog.component.ts b/apps/web/src/app/auth/settings/account/delete-account-dialog.component.ts index aa5cfa3c1dc..64d7dc1b0da 100644 --- a/apps/web/src/app/auth/settings/account/delete-account-dialog.component.ts +++ b/apps/web/src/app/auth/settings/account/delete-account-dialog.component.ts @@ -8,7 +8,6 @@ import { AccountApiService } from "@bitwarden/common/auth/abstractions/account-a import { Verification } from "@bitwarden/common/auth/types/verification"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { DialogService, ToastService } from "@bitwarden/components"; @Component({ @@ -22,7 +21,6 @@ export class DeleteAccountDialogComponent { constructor( private i18nService: I18nService, - private platformUtilsService: PlatformUtilsService, private formBuilder: FormBuilder, private accountApiService: AccountApiService, private dialogRef: DialogRef, diff --git a/apps/web/src/app/auth/settings/account/profile.component.ts b/apps/web/src/app/auth/settings/account/profile.component.ts index 4f4920270f0..72731363806 100644 --- a/apps/web/src/app/auth/settings/account/profile.component.ts +++ b/apps/web/src/app/auth/settings/account/profile.component.ts @@ -9,6 +9,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { UpdateProfileRequest } from "@bitwarden/common/auth/models/request/update-profile.request"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ProfileResponse } from "@bitwarden/common/models/response/profile.response"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -49,16 +50,21 @@ export class ProfileComponent implements OnInit, OnDestroy { this.fingerprintMaterial = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); + + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + this.managingOrganization$ = this.configService .getFeatureFlag$(FeatureFlag.AccountDeprovisioning) .pipe( switchMap((isAccountDeprovisioningEnabled) => isAccountDeprovisioningEnabled - ? this.organizationService.organizations$.pipe( - map((organizations) => - organizations.find((o) => o.userIsManagedByOrganization === true), - ), - ) + ? this.organizationService + .organizations$(userId) + .pipe( + map((organizations) => + organizations.find((o) => o.userIsManagedByOrganization === true), + ), + ) : of(null), ), ); diff --git a/apps/web/src/app/auth/settings/account/set-account-verify-devices-dialog.component.html b/apps/web/src/app/auth/settings/account/set-account-verify-devices-dialog.component.html new file mode 100644 index 00000000000..6cd5bbf9212 --- /dev/null +++ b/apps/web/src/app/auth/settings/account/set-account-verify-devices-dialog.component.html @@ -0,0 +1,43 @@ +
    + + +

    + {{ "turnOffNewDeviceLoginProtectionModalDesc" | i18n }} +

    +

    + {{ "turnOnNewDeviceLoginProtectionModalDesc" | i18n }} +

    + {{ + "turnOffNewDeviceLoginProtectionWarning" | i18n + }} + +
    + + + + + +
    +
    diff --git a/apps/web/src/app/auth/settings/account/set-account-verify-devices-dialog.component.ts b/apps/web/src/app/auth/settings/account/set-account-verify-devices-dialog.component.ts new file mode 100644 index 00000000000..dc7735d7520 --- /dev/null +++ b/apps/web/src/app/auth/settings/account/set-account-verify-devices-dialog.component.ts @@ -0,0 +1,122 @@ +import { DialogRef } from "@angular/cdk/dialog"; +import { CommonModule } from "@angular/common"; +import { Component, OnDestroy, OnInit } from "@angular/core"; +import { FormBuilder, ReactiveFormsModule } from "@angular/forms"; +import { firstValueFrom, Subject, takeUntil } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { UserVerificationFormInputComponent } from "@bitwarden/auth/angular"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AccountApiService } from "@bitwarden/common/auth/abstractions/account-api.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; +import { SetVerifyDevicesRequest } from "@bitwarden/common/auth/models/request/set-verify-devices.request"; +import { Verification } from "@bitwarden/common/auth/types/verification"; +import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { + AsyncActionsModule, + ButtonModule, + CalloutModule, + DialogModule, + DialogService, + FormFieldModule, + IconButtonModule, + RadioButtonModule, + SelectModule, + ToastService, +} from "@bitwarden/components"; + +@Component({ + templateUrl: "./set-account-verify-devices-dialog.component.html", + standalone: true, + imports: [ + CommonModule, + ReactiveFormsModule, + JslibModule, + FormFieldModule, + AsyncActionsModule, + ButtonModule, + IconButtonModule, + SelectModule, + CalloutModule, + RadioButtonModule, + DialogModule, + UserVerificationFormInputComponent, + ], +}) +export class SetAccountVerifyDevicesDialogComponent implements OnInit, OnDestroy { + // use this subject for all subscriptions to ensure all subscripts are completed + private destroy$ = new Subject(); + // the default for new device verification is true + verifyNewDeviceLogin: boolean = true; + has2faConfigured: boolean = false; + + setVerifyDevicesForm = this.formBuilder.group({ + verification: undefined as Verification | undefined, + }); + invalidSecret: boolean = false; + + constructor( + private i18nService: I18nService, + private formBuilder: FormBuilder, + private accountApiService: AccountApiService, + private accountService: AccountService, + private userVerificationService: UserVerificationService, + private dialogRef: DialogRef, + private toastService: ToastService, + private apiService: ApiService, + ) { + this.accountService.accountVerifyNewDeviceLogin$ + .pipe(takeUntil(this.destroy$)) + .subscribe((verifyDevices: boolean) => { + this.verifyNewDeviceLogin = verifyDevices; + }); + } + + async ngOnInit() { + const twoFactorProviders = await this.apiService.getTwoFactorProviders(); + this.has2faConfigured = twoFactorProviders.data.length > 0; + } + + submit = async () => { + try { + const activeAccount = await firstValueFrom( + this.accountService.activeAccount$.pipe(takeUntil(this.destroy$)), + ); + const verification: Verification = this.setVerifyDevicesForm.value.verification!; + const request: SetVerifyDevicesRequest = await this.userVerificationService.buildRequest( + verification, + SetVerifyDevicesRequest, + ); + // set verify device opposite what is currently is. + request.verifyDevices = !this.verifyNewDeviceLogin; + await this.accountApiService.setVerifyDevices(request); + await this.accountService.setAccountVerifyNewDeviceLogin( + activeAccount!.id, + request.verifyDevices, + ); + this.dialogRef.close(); + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("accountNewDeviceLoginProtectionSaved"), + }); + } catch (e) { + if (e instanceof ErrorResponse && e.statusCode === 400) { + this.invalidSecret = true; + } + throw e; + } + }; + + static open(dialogService: DialogService) { + return dialogService.open(SetAccountVerifyDevicesDialogComponent); + } + + // closes subscription leaks + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } +} diff --git a/apps/web/src/app/auth/settings/change-password.component.html b/apps/web/src/app/auth/settings/change-password.component.html index 91144fdfc1f..34bb74ee473 100644 --- a/apps/web/src/app/auth/settings/change-password.component.html +++ b/apps/web/src/app/auth/settings/change-password.component.html @@ -121,7 +121,7 @@ [(ngModel)]="masterPasswordHint" />
    - diff --git a/apps/web/src/app/auth/settings/change-password.component.ts b/apps/web/src/app/auth/settings/change-password.component.ts index 8f0f195440a..d8e371fd36b 100644 --- a/apps/web/src/app/auth/settings/change-password.component.ts +++ b/apps/web/src/app/auth/settings/change-password.component.ts @@ -5,17 +5,19 @@ import { Router } from "@angular/router"; import { firstValueFrom, map } from "rxjs"; import { ChangePasswordComponent as BaseChangePasswordComponent } from "@bitwarden/angular/auth/components/change-password.component"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; +import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { HashPurpose } from "@bitwarden/common/platform/enums"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { UserId } from "@bitwarden/common/types/guid"; @@ -23,7 +25,6 @@ import { MasterKey, UserKey } from "@bitwarden/common/types/key"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { DialogService, ToastService } from "@bitwarden/components"; -import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { KdfConfigService, KeyService } from "@bitwarden/key-management"; import { UserKeyRotationService } from "../../key-management/key-rotation/user-key-rotation.service"; @@ -36,41 +37,40 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent implements OnInit, OnDestroy { + loading = false; rotateUserKey = false; currentMasterPassword: string; masterPasswordHint: string; checkForBreaches = true; characterMinimumMessage = ""; + userkeyRotationV2 = false; constructor( i18nService: I18nService, keyService: KeyService, messagingService: MessagingService, - stateService: StateService, - passwordGenerationService: PasswordGenerationServiceAbstraction, platformUtilsService: PlatformUtilsService, policyService: PolicyService, private auditService: AuditService, private cipherService: CipherService, private syncService: SyncService, - private apiService: ApiService, + private masterPasswordApiService: MasterPasswordApiService, private router: Router, dialogService: DialogService, private userVerificationService: UserVerificationService, private keyRotationService: UserKeyRotationService, kdfConfigService: KdfConfigService, - masterPasswordService: InternalMasterPasswordServiceAbstraction, + protected masterPasswordService: InternalMasterPasswordServiceAbstraction, accountService: AccountService, toastService: ToastService, + private configService: ConfigService, ) { super( i18nService, keyService, messagingService, - passwordGenerationService, platformUtilsService, policyService, - stateService, dialogService, kdfConfigService, masterPasswordService, @@ -80,6 +80,8 @@ export class ChangePasswordComponent } async ngOnInit() { + this.userkeyRotationV2 = await this.configService.getFeatureFlag(FeatureFlag.UserKeyRotationV2); + if (!(await this.userVerificationService.hasMasterPassword())) { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises @@ -93,7 +95,9 @@ export class ChangePasswordComponent async rotateUserKeyClicked() { if (this.rotateUserKey) { - const ciphers = await this.cipherService.getAllDecrypted(); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + + const ciphers = await this.cipherService.getAllDecrypted(activeUserId); let hasOldAttachments = false; if (ciphers != null) { for (let i = 0; i < ciphers.length; i++) { @@ -140,6 +144,130 @@ export class ChangePasswordComponent } async submit() { + if (this.userkeyRotationV2) { + this.loading = true; + await this.submitNew(); + this.loading = false; + } else { + await this.submitOld(); + } + } + + async submitNew() { + if (this.currentMasterPassword == null || this.currentMasterPassword === "") { + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: this.i18nService.t("masterPasswordRequired"), + }); + return; + } + + if ( + this.masterPasswordHint != null && + this.masterPasswordHint.toLowerCase() === this.masterPassword.toLowerCase() + ) { + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: this.i18nService.t("hintEqualsPassword"), + }); + return; + } + + this.leakedPassword = false; + if (this.checkForBreaches) { + this.leakedPassword = (await this.auditService.passwordLeaked(this.masterPassword)) > 0; + } + + if (!(await this.strongPassword())) { + return; + } + + try { + if (this.rotateUserKey) { + await this.syncService.fullSync(true); + const user = await firstValueFrom(this.accountService.activeAccount$); + await this.keyRotationService.rotateUserKeyMasterPasswordAndEncryptedData( + this.currentMasterPassword, + this.masterPassword, + user, + this.masterPasswordHint, + ); + } else { + await this.updatePassword(this.masterPassword); + } + } catch (e) { + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: e.message, + }); + } + } + + // todo: move this to a service + // https://bitwarden.atlassian.net/browse/PM-17108 + private async updatePassword(newMasterPassword: string) { + const currentMasterPassword = this.currentMasterPassword; + const { userId, email } = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => ({ userId: a?.id, email: a?.email }))), + ); + const kdfConfig = await firstValueFrom(this.kdfConfigService.getKdfConfig$(userId)); + + const currentMasterKey = await this.keyService.makeMasterKey( + currentMasterPassword, + email, + kdfConfig, + ); + const decryptedUserKey = await this.masterPasswordService.decryptUserKeyWithMasterKey( + currentMasterKey, + userId, + ); + if (decryptedUserKey == null) { + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("invalidMasterPassword"), + }); + return; + } + + const newMasterKey = await this.keyService.makeMasterKey(newMasterPassword, email, kdfConfig); + const newMasterKeyEncryptedUserKey = await this.keyService.encryptUserKeyWithMasterKey( + newMasterKey, + decryptedUserKey, + ); + + const request = new PasswordRequest(); + request.masterPasswordHash = await this.keyService.hashMasterKey( + this.currentMasterPassword, + currentMasterKey, + ); + request.masterPasswordHint = this.masterPasswordHint; + request.newMasterPasswordHash = await this.keyService.hashMasterKey( + newMasterPassword, + newMasterKey, + ); + request.key = newMasterKeyEncryptedUserKey[1].encryptedString; + try { + await this.masterPasswordApiService.postPassword(request); + this.toastService.showToast({ + variant: "success", + title: this.i18nService.t("masterPasswordChanged"), + message: this.i18nService.t("masterPasswordChangedDesc"), + }); + this.messagingService.send("logout"); + } catch { + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("errorOccurred"), + }); + } + } + + async submitOld() { if ( this.masterPasswordHint != null && this.masterPasswordHint.toLowerCase() === this.masterPassword.toLowerCase() @@ -188,7 +316,7 @@ export class ChangePasswordComponent await this.kdfConfigService.getKdfConfig(), ); - const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id))); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const newLocalKeyHash = await this.keyService.hashMasterKey( this.masterPassword, newMasterKey, @@ -216,14 +344,14 @@ export class ChangePasswordComponent try { if (this.rotateUserKey) { - this.formPromise = this.apiService.postPassword(request).then(async () => { + this.formPromise = this.masterPasswordApiService.postPassword(request).then(async () => { // we need to save this for local masterkey verification during rotation await this.masterPasswordService.setMasterKeyHash(newLocalKeyHash, userId as UserId); await this.masterPasswordService.setMasterKey(newMasterKey, userId as UserId); return this.updateKey(); }); } else { - this.formPromise = this.apiService.postPassword(request); + this.formPromise = this.masterPasswordApiService.postPassword(request); } await this.formPromise; @@ -245,6 +373,6 @@ export class ChangePasswordComponent private async updateKey() { const user = await firstValueFrom(this.accountService.activeAccount$); - await this.keyRotationService.rotateUserKeyAndEncryptedData(this.masterPassword, user); + await this.keyRotationService.rotateUserKeyAndEncryptedDataLegacy(this.masterPassword, user); } } diff --git a/apps/web/src/app/auth/settings/emergency-access/attachments/emergency-access-attachments.component.ts b/apps/web/src/app/auth/settings/emergency-access/attachments/emergency-access-attachments.component.ts index 75d43bb3bc7..73191e1539e 100644 --- a/apps/web/src/app/auth/settings/emergency-access/attachments/emergency-access-attachments.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/attachments/emergency-access-attachments.component.ts @@ -4,7 +4,7 @@ import { AttachmentsComponent as BaseAttachmentsComponent } from "@bitwarden/ang import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; diff --git a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.html b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.html index fc199a55a76..ab93f0be3bc 100644 --- a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.html +++ b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.html @@ -52,7 +52,7 @@ [color]="c.avatarColor" size="small" > - + {{ c.email }} {{ "noTrustedContacts" | i18n }}

    @@ -175,7 +175,7 @@ [color]="c.avatarColor" size="small" > - + {{ c.email }} {{ "invited" | i18n @@ -263,7 +263,7 @@

    {{ "noGrantedAccess" | i18n }}

    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 73e32add5c2..dc464c18059 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 @@ -8,6 +8,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -42,7 +43,6 @@ import { selector: "emergency-access", templateUrl: "emergency-access.component.html", }) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil export class EmergencyAccessComponent implements OnInit { @ViewChild("addEdit", { read: ViewContainerRef, static: true }) addEditModalRef: ViewContainerRef; @ViewChild("takeoverTemplate", { read: ViewContainerRef, static: true }) @@ -83,7 +83,8 @@ export class EmergencyAccessComponent implements OnInit { } async ngOnInit() { - const orgs = await this.organizationService.getAll(); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + const orgs = await firstValueFrom(this.organizationService.organizations$(userId)); this.isOrganizationOwner = orgs.some((o) => o.isOwner); // 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 diff --git a/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts b/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts index 4e00c962ffd..68f439d34a4 100644 --- a/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts @@ -8,14 +8,12 @@ import { takeUntil } from "rxjs"; import { ChangePasswordComponent } from "@bitwarden/angular/auth/components/change-password.component"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { DialogService, ToastService } from "@bitwarden/components"; -import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { KdfType, KdfConfigService, KeyService } from "@bitwarden/key-management"; import { EmergencyAccessService } from "../../../emergency-access"; @@ -35,7 +33,6 @@ type EmergencyAccessTakeoverDialogData = { selector: "emergency-access-takeover", templateUrl: "emergency-access-takeover.component.html", }) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil export class EmergencyAccessTakeoverComponent extends ChangePasswordComponent implements OnInit, OnDestroy @@ -53,8 +50,6 @@ export class EmergencyAccessTakeoverComponent i18nService: I18nService, keyService: KeyService, messagingService: MessagingService, - stateService: StateService, - passwordGenerationService: PasswordGenerationServiceAbstraction, platformUtilsService: PlatformUtilsService, policyService: PolicyService, private emergencyAccessService: EmergencyAccessService, @@ -70,10 +65,8 @@ export class EmergencyAccessTakeoverComponent i18nService, keyService, messagingService, - passwordGenerationService, platformUtilsService, policyService, - stateService, dialogService, kdfConfigService, masterPasswordService, @@ -92,7 +85,6 @@ export class EmergencyAccessTakeoverComponent .subscribe((enforcedPolicyOptions) => (this.enforcedPolicyOptions = enforcedPolicyOptions)); } - // eslint-disable-next-line rxjs-angular/prefer-takeuntil ngOnDestroy(): void { super.ngOnDestroy(); } diff --git a/apps/web/src/app/auth/settings/emergency-access/view/emergency-access-view.component.ts b/apps/web/src/app/auth/settings/emergency-access/view/emergency-access-view.component.ts index 7506f6c5d0b..1e3d0cf705f 100644 --- a/apps/web/src/app/auth/settings/emergency-access/view/emergency-access-view.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/view/emergency-access-view.component.ts @@ -4,8 +4,6 @@ import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; import { ModalService } from "@bitwarden/angular/services/modal.service"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { DialogService } from "@bitwarden/components"; import { CipherFormConfigService, DefaultCipherFormConfigService } from "@bitwarden/vault"; @@ -13,7 +11,6 @@ import { CipherFormConfigService, DefaultCipherFormConfigService } from "@bitwar import { EmergencyAccessService } from "../../../emergency-access"; import { EmergencyAccessAttachmentsComponent } from "../attachments/emergency-access-attachments.component"; -import { EmergencyAddEditCipherComponent } from "./emergency-add-edit-cipher.component"; import { EmergencyViewDialogComponent } from "./emergency-view-dialog.component"; @Component({ @@ -21,10 +18,7 @@ import { EmergencyViewDialogComponent } from "./emergency-view-dialog.component" templateUrl: "emergency-access-view.component.html", providers: [{ provide: CipherFormConfigService, useClass: DefaultCipherFormConfigService }], }) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil export class EmergencyAccessViewComponent implements OnInit { - @ViewChild("cipherAddEdit", { read: ViewContainerRef, static: true }) - cipherAddEditModalRef: ViewContainerRef; @ViewChild("attachments", { read: ViewContainerRef, static: true }) attachmentsModalRef: ViewContainerRef; @@ -37,7 +31,6 @@ export class EmergencyAccessViewComponent implements OnInit { private router: Router, private route: ActivatedRoute, private emergencyAccessService: EmergencyAccessService, - private configService: ConfigService, private dialogService: DialogService, ) {} @@ -57,30 +50,10 @@ export class EmergencyAccessViewComponent implements OnInit { } async selectCipher(cipher: CipherView) { - const browserRefreshEnabled = await this.configService.getFeatureFlag( - FeatureFlag.ExtensionRefresh, - ); - - if (browserRefreshEnabled) { - EmergencyViewDialogComponent.open(this.dialogService, { - cipher, - }); - return; - } - - // FIXME PM-15385: Remove below dialog service logic once extension refresh is live. - - // eslint-disable-next-line - const [_, childComponent] = await this.modalService.openViewRef( - EmergencyAddEditCipherComponent, - this.cipherAddEditModalRef, - (comp) => { - comp.cipherId = cipher == null ? null : cipher.id; - comp.cipher = cipher; - }, - ); - - return childComponent; + EmergencyViewDialogComponent.open(this.dialogService, { + cipher, + }); + return; } async load() { @@ -88,6 +61,7 @@ export class EmergencyAccessViewComponent implements OnInit { this.loaded = true; } + // FIXME PM-17747: This will also need to be replaced with the new AttachmentViewDialog async viewAttachments(cipher: CipherView) { await this.modalService.openViewRef( EmergencyAccessAttachmentsComponent, diff --git a/apps/web/src/app/auth/settings/emergency-access/view/emergency-add-edit-cipher.component.ts b/apps/web/src/app/auth/settings/emergency-access/view/emergency-add-edit-cipher.component.ts deleted file mode 100644 index 78e3b805eb8..00000000000 --- a/apps/web/src/app/auth/settings/emergency-access/view/emergency-add-edit-cipher.component.ts +++ /dev/null @@ -1,104 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { DatePipe } from "@angular/common"; -import { Component, OnInit } from "@angular/core"; - -import { CollectionService } from "@bitwarden/admin-console/common"; -import { AuditService } from "@bitwarden/common/abstractions/audit.service"; -import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.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"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; -import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; -import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; -import { PasswordRepromptService } from "@bitwarden/vault"; - -import { AddEditComponent as BaseAddEditComponent } from "../../../../vault/individual-vault/add-edit.component"; - -@Component({ - selector: "app-org-vault-add-edit", - templateUrl: "../../../../vault/individual-vault/add-edit.component.html", -}) -export class EmergencyAddEditCipherComponent extends BaseAddEditComponent implements OnInit { - originalCipher: Cipher = null; - viewOnly = true; - protected override componentName = "app-org-vault-add-edit"; - - constructor( - cipherService: CipherService, - folderService: FolderService, - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - auditService: AuditService, - accountService: AccountService, - collectionService: CollectionService, - totpService: TotpService, - passwordGenerationService: PasswordGenerationServiceAbstraction, - messagingService: MessagingService, - eventCollectionService: EventCollectionService, - policyService: PolicyService, - passwordRepromptService: PasswordRepromptService, - organizationService: OrganizationService, - logService: LogService, - dialogService: DialogService, - datePipe: DatePipe, - configService: ConfigService, - billingAccountProfileStateService: BillingAccountProfileStateService, - cipherAuthorizationService: CipherAuthorizationService, - toastService: ToastService, - sdkService: SdkService, - ) { - super( - cipherService, - folderService, - i18nService, - platformUtilsService, - auditService, - accountService, - collectionService, - totpService, - passwordGenerationService, - messagingService, - eventCollectionService, - policyService, - organizationService, - logService, - passwordRepromptService, - dialogService, - datePipe, - configService, - billingAccountProfileStateService, - cipherAuthorizationService, - toastService, - sdkService, - ); - } - - async load() { - this.title = this.i18nService.t("viewItem"); - } - - async ngOnInit(): Promise { - await super.ngOnInit(); - // The base component `ngOnInit` calculates the `viewOnly` property based on cipher properties - // In the case of emergency access, `viewOnly` should always be true, set it manually here after - // the base `ngOnInit` is complete. - this.viewOnly = true; - } - - protected async loadCipher() { - return Promise.resolve(this.originalCipher); - } -} diff --git a/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts index 0ad7eef81be..6e96b357e3e 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 @@ -7,14 +7,18 @@ import { mock } from "jest-mock-extended"; import { CollectionService } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { DialogService } from "@bitwarden/components"; +import { ChangeLoginPasswordService, TaskService } from "@bitwarden/vault"; import { EmergencyViewDialogComponent } from "./emergency-view-dialog.component"; @@ -43,6 +47,7 @@ describe("EmergencyViewDialogComponent", () => { imports: [EmergencyViewDialogComponent, NoopAnimationsModule], providers: [ { provide: OrganizationService, useValue: mock() }, + { provide: AccountService, useValue: accountService }, { provide: CollectionService, useValue: mock() }, { provide: FolderService, useValue: mock() }, { provide: I18nService, useValue: { t: (...keys: string[]) => keys.join(" ") } }, @@ -51,7 +56,36 @@ describe("EmergencyViewDialogComponent", () => { { provide: DIALOG_DATA, useValue: { cipher: mockCipher } }, { provide: AccountService, useValue: accountService }, ], - }).compileComponents(); + }) + .overrideComponent(EmergencyViewDialogComponent, { + remove: { + providers: [ + { provide: PlatformUtilsService, useValue: PlatformUtilsService }, + { + provide: ChangeLoginPasswordService, + useValue: ChangeLoginPasswordService, + }, + { provide: ConfigService, useValue: ConfigService }, + { provide: CipherService, useValue: mock() }, + ], + }, + add: { + providers: [ + { + provide: TaskService, + useValue: mock(), + }, + { provide: PlatformUtilsService, useValue: mock() }, + { + provide: ChangeLoginPasswordService, + useValue: mock(), + }, + { provide: ConfigService, useValue: mock() }, + { provide: CipherService, useValue: mock() }, + ], + }, + }) + .compileComponents(); fixture = TestBed.createComponent(EmergencyViewDialogComponent); component = fixture.componentInstance; diff --git a/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.ts b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.ts index 68423c50d88..d2a6389806d 100644 --- a/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.ts @@ -9,7 +9,13 @@ import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { ButtonModule, DialogModule, DialogService } from "@bitwarden/components"; -import { CipherViewComponent } from "@bitwarden/vault"; +import { + ChangeLoginPasswordService, + CipherViewComponent, + DefaultChangeLoginPasswordService, + DefaultTaskService, + TaskService, +} from "@bitwarden/vault"; import { WebViewPasswordHistoryService } from "../../../../vault/services/web-view-password-history.service"; @@ -33,6 +39,8 @@ class PremiumUpgradePromptNoop implements PremiumUpgradePromptService { providers: [ { provide: ViewPasswordHistoryService, useClass: WebViewPasswordHistoryService }, { provide: PremiumUpgradePromptService, useClass: PremiumUpgradePromptNoop }, + { provide: TaskService, useClass: DefaultTaskService }, + { provide: ChangeLoginPasswordService, useClass: DefaultChangeLoginPasswordService }, ], }) export class EmergencyViewDialogComponent { diff --git a/apps/web/src/app/auth/settings/security/device-management.component.html b/apps/web/src/app/auth/settings/security/device-management.component.html index 743414aac41..587703c7389 100644 --- a/apps/web/src/app/auth/settings/security/device-management.component.html +++ b/apps/web/src/app/auth/settings/security/device-management.component.html @@ -1,11 +1,11 @@ -
    +
    -

    {{ "devices" | i18n }}

    +

    {{ "devices" | i18n }}

    @@ -40,7 +40,8 @@ > {{ col.title }} - + + @@ -48,17 +49,31 @@
    - {{ row.displayName }} - - {{ "trusted" | i18n }} - + + + {{ row.displayName }} + + + + {{ "needsApproval" | i18n }} + + + + {{ row.displayName }} + + {{ "trusted" | i18n }} + +
    {{ "currentSession" | i18n }} - {{ + {{ "requestPending" | i18n }} diff --git a/apps/web/src/app/auth/settings/security/device-management.component.spec.ts b/apps/web/src/app/auth/settings/security/device-management.component.spec.ts new file mode 100644 index 00000000000..84c1dfcb63b --- /dev/null +++ b/apps/web/src/app/auth/settings/security/device-management.component.spec.ts @@ -0,0 +1,181 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { RouterTestingModule } from "@angular/router/testing"; +import { of, Subject } from "rxjs"; + +import { AuthRequestApiService } from "@bitwarden/auth/common"; +import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; +import { DeviceView } from "@bitwarden/common/auth/abstractions/devices/views/device.view"; +import { DeviceType } from "@bitwarden/common/enums"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; +import { MessageListener } from "@bitwarden/common/platform/messaging"; +import { DialogService, ToastService, TableModule, PopoverModule } from "@bitwarden/components"; + +import { SharedModule } from "../../../shared"; +import { VaultBannersService } from "../../../vault/individual-vault/vault-banners/services/vault-banners.service"; + +import { DeviceManagementComponent } from "./device-management.component"; + +class MockResizeObserver { + observe = jest.fn(); + unobserve = jest.fn(); + disconnect = jest.fn(); +} + +global.ResizeObserver = MockResizeObserver; + +interface Message { + command: string; + notificationId?: string; +} + +describe("DeviceManagementComponent", () => { + let fixture: ComponentFixture; + let messageSubject: Subject; + let mockDevices: DeviceView[]; + let vaultBannersService: VaultBannersService; + + const mockDeviceResponse = { + id: "test-id", + requestDeviceType: "test-type", + requestDeviceTypeValue: DeviceType.Android, + requestDeviceIdentifier: "test-identifier", + requestIpAddress: "127.0.0.1", + creationDate: new Date().toISOString(), + responseDate: null, + key: "test-key", + masterPasswordHash: null, + publicKey: "test-public-key", + requestApproved: false, + origin: "test-origin", + }; + + beforeEach(async () => { + messageSubject = new Subject(); + mockDevices = []; + + await TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + SharedModule, + TableModule, + PopoverModule, + DeviceManagementComponent, + ], + providers: [ + { + provide: DevicesServiceAbstraction, + useValue: { + getDevices$: jest.fn().mockReturnValue(mockDevices), + getCurrentDevice$: jest.fn().mockReturnValue(of(null)), + getDeviceByIdentifier$: jest.fn().mockReturnValue(of(null)), + updateTrustedDeviceKeys: jest.fn(), + }, + }, + { + provide: AuthRequestApiService, + useValue: { + getAuthRequest: jest.fn().mockResolvedValue(mockDeviceResponse), + }, + }, + { + provide: MessageListener, + useValue: { + allMessages$: messageSubject.asObservable(), + }, + }, + { + provide: DialogService, + useValue: { + openSimpleDialog: jest.fn(), + }, + }, + { + provide: ToastService, + useValue: { + success: jest.fn(), + error: jest.fn(), + }, + }, + { + provide: VaultBannersService, + useValue: { + shouldShowPendingAuthRequestBanner: jest.fn(), + }, + }, + { + provide: I18nService, + useValue: { + t: jest.fn((key: string) => key), + }, + }, + { + provide: ValidationService, + useValue: { + showError: jest.fn(), + }, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(DeviceManagementComponent); + + vaultBannersService = TestBed.inject(VaultBannersService); + }); + + describe("message listener", () => { + beforeEach(() => { + jest.spyOn(vaultBannersService, "shouldShowPendingAuthRequestBanner").mockResolvedValue(true); + }); + + it("ignores other message types", async () => { + const initialDataLength = (fixture.componentInstance as any).dataSource.data.length; + const message: Message = { command: "other", notificationId: "test-id" }; + messageSubject.next(message); + await fixture.whenStable(); + + expect((fixture.componentInstance as any).dataSource.data.length).toBe(initialDataLength); + }); + + it("adds device to table when auth request message received", async () => { + const initialDataLength = (fixture.componentInstance as any).dataSource.data.length; + const message: Message = { + command: "openLoginApproval", + notificationId: "test-id", + }; + + messageSubject.next(message); + fixture.detectChanges(); + await fixture.whenStable(); + + const dataSource = (fixture.componentInstance as any).dataSource; + expect(dataSource.data.length).toBe(initialDataLength + 1); + + const addedDevice = dataSource.data[0]; + expect(addedDevice).toEqual({ + id: "", + type: mockDeviceResponse.requestDeviceTypeValue, + displayName: expect.any(String), + loginStatus: "requestPending", + firstLogin: expect.any(Date), + trusted: false, + devicePendingAuthRequest: { + id: mockDeviceResponse.id, + creationDate: mockDeviceResponse.creationDate, + }, + hasPendingAuthRequest: true, + identifier: mockDeviceResponse.requestDeviceIdentifier, + }); + }); + + it("stops listening when component is destroyed", async () => { + fixture.destroy(); + const message: Message = { + command: "openLoginApproval", + notificationId: "test-id", + }; + messageSubject.next(message); + expect((fixture.componentInstance as any).dataSource.data.length).toBe(0); + }); + }); +}); diff --git a/apps/web/src/app/auth/settings/security/device-management.component.ts b/apps/web/src/app/auth/settings/security/device-management.component.ts index 65f2afc250e..631ab02db7d 100644 --- a/apps/web/src/app/auth/settings/security/device-management.component.ts +++ b/apps/web/src/app/auth/settings/security/device-management.component.ts @@ -1,14 +1,20 @@ import { CommonModule } from "@angular/common"; -import { Component } from "@angular/core"; +import { Component, DestroyRef } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { firstValueFrom } from "rxjs"; -import { switchMap } from "rxjs/operators"; +import { LoginApprovalComponent } from "@bitwarden/auth/angular"; +import { AuthRequestApiService } from "@bitwarden/auth/common"; import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; +import { + DevicePendingAuthRequest, + DeviceResponse, +} from "@bitwarden/common/auth/abstractions/devices/responses/device.response"; import { DeviceView } from "@bitwarden/common/auth/abstractions/devices/views/device.view"; import { DeviceType, DeviceTypeMetadata } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; +import { MessageListener } from "@bitwarden/common/platform/messaging"; import { DialogService, ToastService, @@ -19,6 +25,9 @@ import { import { SharedModule } from "../../../shared"; +/** + * Interface representing a row in the device management table + */ interface DeviceTableData { id: string; type: DeviceType; @@ -26,7 +35,9 @@ interface DeviceTableData { loginStatus: string; firstLogin: Date; trusted: boolean; - devicePendingAuthRequest: object | null; + devicePendingAuthRequest: DevicePendingAuthRequest | null; + hasPendingAuthRequest: boolean; + identifier: string; } /** @@ -39,7 +50,6 @@ interface DeviceTableData { imports: [CommonModule, SharedModule, TableModule, PopoverModule], }) export class DeviceManagementComponent { - protected readonly tableId = "device-management-table"; protected dataSource = new TableDataSource(); protected currentDevice: DeviceView | undefined; protected loading = true; @@ -51,35 +61,158 @@ export class DeviceManagementComponent { private dialogService: DialogService, private toastService: ToastService, private validationService: ValidationService, + private messageListener: MessageListener, + private authRequestApiService: AuthRequestApiService, + private destroyRef: DestroyRef, ) { - this.devicesService - .getCurrentDevice$() - .pipe( - takeUntilDestroyed(), - switchMap((currentDevice) => { - this.currentDevice = new DeviceView(currentDevice); - return this.devicesService.getDevices$(); - }), - ) - .subscribe({ - next: (devices) => { - this.dataSource.data = devices.map((device) => { - return { - id: device.id, - type: device.type, - displayName: this.getHumanReadableDeviceType(device.type), - loginStatus: this.getLoginStatus(device), - devicePendingAuthRequest: device.response.devicePendingAuthRequest, - firstLogin: new Date(device.creationDate), - trusted: device.response.isTrusted, - }; - }); - this.loading = false; - }, - error: () => { - this.loading = false; - }, - }); + void this.initializeDevices(); + } + + /** + * Initialize the devices list and set up the message listener + */ + private async initializeDevices(): Promise { + try { + await this.loadDevices(); + + this.messageListener.allMessages$ + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((message) => { + if (message.command !== "openLoginApproval") { + return; + } + // Handle inserting a new device when an auth request is received + this.upsertDeviceWithPendingAuthRequest( + message as { command: string; notificationId: string }, + ).catch((error) => this.validationService.showError(error)); + }); + } catch (error) { + this.validationService.showError(error); + } + } + + /** + * Handle inserting a new device when an auth request is received + * @param message - The auth request message + */ + private async upsertDeviceWithPendingAuthRequest(message: { + command: string; + notificationId: string; + }): Promise { + const requestId = message.notificationId; + if (!requestId) { + return; + } + + const authRequestResponse = await this.authRequestApiService.getAuthRequest(requestId); + if (!authRequestResponse) { + return; + } + + // Add new device to the table + const upsertDevice: DeviceTableData = { + id: "", + type: authRequestResponse.requestDeviceTypeValue, + displayName: this.getHumanReadableDeviceType(authRequestResponse.requestDeviceTypeValue), + loginStatus: this.i18nService.t("requestPending"), + firstLogin: new Date(authRequestResponse.creationDate), + trusted: false, + devicePendingAuthRequest: { + id: authRequestResponse.id, + creationDate: authRequestResponse.creationDate, + }, + hasPendingAuthRequest: true, + identifier: authRequestResponse.requestDeviceIdentifier, + }; + + // If the device already exists in the DB, update the device id and first login date + if (authRequestResponse.requestDeviceIdentifier) { + const existingDevice = await firstValueFrom( + this.devicesService.getDeviceByIdentifier$(authRequestResponse.requestDeviceIdentifier), + ); + + if (existingDevice?.id && existingDevice.creationDate) { + upsertDevice.id = existingDevice.id; + upsertDevice.firstLogin = new Date(existingDevice.creationDate); + } + } + + const existingDeviceIndex = this.dataSource.data.findIndex( + (device) => device.identifier === upsertDevice.identifier, + ); + + if (existingDeviceIndex >= 0) { + // Update existing device + this.dataSource.data[existingDeviceIndex] = upsertDevice; + this.dataSource.data = [...this.dataSource.data]; + } else { + // Add new device + this.dataSource.data = [upsertDevice, ...this.dataSource.data]; + } + } + + /** + * Load current device and all devices + */ + private async loadDevices(): Promise { + try { + const currentDevice = await firstValueFrom(this.devicesService.getCurrentDevice$()); + const devices = await firstValueFrom(this.devicesService.getDevices$()); + + if (!currentDevice || !devices) { + this.loading = false; + return; + } + + this.currentDevice = new DeviceView(currentDevice); + this.updateDeviceTable(devices); + } catch (error) { + this.validationService.showError(error); + } finally { + this.loading = false; + } + } + + /** + * Updates the device table with the latest device data + * @param devices - Array of device views to display in the table + */ + private updateDeviceTable(devices: Array): void { + this.dataSource.data = devices + .map((device: DeviceView): DeviceTableData | null => { + if (!device.id) { + this.validationService.showError(new Error(this.i18nService.t("deviceIdMissing"))); + return null; + } + + if (device.type == undefined) { + this.validationService.showError(new Error(this.i18nService.t("deviceTypeMissing"))); + return null; + } + + if (!device.creationDate) { + this.validationService.showError( + new Error(this.i18nService.t("deviceCreationDateMissing")), + ); + return null; + } + + const hasPendingRequest = device.response + ? this.hasPendingAuthRequest(device.response) + : false; + return { + id: device.id, + type: device.type, + displayName: this.getHumanReadableDeviceType(device.type), + loginStatus: this.getLoginStatus(device), + firstLogin: new Date(device.creationDate), + trusted: device.response?.isTrusted ?? false, + devicePendingAuthRequest: device.response?.devicePendingAuthRequest ?? null, + hasPendingAuthRequest: hasPendingRequest, + identifier: device.identifier ?? "", + }; + }) + .filter((device): device is DeviceTableData => device !== null); } /** @@ -138,7 +271,7 @@ export class DeviceManagementComponent { return this.i18nService.t("currentSession"); } - if (device.response.devicePendingAuthRequest?.creationDate) { + if (device?.response?.devicePendingAuthRequest?.creationDate) { return this.i18nService.t("requestPending"); } @@ -176,15 +309,36 @@ export class DeviceManagementComponent { /** * Check if a device has a pending auth request - * @param device - The device + * @param device - The device response * @returns True if the device has a pending auth request, false otherwise */ - protected hasPendingAuthRequest(device: DeviceTableData): boolean { + private hasPendingAuthRequest(device: DeviceResponse): boolean { return ( device.devicePendingAuthRequest !== undefined && device.devicePendingAuthRequest !== null ); } + /** + * Open a dialog to approve or deny a pending auth request for a device + */ + async managePendingAuthRequest(device: DeviceTableData) { + if (device.devicePendingAuthRequest === undefined || device.devicePendingAuthRequest === null) { + return; + } + + const dialogRef = LoginApprovalComponent.open(this.dialogService, { + notificationId: device.devicePendingAuthRequest.id, + }); + + const result = await firstValueFrom(dialogRef.closed); + + if (result !== undefined && typeof result === "boolean") { + // auth request approved or denied so reset + device.devicePendingAuthRequest = null; + device.hasPendingAuthRequest = false; + } + } + /** * Remove a device * @param device - The device diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-recovery.component.html b/apps/web/src/app/auth/settings/two-factor/two-factor-recovery.component.html index 98676509078..413432e5a02 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-recovery.component.html +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-recovery.component.html @@ -1,8 +1,8 @@ - - - {{ "twoStepLogin" | i18n }} - {{ "recoveryCodeTitle" | i18n }} - +

    {{ "twoFactorRecoveryYourCode" | i18n }}:

    diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.html b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.html index c9214d59caa..a31d4c33458 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.html +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.html @@ -1,9 +1,9 @@
    - - - {{ "twoStepLogin" | i18n }} - {{ "authenticatorAppTitle" | i18n }} - + diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-duo.component.html b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-duo.component.html index f20bd4f5f70..3a62ec91a8e 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-duo.component.html +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-duo.component.html @@ -1,9 +1,5 @@ - - - {{ "twoStepLogin" | i18n }} - Duo - + diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-email.component.html b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-email.component.html index f79a3bc7b0a..5861d1fba4f 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-email.component.html +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-email.component.html @@ -1,9 +1,5 @@ - - - {{ "twoStepLogin" | i18n }} - {{ "emailTitle" | i18n }} - + 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 0a2eb346b1b..4d505161631 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 @@ -1,9 +1,9 @@ - - - {{ "twoStepLogin" | i18n }} - {{ "webAuthnTitle" | i18n }} - + -

    {{ "twoFactorWebAuthnWarning" | i18n }}

    -
      -
    • {{ "twoFactorWebAuthnSupportWeb" | i18n }}
    • -
    +

    {{ "twoFactorWebAuthnWarning1" | i18n }}

    FIDO2 WebAuthn logo
      @@ -66,16 +63,16 @@ type="button" [bitAction]="readKey" buttonType="secondary" - [disabled]="$any(readKeyBtn).loading || webAuthnListening || !keyIdAvailable" + [disabled]="$any(readKeyBtn).loading() || webAuthnListening || !keyIdAvailable" class="tw-mr-2" #readKeyBtn > {{ "readKey" | i18n }} - + - + {{ "twoFactorU2fWaiting" | i18n }}... 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 098669d7522..112982b4971 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 @@ -1,9 +1,5 @@ - - - {{ "twoStepLogin" | i18n }} - YubiKey - + -

      {{ "twoStepLoginRecoveryWarning" | i18n }}

      +

      {{ recoveryCodeWarningMessage }}

      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 3b20718873d..83c9ff23f3c 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 @@ -1,7 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { DialogRef } from "@angular/cdk/dialog"; -import { Component, OnDestroy, OnInit, Type, ViewChild, ViewContainerRef } from "@angular/core"; +import { Component, OnDestroy, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; import { first, firstValueFrom, @@ -14,7 +14,6 @@ import { } from "rxjs"; import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref"; -import { ModalService } from "@bitwarden/angular/services/modal.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; @@ -30,6 +29,8 @@ import { TwoFactorProviders } from "@bitwarden/common/auth/services/two-factor.s import { AuthResponse } from "@bitwarden/common/auth/types/auth-response"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { ProductTierType } from "@bitwarden/common/billing/enums"; +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 { DialogService } from "@bitwarden/components"; @@ -53,6 +54,7 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { organization: Organization; providers: any[] = []; canAccessPremium$: Observable; + recoveryCodeWarningMessage: string; showPolicyWarning = false; loading = true; modal: ModalRef; @@ -67,11 +69,12 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { constructor( protected dialogService: DialogService, protected apiService: ApiService, - protected modalService: ModalService, protected messagingService: MessagingService, protected policyService: PolicyService, billingAccountProfileStateService: BillingAccountProfileStateService, - private accountService: AccountService, + protected accountService: AccountService, + protected configService: ConfigService, + protected i18nService: I18nService, ) { this.canAccessPremium$ = this.accountService.activeAccount$.pipe( switchMap((account) => @@ -81,6 +84,8 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { } async ngOnInit() { + this.recoveryCodeWarningMessage = this.i18nService.t("yourSingleUseRecoveryCode"); + for (const key in TwoFactorProviders) { // eslint-disable-next-line if (!TwoFactorProviders.hasOwnProperty(key)) { @@ -268,13 +273,6 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { return type === TwoFactorProviderType.OrganizationDuo; } - protected async openModal(ref: ViewContainerRef, type: Type): Promise { - const [modal, childComponent] = await this.modalService.openViewRef(type, ref); - this.modal = modal; - - return childComponent; - } - protected updateStatus(enabled: boolean, type: TwoFactorProviderType) { if (!enabled && this.modal != null) { this.modal.close(); diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-verify.component.html b/apps/web/src/app/auth/settings/two-factor/two-factor-verify.component.html index 288a096c23f..d064de08e19 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-verify.component.html +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-verify.component.html @@ -1,9 +1,5 @@ - - - {{ "twoStepLogin" | i18n }} - {{ dialogTitle }} - + {{ "logInWithPasskey" | i18n }} - - - {{ "off" | i18n }} - {{ "ssoLoginIsRequired" | i18n }} - - - {{ - "on" | i18n - }} - {{ - "off" | i18n - }} + + + + {{ "off" | i18n }} - {{ "ssoLoginIsRequired" | i18n }} + + + + {{ "on" | i18n }} + + + {{ "off" | i18n }} + + - - {{ "beta" | i18n }} + {{ "beta" | i18n }} + diff --git a/apps/web/src/app/auth/sso-v1.component.ts b/apps/web/src/app/auth/sso-v1.component.ts index 8699ecf7b24..d664f5b890b 100644 --- a/apps/web/src/app/auth/sso-v1.component.ts +++ b/apps/web/src/app/auth/sso-v1.component.ts @@ -16,10 +16,10 @@ import { OrgDomainApiServiceAbstraction } from "@bitwarden/common/admin-console/ import { OrganizationDomainSsoDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-domain/responses/organization-domain-sso-details.response"; import { VerifiedOrganizationDomainSsoDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { HttpStatusCode } from "@bitwarden/common/enums"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -37,7 +37,6 @@ import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legac selector: "app-sso", templateUrl: "sso-v1.component.html", }) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil export class SsoComponentV1 extends BaseSsoComponent implements OnInit { protected formGroup = new FormGroup({ identifier: new FormControl(null, [Validators.required]), diff --git a/apps/web/src/app/auth/trial-initiation/trial-initiation.component.html b/apps/web/src/app/auth/trial-initiation/trial-initiation.component.html deleted file mode 100644 index 077836a7634..00000000000 --- a/apps/web/src/app/auth/trial-initiation/trial-initiation.component.html +++ /dev/null @@ -1,149 +0,0 @@ - - - -
      -

      {{ "createAccount" | i18n }}

      -
      - -
      -
      -
      -
      -
      -
      - Bitwarden - -
      - - - - - - - - - - - - - - -
      -
      -
      -
      -
      - -
      -
      -
      -
      -
      -

      - {{ freeTrialText }} -

      - -
      - - - - - - - - - - - - - - -
      - - -
      -
      -
      -
      -
      -
      -
      -
      -
      diff --git a/apps/web/src/app/auth/trial-initiation/trial-initiation.component.spec.ts b/apps/web/src/app/auth/trial-initiation/trial-initiation.component.spec.ts deleted file mode 100644 index 61fc7a60035..00000000000 --- a/apps/web/src/app/auth/trial-initiation/trial-initiation.component.spec.ts +++ /dev/null @@ -1,336 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { StepperSelectionEvent } from "@angular/cdk/stepper"; -import { TitleCasePipe } from "@angular/common"; -import { NO_ERRORS_SCHEMA } from "@angular/core"; -import { ComponentFixture, fakeAsync, TestBed, tick } from "@angular/core/testing"; -import { FormBuilder, UntypedFormBuilder } from "@angular/forms"; -import { ActivatedRoute, Router } from "@angular/router"; -import { RouterTestingModule } from "@angular/router/testing"; -import { mock, MockProxy } from "jest-mock-extended"; -import { BehaviorSubject, of } from "rxjs"; - -import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; -import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; -import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; -import { OrganizationBillingServiceAbstraction as OrganizationBillingService } from "@bitwarden/common/billing/abstractions/organization-billing.service"; -import { PlanType } from "@bitwarden/common/billing/enums"; -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 { StateService } from "@bitwarden/common/platform/abstractions/state.service"; - -import { RouterService } from "../../core"; -import { SharedModule } from "../../shared"; -import { AcceptOrganizationInviteService } from "../organization-invite/accept-organization.service"; -import { OrganizationInvite } from "../organization-invite/organization-invite"; - -import { TrialInitiationComponent } from "./trial-initiation.component"; -import { VerticalStepperComponent } from "./vertical-stepper/vertical-stepper.component"; - -describe("TrialInitiationComponent", () => { - let component: TrialInitiationComponent; - let fixture: ComponentFixture; - const mockQueryParams = new BehaviorSubject({ org: "enterprise" }); - const testOrgId = "91329456-5b9f-44b3-9279-6bb9ee6a0974"; - const formBuilder: FormBuilder = new FormBuilder(); - let routerSpy: jest.SpyInstance; - - let stateServiceMock: MockProxy; - let policyApiServiceMock: MockProxy; - let policyServiceMock: MockProxy; - let routerServiceMock: MockProxy; - let acceptOrgInviteServiceMock: MockProxy; - let organizationBillingServiceMock: MockProxy; - let configServiceMock: MockProxy; - - beforeEach(() => { - // only define services directly that we want to mock return values in this component - stateServiceMock = mock(); - policyApiServiceMock = mock(); - policyServiceMock = mock(); - routerServiceMock = mock(); - acceptOrgInviteServiceMock = mock(); - organizationBillingServiceMock = mock(); - configServiceMock = mock(); - - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - TestBed.configureTestingModule({ - imports: [ - SharedModule, - RouterTestingModule.withRoutes([ - { path: "trial", component: TrialInitiationComponent }, - { - path: `organizations/${testOrgId}/vault`, - component: BlankComponent, - }, - { - path: `organizations/${testOrgId}/members`, - component: BlankComponent, - }, - ]), - ], - declarations: [TrialInitiationComponent, I18nPipe], - providers: [ - UntypedFormBuilder, - { - provide: ActivatedRoute, - useValue: { - queryParams: mockQueryParams.asObservable(), - }, - }, - { provide: StateService, useValue: stateServiceMock }, - { provide: PolicyService, useValue: policyServiceMock }, - { provide: PolicyApiServiceAbstraction, useValue: policyApiServiceMock }, - { provide: LogService, useValue: mock() }, - { provide: I18nService, useValue: mock() }, - { provide: TitleCasePipe, useValue: mock() }, - { - provide: VerticalStepperComponent, - useClass: VerticalStepperStubComponent, - }, - { - provide: RouterService, - useValue: routerServiceMock, - }, - { - provide: AcceptOrganizationInviteService, - useValue: acceptOrgInviteServiceMock, - }, - { - provide: OrganizationBillingService, - useValue: organizationBillingServiceMock, - }, - { - provide: ConfigService, - useValue: configServiceMock, - }, - ], - schemas: [NO_ERRORS_SCHEMA], // Allows child components to be ignored (such as register component) - }).compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(TrialInitiationComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it("should create", () => { - expect(component).toBeTruthy(); - }); - - // These tests demonstrate mocking service calls - describe("onInit() enforcedPolicyOptions", () => { - it("should not set enforcedPolicyOptions if there isn't an org invite in deep linked url", async () => { - acceptOrgInviteServiceMock.getOrganizationInvite.mockResolvedValueOnce(null); - // Need to recreate component with new service mock - fixture = TestBed.createComponent(TrialInitiationComponent); - component = fixture.componentInstance; - await component.ngOnInit(); - - expect(component.enforcedPolicyOptions).toBe(undefined); - }); - it("should set enforcedPolicyOptions if the deep linked url has an org invite", async () => { - // Set up service method mocks - acceptOrgInviteServiceMock.getOrganizationInvite.mockResolvedValueOnce({ - organizationId: testOrgId, - token: "token", - email: "testEmail", - organizationUserId: "123", - } as OrganizationInvite); - policyApiServiceMock.getPoliciesByToken.mockReturnValueOnce( - Promise.resolve([ - { - id: "345", - organizationId: testOrgId, - type: 1, - data: { - minComplexity: 4, - minLength: 10, - requireLower: null, - requireNumbers: null, - requireSpecial: null, - requireUpper: null, - }, - enabled: true, - }, - ] as Policy[]), - ); - policyServiceMock.masterPasswordPolicyOptions$.mockReturnValue( - of({ - minComplexity: 4, - minLength: 10, - requireLower: null, - requireNumbers: null, - requireSpecial: null, - requireUpper: null, - } as MasterPasswordPolicyOptions), - ); - - // Need to recreate component with new service mocks - fixture = TestBed.createComponent(TrialInitiationComponent); - component = fixture.componentInstance; - await component.ngOnInit(); - expect(component.enforcedPolicyOptions).toMatchObject({ - minComplexity: 4, - minLength: 10, - requireLower: null, - requireNumbers: null, - requireSpecial: null, - requireUpper: null, - }); - }); - }); - - // These tests demonstrate route params - describe("Route params", () => { - it("should set org variable to be enterprise and plan to EnterpriseAnnually if org param is enterprise", fakeAsync(() => { - mockQueryParams.next({ org: "enterprise" }); - tick(); // wait for resolution - fixture = TestBed.createComponent(TrialInitiationComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - expect(component.org).toBe("enterprise"); - expect(component.plan).toBe(PlanType.EnterpriseAnnually); - })); - it("should not set org variable if no org param is provided", fakeAsync(() => { - mockQueryParams.next({}); - tick(); // wait for resolution - fixture = TestBed.createComponent(TrialInitiationComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - expect(component.org).toBe(""); - expect(component.accountCreateOnly).toBe(true); - })); - it("should not set the org if org param is invalid ", fakeAsync(async () => { - mockQueryParams.next({ org: "hahahaha" }); - tick(); // wait for resolution - fixture = TestBed.createComponent(TrialInitiationComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - expect(component.org).toBe(""); - expect(component.accountCreateOnly).toBe(true); - })); - it("should set the layout variable if layout param is valid ", fakeAsync(async () => { - mockQueryParams.next({ layout: "teams1" }); - tick(); // wait for resolution - fixture = TestBed.createComponent(TrialInitiationComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - expect(component.layout).toBe("teams1"); - expect(component.accountCreateOnly).toBe(false); - })); - it("should not set the layout variable and leave as 'default' if layout param is invalid ", fakeAsync(async () => { - mockQueryParams.next({ layout: "asdfasdf" }); - tick(); // wait for resolution - fixture = TestBed.createComponent(TrialInitiationComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - component.ngOnInit(); - expect(component.layout).toBe("default"); - expect(component.accountCreateOnly).toBe(true); - })); - }); - - // These tests demonstrate the use of a stub component - describe("createAccount()", () => { - beforeEach(() => { - component.verticalStepper = TestBed.createComponent(VerticalStepperStubComponent) - .componentInstance as VerticalStepperComponent; - }); - - it("should set email and call verticalStepper.next()", fakeAsync(() => { - const verticalStepperNext = jest.spyOn(component.verticalStepper, "next"); - component.createdAccount("test@email.com"); - expect(verticalStepperNext).toHaveBeenCalled(); - expect(component.email).toBe("test@email.com"); - })); - }); - - describe("billingSuccess()", () => { - beforeEach(() => { - component.verticalStepper = TestBed.createComponent(VerticalStepperStubComponent) - .componentInstance as VerticalStepperComponent; - }); - - it("should set orgId and call verticalStepper.next()", () => { - const verticalStepperNext = jest.spyOn(component.verticalStepper, "next"); - component.billingSuccess({ orgId: testOrgId }); - expect(verticalStepperNext).toHaveBeenCalled(); - expect(component.orgId).toBe(testOrgId); - }); - }); - - describe("stepSelectionChange()", () => { - beforeEach(() => { - component.verticalStepper = TestBed.createComponent(VerticalStepperStubComponent) - .componentInstance as VerticalStepperComponent; - }); - - it("on step 2 should show organization copy text", () => { - component.stepSelectionChange({ - selectedIndex: 1, - previouslySelectedIndex: 0, - } as StepperSelectionEvent); - - expect(component.orgInfoSubLabel).toContain("Enter your"); - expect(component.orgInfoSubLabel).toContain(" organization information"); - }); - it("going from step 2 to 3 should set the orgInforSubLabel to be the Org name from orgInfoFormGroup", () => { - component.orgInfoFormGroup = formBuilder.group({ - name: ["Hooli"], - email: [""], - }); - component.stepSelectionChange({ - selectedIndex: 2, - previouslySelectedIndex: 1, - } as StepperSelectionEvent); - - expect(component.orgInfoSubLabel).toContain("Hooli"); - }); - }); - - describe("previousStep()", () => { - beforeEach(() => { - component.verticalStepper = TestBed.createComponent(VerticalStepperStubComponent) - .componentInstance as VerticalStepperComponent; - }); - - it("should call verticalStepper.previous()", fakeAsync(() => { - const verticalStepperPrevious = jest.spyOn(component.verticalStepper, "previous"); - component.previousStep(); - expect(verticalStepperPrevious).toHaveBeenCalled(); - })); - }); - - // These tests demonstrate router navigation - describe("navigation methods", () => { - beforeEach(() => { - component.orgId = testOrgId; - const router = TestBed.inject(Router); - fixture.detectChanges(); - routerSpy = jest.spyOn(router, "navigate"); - }); - describe("navigateToOrgVault", () => { - it("should call verticalStepper.previous()", fakeAsync(() => { - component.navigateToOrgVault(); - expect(routerSpy).toHaveBeenCalledWith(["organizations", testOrgId, "vault"]); - })); - }); - describe("navigateToOrgVault", () => { - it("should call verticalStepper.previous()", fakeAsync(() => { - component.navigateToOrgInvite(); - expect(routerSpy).toHaveBeenCalledWith(["organizations", testOrgId, "members"]); - })); - }); - }); -}); - -export class VerticalStepperStubComponent extends VerticalStepperComponent {} -export class BlankComponent {} // For router tests diff --git a/apps/web/src/app/auth/trial-initiation/trial-initiation.component.ts b/apps/web/src/app/auth/trial-initiation/trial-initiation.component.ts deleted file mode 100644 index fbe3eb7aa6d..00000000000 --- a/apps/web/src/app/auth/trial-initiation/trial-initiation.component.ts +++ /dev/null @@ -1,353 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { StepperSelectionEvent } from "@angular/cdk/stepper"; -import { TitleCasePipe } from "@angular/common"; -import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core"; -import { UntypedFormBuilder, Validators } from "@angular/forms"; -import { ActivatedRoute, Router } from "@angular/router"; -import { Subject, takeUntil } from "rxjs"; - -import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; -import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; -import { - OrganizationInformation, - PlanInformation, - OrganizationBillingServiceAbstraction as OrganizationBillingService, -} from "@bitwarden/common/billing/abstractions/organization-billing.service"; -import { PlanType, ProductTierType } from "@bitwarden/common/billing/enums"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ReferenceEventRequest } from "@bitwarden/common/models/request/reference-event.request"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; - -import { - OrganizationCreatedEvent, - SubscriptionProduct, - TrialOrganizationType, -} from "../../billing/accounts/trial-initiation/trial-billing-step.component"; -import { AcceptOrganizationInviteService } from "../organization-invite/accept-organization.service"; -import { OrganizationInvite } from "../organization-invite/organization-invite"; - -import { RouterService } from "./../../core/router.service"; -import { VerticalStepperComponent } from "./vertical-stepper/vertical-stepper.component"; - -export enum ValidOrgParams { - families = "families", - enterprise = "enterprise", - teams = "teams", - teamsStarter = "teamsStarter", - individual = "individual", - premium = "premium", - free = "free", -} - -enum ValidLayoutParams { - default = "default", - teams = "teams", - teams1 = "teams1", - teams2 = "teams2", - teams3 = "teams3", - enterprise = "enterprise", - enterprise1 = "enterprise1", - enterprise2 = "enterprise2", - cnetcmpgnent = "cnetcmpgnent", - cnetcmpgnind = "cnetcmpgnind", - cnetcmpgnteams = "cnetcmpgnteams", - abmenterprise = "abmenterprise", - abmteams = "abmteams", - secretsManager = "secretsManager", -} - -@Component({ - selector: "app-trial", - templateUrl: "trial-initiation.component.html", -}) -export class TrialInitiationComponent implements OnInit, OnDestroy { - email = ""; - fromOrgInvite = false; - org = ""; - orgInfoSubLabel = ""; - orgId = ""; - orgLabel = ""; - billingSubLabel = ""; - layout = "default"; - plan: PlanType; - productTier: ProductTierType; - accountCreateOnly = true; - useTrialStepper = false; - loading = false; - policies: Policy[]; - enforcedPolicyOptions: MasterPasswordPolicyOptions; - trialFlowOrgs: string[] = [ - ValidOrgParams.teams, - ValidOrgParams.teamsStarter, - ValidOrgParams.enterprise, - ValidOrgParams.families, - ]; - routeFlowOrgs: string[] = [ - ValidOrgParams.free, - ValidOrgParams.premium, - ValidOrgParams.individual, - ]; - layouts = ValidLayoutParams; - referenceData: ReferenceEventRequest; - @ViewChild("stepper", { static: false }) verticalStepper: VerticalStepperComponent; - - orgInfoFormGroup = this.formBuilder.group({ - name: ["", { validators: [Validators.required, Validators.maxLength(50)], updateOn: "change" }], - email: [""], - }); - - private set referenceDataId(referenceId: string) { - if (referenceId != null) { - this.referenceData.id = referenceId; - } else { - this.referenceData.id = ("; " + document.cookie) - .split("; reference=") - .pop() - .split(";") - .shift(); - } - - if (this.referenceData.id === "") { - this.referenceData.id = null; - } else { - // Matches "_ga_QBRN562QQQ=value1.value2.session" and captures values and session. - const regex = /_ga_QBRN562QQQ=([^.]+)\.([^.]+)\.(\d+)/; - const match = document.cookie.match(regex); - if (match) { - this.referenceData.session = match[3]; - } - } - } - - private destroy$ = new Subject(); - protected enableTrialPayment$ = this.configService.getFeatureFlag$( - FeatureFlag.TrialPaymentOptional, - ); - - constructor( - private route: ActivatedRoute, - protected router: Router, - private formBuilder: UntypedFormBuilder, - private titleCasePipe: TitleCasePipe, - private logService: LogService, - private policyApiService: PolicyApiServiceAbstraction, - private policyService: PolicyService, - private i18nService: I18nService, - private routerService: RouterService, - private acceptOrgInviteService: AcceptOrganizationInviteService, - private organizationBillingService: OrganizationBillingService, - private configService: ConfigService, - ) {} - - async ngOnInit(): Promise { - this.route.queryParams.pipe(takeUntil(this.destroy$)).subscribe((qParams) => { - this.referenceData = new ReferenceEventRequest(); - if (qParams.email != null && qParams.email.indexOf("@") > -1) { - this.email = qParams.email; - this.fromOrgInvite = qParams.fromOrgInvite === "true"; - } - - this.referenceDataId = qParams.reference; - - if (Object.values(ValidLayoutParams).includes(qParams.layout)) { - this.layout = qParams.layout; - this.accountCreateOnly = false; - } - - if (this.trialFlowOrgs.includes(qParams.org)) { - this.org = qParams.org; - this.orgLabel = this.titleCasePipe.transform(this.orgDisplayName); - this.useTrialStepper = true; - this.referenceData.flow = qParams.org; - - if (this.org === ValidOrgParams.families) { - this.plan = PlanType.FamiliesAnnually; - this.productTier = ProductTierType.Families; - } else if (this.org === ValidOrgParams.teamsStarter) { - this.plan = PlanType.TeamsStarter; - this.productTier = ProductTierType.TeamsStarter; - } else if (this.org === ValidOrgParams.teams) { - this.plan = PlanType.TeamsAnnually; - this.productTier = ProductTierType.Teams; - } else if (this.org === ValidOrgParams.enterprise) { - this.plan = PlanType.EnterpriseAnnually; - this.productTier = ProductTierType.Enterprise; - } - } else if (this.routeFlowOrgs.includes(qParams.org)) { - this.referenceData.flow = qParams.org; - const route = this.router.createUrlTree(["create-organization"], { - queryParams: { plan: qParams.org }, - }); - this.routerService.setPreviousUrl(route.toString()); - } - - // Are they coming from an email for sponsoring a families organization - // After logging in redirect them to setup the families sponsorship - this.setupFamilySponsorship(qParams.sponsorshipToken); - - this.referenceData.initiationPath = this.accountCreateOnly - ? "Registration form" - : "Password Manager trial from marketing website"; - }); - - // If there's a deep linked org invite, use it to get the password policies - const orgInvite = await this.acceptOrgInviteService.getOrganizationInvite(); - if (orgInvite != null) { - await this.initPasswordPolicies(orgInvite); - } - - this.orgInfoFormGroup.controls.name.valueChanges - .pipe(takeUntil(this.destroy$)) - .subscribe(() => { - this.orgInfoFormGroup.controls.name.markAsTouched(); - }); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - stepSelectionChange(event: StepperSelectionEvent) { - // Set org info sub label - if (event.selectedIndex === 1 && this.orgInfoFormGroup.controls.name.value === "") { - this.orgInfoSubLabel = - "Enter your " + - this.titleCasePipe.transform(this.orgDisplayName) + - " organization information"; - } else if (event.previouslySelectedIndex === 1) { - this.orgInfoSubLabel = this.orgInfoFormGroup.controls.name.value; - } - - //set billing sub label - if (event.selectedIndex === 2) { - this.billingSubLabel = this.i18nService.t("billingTrialSubLabel"); - } - } - - async createOrganizationOnTrial() { - this.loading = true; - const organization: OrganizationInformation = { - name: this.orgInfoFormGroup.get("name").value, - billingEmail: this.orgInfoFormGroup.get("email").value, - initiationPath: "Password Manager trial from marketing website", - }; - - const plan: PlanInformation = { - type: this.plan, - passwordManagerSeats: 1, - }; - - const response = await this.organizationBillingService.purchaseSubscriptionNoPaymentMethod({ - organization, - plan, - }); - - this.orgId = response?.id; - this.billingSubLabel = `${this.i18nService.t("annual")} ($0/${this.i18nService.t("yr")})`; - this.loading = false; - this.verticalStepper.next(); - } - - createdAccount(email: string) { - this.email = email; - this.orgInfoFormGroup.get("email")?.setValue(email); - this.verticalStepper.next(); - } - - billingSuccess(event: any) { - this.orgId = event?.orgId; - this.billingSubLabel = event?.subLabelText; - this.verticalStepper.next(); - } - - createdOrganization(event: OrganizationCreatedEvent) { - this.orgId = event.organizationId; - this.billingSubLabel = event.planDescription; - this.verticalStepper.next(); - } - - navigateToOrgVault() { - // 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(["organizations", this.orgId, "vault"]); - } - - navigateToOrgInvite() { - // 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(["organizations", this.orgId, "members"]); - } - - previousStep() { - this.verticalStepper.previous(); - } - - get orgDisplayName() { - if (this.org === "teamsStarter") { - return "Teams Starter"; - } - - return this.org; - } - - get freeTrialText() { - const translationKey = - this.layout === this.layouts.secretsManager - ? "startYour7DayFreeTrialOfBitwardenSecretsManagerFor" - : "startYour7DayFreeTrialOfBitwardenFor"; - - return this.i18nService.t(translationKey, this.org); - } - - get trialOrganizationType(): TrialOrganizationType { - switch (this.productTier) { - case ProductTierType.Free: - return null; - default: - return this.productTier; - } - } - - private setupFamilySponsorship(sponsorshipToken: string) { - if (sponsorshipToken != null) { - const route = this.router.createUrlTree(["setup/families-for-enterprise"], { - queryParams: { plan: sponsorshipToken }, - }); - this.routerService.setPreviousUrl(route.toString()); - } - } - - private async initPasswordPolicies(invite: OrganizationInvite): Promise { - if (invite == null) { - return; - } - - try { - this.policies = await this.policyApiService.getPoliciesByToken( - invite.organizationId, - invite.token, - invite.email, - invite.organizationUserId, - ); - } catch (e) { - this.logService.error(e); - } - - if (this.policies != null) { - this.policyService - .masterPasswordPolicyOptions$(this.policies) - .pipe(takeUntil(this.destroy$)) - .subscribe((enforcedPasswordPolicyOptions) => { - this.enforcedPolicyOptions = enforcedPasswordPolicyOptions; - }); - } - } - - protected readonly SubscriptionProduct = SubscriptionProduct; -} diff --git a/apps/web/src/app/auth/two-factor-auth-duo.component.ts b/apps/web/src/app/auth/two-factor-auth-duo.component.ts deleted file mode 100644 index b82632008bd..00000000000 --- a/apps/web/src/app/auth/two-factor-auth-duo.component.ts +++ /dev/null @@ -1,65 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { DialogModule } from "@angular/cdk/dialog"; -import { CommonModule } from "@angular/common"; -import { Component, OnDestroy, OnInit } from "@angular/core"; -import { ReactiveFormsModule, FormsModule } from "@angular/forms"; - -import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; - -import { TwoFactorAuthDuoComponent as TwoFactorAuthDuoBaseComponent } from "../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-duo.component"; -import { AsyncActionsModule } from "../../../../../libs/components/src/async-actions"; -import { ButtonModule } from "../../../../../libs/components/src/button"; -import { FormFieldModule } from "../../../../../libs/components/src/form-field"; -import { LinkModule } from "../../../../../libs/components/src/link"; -import { TypographyModule } from "../../../../../libs/components/src/typography"; - -@Component({ - standalone: true, - selector: "app-two-factor-auth-duo", - templateUrl: - "../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-duo.component.html", - imports: [ - CommonModule, - JslibModule, - DialogModule, - ButtonModule, - LinkModule, - TypographyModule, - ReactiveFormsModule, - FormFieldModule, - AsyncActionsModule, - FormsModule, - ], - providers: [I18nPipe], -}) -export class TwoFactorAuthDuoComponent - extends TwoFactorAuthDuoBaseComponent - implements OnInit, OnDestroy -{ - async ngOnInit(): Promise { - await super.ngOnInit(); - } - - private duoResultChannel: BroadcastChannel; - - protected override setupDuoResultListener() { - if (!this.duoResultChannel) { - this.duoResultChannel = new BroadcastChannel("duoResult"); - this.duoResultChannel.addEventListener("message", this.handleDuoResultMessage); - } - } - - private handleDuoResultMessage = async (msg: { data: { code: string; state: string } }) => { - this.token.emit(msg.data.code + "|" + msg.data.state); - }; - - async ngOnDestroy() { - if (this.duoResultChannel) { - // clean up duo listener if it was initialized. - this.duoResultChannel.removeEventListener("message", this.handleDuoResultMessage); - this.duoResultChannel.close(); - } - } -} diff --git a/apps/web/src/app/auth/two-factor-auth.component.ts b/apps/web/src/app/auth/two-factor-auth.component.ts deleted file mode 100644 index 18660b2ca63..00000000000 --- a/apps/web/src/app/auth/two-factor-auth.component.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { DialogModule } from "@angular/cdk/dialog"; -import { CommonModule } from "@angular/common"; -import { Component, Inject } from "@angular/core"; -import { FormBuilder, ReactiveFormsModule } from "@angular/forms"; -import { ActivatedRoute, Router, RouterLink } from "@angular/router"; - -import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; -import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; -import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; -import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { - LinkModule, - TypographyModule, - CheckboxModule, - DialogService, - ToastService, -} from "@bitwarden/components"; - -import { TwoFactorAuthAuthenticatorComponent } from "../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-authenticator.component"; -import { TwoFactorAuthEmailComponent } from "../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-email.component"; -import { TwoFactorAuthWebAuthnComponent } from "../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-webauthn.component"; -import { TwoFactorAuthYubikeyComponent } from "../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-yubikey.component"; -import { TwoFactorAuthComponent as BaseTwoFactorAuthComponent } from "../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component"; -import { TwoFactorOptionsComponent } from "../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-options.component"; -import { - LoginStrategyServiceAbstraction, - LoginEmailServiceAbstraction, - UserDecryptionOptionsServiceAbstraction, -} from "../../../../../libs/auth/src/common/abstractions"; -import { AsyncActionsModule } from "../../../../../libs/components/src/async-actions"; -import { ButtonModule } from "../../../../../libs/components/src/button"; -import { FormFieldModule } from "../../../../../libs/components/src/form-field"; - -import { TwoFactorAuthDuoComponent } from "./two-factor-auth-duo.component"; - -@Component({ - standalone: true, - templateUrl: - "../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.html", - selector: "app-two-factor-auth", - imports: [ - CommonModule, - JslibModule, - DialogModule, - ButtonModule, - LinkModule, - TypographyModule, - ReactiveFormsModule, - FormFieldModule, - AsyncActionsModule, - RouterLink, - CheckboxModule, - TwoFactorOptionsComponent, - TwoFactorAuthEmailComponent, - TwoFactorAuthAuthenticatorComponent, - TwoFactorAuthYubikeyComponent, - TwoFactorAuthDuoComponent, - TwoFactorAuthWebAuthnComponent, - ], - providers: [I18nPipe], -}) -export class TwoFactorAuthComponent extends BaseTwoFactorAuthComponent { - constructor( - protected loginStrategyService: LoginStrategyServiceAbstraction, - protected router: Router, - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - environmentService: EnvironmentService, - dialogService: DialogService, - protected route: ActivatedRoute, - logService: LogService, - protected twoFactorService: TwoFactorService, - loginEmailService: LoginEmailServiceAbstraction, - userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, - protected ssoLoginService: SsoLoginServiceAbstraction, - protected configService: ConfigService, - masterPasswordService: InternalMasterPasswordServiceAbstraction, - accountService: AccountService, - formBuilder: FormBuilder, - @Inject(WINDOW) protected win: Window, - toastService: ToastService, - ) { - super( - loginStrategyService, - router, - i18nService, - platformUtilsService, - environmentService, - dialogService, - route, - logService, - twoFactorService, - loginEmailService, - userDecryptionOptionsService, - ssoLoginService, - configService, - masterPasswordService, - accountService, - formBuilder, - win, - toastService, - ); - this.onSuccessfulLoginNavigate = this.goAfterLogIn; - } - - protected override handleMigrateEncryptionKey(result: AuthResult): boolean { - if (!result.requiresEncryptionKeyMigration) { - return false; - } - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["migrate-legacy-encryption"]); - return true; - } -} diff --git a/apps/web/src/app/auth/two-factor-options.component.html b/apps/web/src/app/auth/two-factor-options-v1.component.html similarity index 100% rename from apps/web/src/app/auth/two-factor-options.component.html rename to apps/web/src/app/auth/two-factor-options-v1.component.html diff --git a/apps/web/src/app/auth/two-factor-options.component.ts b/apps/web/src/app/auth/two-factor-options-v1.component.ts similarity index 84% rename from apps/web/src/app/auth/two-factor-options.component.ts rename to apps/web/src/app/auth/two-factor-options-v1.component.ts index fef15799df5..08665dcfcdd 100644 --- a/apps/web/src/app/auth/two-factor-options.component.ts +++ b/apps/web/src/app/auth/two-factor-options-v1.component.ts @@ -2,7 +2,7 @@ import { DialogRef } from "@angular/cdk/dialog"; import { Component } from "@angular/core"; import { Router } from "@angular/router"; -import { TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent } from "@bitwarden/angular/auth/components/two-factor-options.component"; +import { TwoFactorOptionsComponentV1 as BaseTwoFactorOptionsComponentV1 } from "@bitwarden/angular/auth/components/two-factor-options-v1.component"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; @@ -22,9 +22,9 @@ export type TwoFactorOptionsDialogResultType = { @Component({ selector: "app-two-factor-options", - templateUrl: "two-factor-options.component.html", + templateUrl: "two-factor-options-v1.component.html", }) -export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent { +export class TwoFactorOptionsComponentV1 extends BaseTwoFactorOptionsComponentV1 { constructor( twoFactorService: TwoFactorService, router: Router, @@ -47,6 +47,6 @@ export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent { } static open(dialogService: DialogService) { - return dialogService.open(TwoFactorOptionsComponent); + return dialogService.open(TwoFactorOptionsComponentV1); } } diff --git a/apps/web/src/app/auth/two-factor.component.html b/apps/web/src/app/auth/two-factor-v1.component.html similarity index 100% rename from apps/web/src/app/auth/two-factor.component.html rename to apps/web/src/app/auth/two-factor-v1.component.html diff --git a/apps/web/src/app/auth/two-factor.component.ts b/apps/web/src/app/auth/two-factor-v1.component.ts similarity index 91% rename from apps/web/src/app/auth/two-factor.component.ts rename to apps/web/src/app/auth/two-factor-v1.component.ts index eead66468fd..9a9fab02de3 100644 --- a/apps/web/src/app/auth/two-factor.component.ts +++ b/apps/web/src/app/auth/two-factor-v1.component.ts @@ -5,7 +5,7 @@ import { FormBuilder, Validators } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; import { Subject, takeUntil, lastValueFrom } from "rxjs"; -import { TwoFactorComponent as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor.component"; +import { TwoFactorComponentV1 as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor-v1.component"; import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; import { LoginStrategyServiceAbstraction, @@ -14,10 +14,10 @@ import { } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; @@ -29,16 +29,15 @@ import { DialogService, ToastService } from "@bitwarden/components"; import { TwoFactorOptionsDialogResult, - TwoFactorOptionsComponent, + TwoFactorOptionsComponentV1, TwoFactorOptionsDialogResultType, -} from "./two-factor-options.component"; +} from "./two-factor-options-v1.component"; @Component({ selector: "app-two-factor", - templateUrl: "two-factor.component.html", + templateUrl: "two-factor-v1.component.html", }) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class TwoFactorComponent extends BaseTwoFactorComponent implements OnInit, OnDestroy { +export class TwoFactorComponentV1 extends BaseTwoFactorComponent implements OnInit, OnDestroy { @ViewChild("twoFactorOptions", { read: ViewContainerRef, static: true }) twoFactorOptionsModal: ViewContainerRef; formGroup = this.formBuilder.group({ @@ -110,7 +109,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent implements OnInit }; async anotherMethod() { - const dialogRef = TwoFactorOptionsComponent.open(this.dialogService); + const dialogRef = TwoFactorOptionsComponentV1.open(this.dialogService); const response: TwoFactorOptionsDialogResultType = await lastValueFrom(dialogRef.closed); if (response.result === TwoFactorOptionsDialogResult.Provider) { this.selectedProviderType = response.type; diff --git a/apps/web/src/app/auth/verify-email-token.component.html b/apps/web/src/app/auth/verify-email-token.component.html index 39a7d4524f8..63437352e19 100644 --- a/apps/web/src/app/auth/verify-email-token.component.html +++ b/apps/web/src/app/auth/verify-email-token.component.html @@ -1,13 +1,6 @@ -
      -
      - -

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

      +
      + Bitwarden +
      +
      diff --git a/apps/web/src/app/auth/verify-email-token.component.ts b/apps/web/src/app/auth/verify-email-token.component.ts index 4ed5369773a..55569f44c10 100644 --- a/apps/web/src/app/auth/verify-email-token.component.ts +++ b/apps/web/src/app/auth/verify-email-token.component.ts @@ -16,7 +16,6 @@ import { ToastService } from "@bitwarden/components"; selector: "app-verify-email-token", templateUrl: "verify-email-token.component.html", }) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil export class VerifyEmailTokenComponent implements OnInit { constructor( private router: Router, diff --git a/apps/web/src/app/auth/verify-recover-delete.component.ts b/apps/web/src/app/auth/verify-recover-delete.component.ts index 725f012bf5e..8d95dd01b77 100644 --- a/apps/web/src/app/auth/verify-recover-delete.component.ts +++ b/apps/web/src/app/auth/verify-recover-delete.component.ts @@ -15,7 +15,6 @@ import { ToastService } from "@bitwarden/components"; selector: "app-verify-recover-delete", templateUrl: "verify-recover-delete.component.html", }) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil export class VerifyRecoverDeleteComponent implements OnInit { email: string; diff --git a/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.html b/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.html index 2f983944b70..212df558d94 100644 --- a/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.html +++ b/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.html @@ -1,6 +1,6 @@ @@ -19,7 +19,7 @@
    -

    +

    {{ "premiumPriceWithFamilyPlan" | i18n: (premiumPrice | currency: "$") : familyPlanMaxUserCount }} @@ -49,49 +49,28 @@ linkType="primary" routerLink="/create-organization" [queryParams]="{ plan: 'families' }" - >{{ "bitwardenFamiliesPlan" | i18n }} + {{ "bitwardenFamiliesPlan" | i18n }} +

    {{ "purchasePremium" | i18n }}
    - -

    {{ "uploadLicenseFilePremium" | i18n }}

    -
    - - {{ "licenseFile" | i18n }} -
    - - {{ this.licenseFile ? this.licenseFile.name : ("noFileChosen" | i18n) }} -
    - - {{ "licenseFileDesc" | i18n: "bitwarden_premium_license.json" }} -
    - -
    + + -
    +

    {{ "addons" | i18n }}

    @@ -106,7 +85,7 @@ /> {{ "additionalStorageIntervalDesc" - | i18n: "1 GB" : (storageGbPrice | currency: "$") : ("year" | i18n) + | i18n: "1 GB" : (storageGBPrice | currency: "$") : ("year" | i18n) }}
    @@ -114,30 +93,26 @@

    {{ "summary" | i18n }}

    {{ "premiumMembership" | i18n }}: {{ premiumPrice | currency: "$" }}
    - {{ "additionalStorageGb" | i18n }}: {{ additionalStorage || 0 }} GB × - {{ storageGbPrice | currency: "$" }} = - {{ additionalStorageTotal | currency: "$" }} + {{ "additionalStorageGb" | i18n }}: {{ addOnFormGroup.value.additionalStorage || 0 }} GB × + {{ storageGBPrice | currency: "$" }} = + {{ additionalStorageCost | currency: "$" }}

    {{ "paymentInformation" | i18n }}

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

    - {{ "total" | i18n }}: {{ total | currency: "USD $" }}/{{ "year" | i18n }} -

    -

    {{ "paymentChargedAnnually" | i18n }}

    - diff --git a/apps/web/src/app/billing/individual/premium/premium.component.ts b/apps/web/src/app/billing/individual/premium/premium.component.ts index f96f573cd4d..2934e69f0ad 100644 --- a/apps/web/src/app/billing/individual/premium/premium.component.ts +++ b/apps/web/src/app/billing/individual/premium/premium.component.ts @@ -1,187 +1,192 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Component, OnInit, ViewChild } from "@angular/core"; +import { Component, ViewChild } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormControl, FormGroup, Validators } from "@angular/forms"; -import { Router } from "@angular/router"; -import { firstValueFrom, Observable, switchMap } from "rxjs"; +import { ActivatedRoute, Router } from "@angular/router"; +import { combineLatest, concatMap, from, Observable, of, switchMap } from "rxjs"; import { debounceTime } from "rxjs/operators"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; -import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; +import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; import { PreviewIndividualInvoiceRequest } from "@bitwarden/common/billing/models/request/preview-individual-invoice.request"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; +import { SyncService } from "@bitwarden/common/platform/sync"; import { ToastService } from "@bitwarden/components"; -import { PaymentComponent, TaxInfoComponent } from "../../shared"; +import { PaymentComponent } from "../../shared/payment/payment.component"; +import { TaxInfoComponent } from "../../shared/tax-info.component"; @Component({ - templateUrl: "premium.component.html", + templateUrl: "./premium.component.html", }) -export class PremiumComponent implements OnInit { +export class PremiumComponent { @ViewChild(PaymentComponent) paymentComponent: PaymentComponent; @ViewChild(TaxInfoComponent) taxInfoComponent: TaxInfoComponent; - canAccessPremium$: Observable; - selfHosted = false; - premiumPrice = 10; - familyPlanMaxUserCount = 6; - storageGbPrice = 4; - cloudWebVaultUrl: string; - licenseFile: File = null; + protected hasPremiumFromAnyOrganization$: Observable; - formPromise: Promise; - protected licenseForm = new FormGroup({ - file: new FormControl(null, [Validators.required]), - }); - protected addonForm = new FormGroup({ - additionalStorage: new FormControl(0, [Validators.max(99), Validators.min(0)]), + protected addOnFormGroup = new FormGroup({ + additionalStorage: new FormControl(0, [Validators.min(0), Validators.max(99)]), }); - private estimatedTax: number = 0; + protected licenseFormGroup = new FormGroup({ + file: new FormControl(null, [Validators.required]), + }); + + protected cloudWebVaultURL: string; + protected isSelfHost = false; + + protected estimatedTax: number = 0; + protected readonly familyPlanMaxUserCount = 6; + protected readonly premiumPrice = 10; + protected readonly storageGBPrice = 4; constructor( + private activatedRoute: ActivatedRoute, private apiService: ApiService, + private billingAccountProfileStateService: BillingAccountProfileStateService, + private configService: ConfigService, + private environmentService: EnvironmentService, private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, - private tokenService: TokenService, private router: Router, - private messagingService: MessagingService, private syncService: SyncService, - private environmentService: EnvironmentService, - private billingAccountProfileStateService: BillingAccountProfileStateService, private toastService: ToastService, + private tokenService: TokenService, private taxService: TaxServiceAbstraction, private accountService: AccountService, ) { - this.selfHosted = platformUtilsService.isSelfHost(); - this.canAccessPremium$ = this.accountService.activeAccount$.pipe( + this.isSelfHost = this.platformUtilsService.isSelfHost(); + + this.hasPremiumFromAnyOrganization$ = this.accountService.activeAccount$.pipe( switchMap((account) => - this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), + this.billingAccountProfileStateService.hasPremiumFromAnyOrganization$(account.id), ), ); - this.addonForm.controls.additionalStorage.valueChanges + combineLatest([ + this.accountService.activeAccount$.pipe( + switchMap((account) => + this.billingAccountProfileStateService.hasPremiumPersonally$(account.id), + ), + ), + this.environmentService.cloudWebVaultUrl$, + ]) + .pipe( + takeUntilDestroyed(), + concatMap(([hasPremiumPersonally, cloudWebVaultURL]) => { + if (hasPremiumPersonally) { + return from(this.navigateToSubscriptionPage()); + } + + this.cloudWebVaultURL = cloudWebVaultURL; + return of(true); + }), + ) + .subscribe(); + + this.addOnFormGroup.controls.additionalStorage.valueChanges .pipe(debounceTime(1000), takeUntilDestroyed()) .subscribe(() => { this.refreshSalesTax(); }); } - protected setSelectedFile(event: Event) { - const fileInputEl = event.target; - const file: File = fileInputEl.files.length > 0 ? fileInputEl.files[0] : null; - this.licenseFile = file; - } - async ngOnInit() { - this.cloudWebVaultUrl = await firstValueFrom(this.environmentService.cloudWebVaultUrl$); - const account = await firstValueFrom(this.accountService.activeAccount$); - if ( - await firstValueFrom(this.billingAccountProfileStateService.hasPremiumPersonally$(account.id)) - ) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["/settings/subscription/user-subscription"]); - return; - } - } - submit = async () => { - if (this.taxInfoComponent) { - if (!this.taxInfoComponent?.taxFormGroup.valid) { - this.taxInfoComponent.taxFormGroup.markAllAsTouched(); - return; - } - } - this.licenseForm.markAllAsTouched(); - this.addonForm.markAllAsTouched(); - if (this.selfHosted) { - if (this.licenseFile == null) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("selectFile"), - }); - return; - } - } - if (this.selfHosted) { - // eslint-disable-next-line @typescript-eslint/no-misused-promises - if (!this.tokenService.getEmailVerified()) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("verifyEmailFirst"), - }); - return; - } - - const fd = new FormData(); - fd.append("license", this.licenseFile); - await this.apiService.postAccountLicense(fd).then(() => { - return this.finalizePremium(); - }); - } else { - await this.paymentComponent - .createPaymentToken() - .then((result) => { - const fd = new FormData(); - fd.append("paymentMethodType", result[1].toString()); - if (result[0] != null) { - fd.append("paymentToken", result[0]); - } - fd.append("additionalStorageGb", (this.additionalStorage || 0).toString()); - fd.append("country", this.taxInfoComponent?.taxFormGroup?.value.country); - fd.append("postalCode", this.taxInfoComponent?.taxFormGroup?.value.postalCode); - return this.apiService.postPremium(fd); - }) - .then((paymentResponse) => { - if (!paymentResponse.success && paymentResponse.paymentIntentClientSecret != null) { - return this.paymentComponent.handleStripeCardPayment( - paymentResponse.paymentIntentClientSecret, - () => this.finalizePremium(), - ); - } else { - return this.finalizePremium(); - } - }); - } - }; - - async finalizePremium() { + finalizeUpgrade = async () => { await this.apiService.refreshIdentityToken(); await this.syncService.fullSync(true); + }; + + postFinalizeUpgrade = async () => { this.toastService.showToast({ variant: "success", title: null, message: this.i18nService.t("premiumUpdated"), }); - await this.router.navigate(["/settings/subscription/user-subscription"]); + await this.navigateToSubscriptionPage(); + }; + + navigateToSubscriptionPage = (): Promise => + this.router.navigate(["../user-subscription"], { relativeTo: this.activatedRoute }); + + onLicenseFileSelected = (event: Event): void => { + const element = event.target as HTMLInputElement; + this.licenseFormGroup.value.file = element.files.length > 0 ? element.files[0] : null; + }; + + submitPremiumLicense = async (): Promise => { + this.licenseFormGroup.markAllAsTouched(); + + if (this.licenseFormGroup.invalid) { + return this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: this.i18nService.t("selectFile"), + }); + } + + const emailVerified = await this.tokenService.getEmailVerified(); + if (!emailVerified) { + return this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: this.i18nService.t("verifyEmailFirst"), + }); + } + + const formData = new FormData(); + formData.append("license", this.licenseFormGroup.value.file); + + await this.apiService.postAccountLicense(formData); + await this.finalizeUpgrade(); + await this.postFinalizeUpgrade(); + }; + + submitPayment = async (): Promise => { + this.taxInfoComponent.taxFormGroup.markAllAsTouched(); + if (this.taxInfoComponent.taxFormGroup.invalid) { + return; + } + + const { type, token } = await this.paymentComponent.tokenize(); + + const formData = new FormData(); + formData.append("paymentMethodType", type.toString()); + formData.append("paymentToken", token); + formData.append("additionalStorageGb", this.addOnFormGroup.value.additionalStorage.toString()); + formData.append("country", this.taxInfoComponent.country); + formData.append("postalCode", this.taxInfoComponent.postalCode); + + await this.apiService.postPremium(formData); + await this.finalizeUpgrade(); + await this.postFinalizeUpgrade(); + }; + + protected get additionalStorageCost(): number { + return this.storageGBPrice * this.addOnFormGroup.value.additionalStorage; } - get additionalStorage(): number { - return this.addonForm.get("additionalStorage").value; - } - get additionalStorageTotal(): number { - return this.storageGbPrice * Math.abs(this.additionalStorage || 0); + protected get premiumURL(): string { + return `${this.cloudWebVaultURL}/#/settings/subscription/premium`; } - get subtotal(): number { - return this.premiumPrice + this.additionalStorageTotal; + protected get subtotal(): number { + return this.premiumPrice + this.additionalStorageCost; } - get taxCharges(): number { - return this.estimatedTax; + protected get total(): number { + return this.subtotal + this.estimatedTax; } - get total(): number { - return this.subtotal + this.taxCharges || 0; + protected async onLicenseFileSelectedChanged(): Promise { + await this.postFinalizeUpgrade(); } private refreshSalesTax(): void { @@ -190,7 +195,7 @@ export class PremiumComponent implements OnInit { } const request: PreviewIndividualInvoiceRequest = { passwordManager: { - additionalStorage: this.addonForm.value.additionalStorage, + additionalStorage: this.addOnFormGroup.value.additionalStorage, }, taxInformation: { postalCode: this.taxInfoComponent.postalCode, diff --git a/apps/web/src/app/billing/individual/user-subscription.component.html b/apps/web/src/app/billing/individual/user-subscription.component.html index 1c1382cd816..e801237467a 100644 --- a/apps/web/src/app/billing/individual/user-subscription.component.html +++ b/apps/web/src/app/billing/individual/user-subscription.component.html @@ -27,7 +27,7 @@ #reinstateBtn (click)="reinstate()" [appApiAction]="reinstatePromise" - [disabled]="$any(reinstateBtn).loading" + [disabled]="$any(reinstateBtn).loading()" > {{ "reinstateSubscription" | i18n }} @@ -109,7 +109,7 @@ class="tw-ml-auto" (click)="cancelSubscription()" [appApiAction]="cancelPromise" - [disabled]="$any(cancelBtn).loading" + [disabled]="$any(cancelBtn).loading()" *ngIf="subscription && !subscription.cancelled && !subscriptionMarkedForCancel" > {{ "cancelSubscription" | i18n }} diff --git a/apps/web/src/app/billing/individual/user-subscription.component.ts b/apps/web/src/app/billing/individual/user-subscription.component.ts index 97b4725e6d7..38f4436fb47 100644 --- a/apps/web/src/app/billing/individual/user-subscription.component.ts +++ b/apps/web/src/app/billing/individual/user-subscription.component.ts @@ -8,8 +8,6 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { SubscriptionResponse } from "@bitwarden/common/billing/models/response/subscription.response"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -18,12 +16,8 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { DialogService, ToastService } from "@bitwarden/components"; import { - AdjustStorageDialogV2Component, - AdjustStorageDialogV2ResultType, -} from "../shared/adjust-storage-dialog/adjust-storage-dialog-v2.component"; -import { - AdjustStorageDialogResult, - openAdjustStorageDialog, + AdjustStorageDialogComponent, + AdjustStorageDialogResultType, } from "../shared/adjust-storage-dialog/adjust-storage-dialog.component"; import { OffboardingSurveyDialogResultType, @@ -45,10 +39,6 @@ export class UserSubscriptionComponent implements OnInit { cancelPromise: Promise; reinstatePromise: Promise; - protected deprecateStripeSourcesAPI$ = this.configService.getFeatureFlag$( - FeatureFlag.AC2476_DeprecateStripeSourcesAPI, - ); - constructor( private apiService: ApiService, private platformUtilsService: PlatformUtilsService, @@ -60,7 +50,6 @@ export class UserSubscriptionComponent implements OnInit { private environmentService: EnvironmentService, private billingAccountProfileStateService: BillingAccountProfileStateService, private toastService: ToastService, - private configService: ConfigService, private accountService: AccountService, ) { this.selfHosted = this.platformUtilsService.isSelfHost(); @@ -166,33 +155,18 @@ export class UserSubscriptionComponent implements OnInit { }; adjustStorage = async (add: boolean) => { - const deprecateStripeSourcesAPI = await firstValueFrom(this.deprecateStripeSourcesAPI$); + const dialogRef = AdjustStorageDialogComponent.open(this.dialogService, { + data: { + price: 4, + cadence: "year", + type: add ? "Add" : "Remove", + }, + }); - if (deprecateStripeSourcesAPI) { - const dialogRef = AdjustStorageDialogV2Component.open(this.dialogService, { - data: { - price: 4, - cadence: "year", - type: add ? "Add" : "Remove", - }, - }); + const result = await lastValueFrom(dialogRef.closed); - const result = await lastValueFrom(dialogRef.closed); - - if (result === AdjustStorageDialogV2ResultType.Submitted) { - await this.load(); - } - } else { - const dialogRef = openAdjustStorageDialog(this.dialogService, { - data: { - storageGbPrice: 4, - add: add, - }, - }); - const result = await lastValueFrom(dialogRef.closed); - if (result === AdjustStorageDialogResult.Adjusted) { - await this.load(); - } + if (result === AdjustStorageDialogResultType.Submitted) { + await this.load(); } }; diff --git a/apps/web/src/app/billing/organizations/adjust-subscription.component.ts b/apps/web/src/app/billing/organizations/adjust-subscription.component.ts index 0166560007e..786c25c8d4c 100644 --- a/apps/web/src/app/billing/organizations/adjust-subscription.component.ts +++ b/apps/web/src/app/billing/organizations/adjust-subscription.component.ts @@ -2,11 +2,16 @@ // @ts-strict-ignore import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; -import { Subject, takeUntil } from "rxjs"; +import { Subject, firstValueFrom, takeUntil } from "rxjs"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + InternalOrganizationServiceAbstraction, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationData } from "@bitwarden/common/admin-console/models/data/organization.data"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { OrganizationSubscriptionUpdateRequest } from "@bitwarden/common/billing/models/request/organization-subscription-update.request"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ToastService } from "@bitwarden/components"; @@ -37,6 +42,7 @@ export class AdjustSubscription implements OnInit, OnDestroy { private formBuilder: FormBuilder, private toastService: ToastService, private internalOrganizationService: InternalOrganizationServiceAbstraction, + private accountService: AccountService, ) {} ngOnInit() { @@ -73,14 +79,19 @@ export class AdjustSubscription implements OnInit, OnDestroy { request, ); - const organization = await this.internalOrganizationService.get(this.organizationId); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + const organization = await firstValueFrom( + this.internalOrganizationService + .organizations$(userId) + .pipe(getOrganizationById(this.organizationId)), + ); const organizationData = new OrganizationData(response, { isMember: organization.isMember, isProviderUser: organization.isProviderUser, }); - await this.internalOrganizationService.upsert(organizationData); + await this.internalOrganizationService.upsert(organizationData, userId); this.toastService.showToast({ variant: "success", 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 902cac9c771..64a694cdef0 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 @@ -30,7 +30,7 @@ }} -
    +
    -
    - /{{ "monthPerMember" | i18n }} + + /{{ + selectableProduct.productTier === productTypes.Families + ? "month" + : ("monthPerMember" | i18n) + }} @@ -316,7 +321,7 @@ -
    +

    - {{ - deprecateStripeSourcesAPI - ? paymentSource?.description - : billing?.paymentSource?.description - }} - + {{ paymentSource?.description }} + {{ "changePaymentMethod" | i18n }}

    - - + -
    +

    {{ "total" | i18n }}: @@ -381,8 +381,8 @@

    -
    - +
    +

    {{ "passwordManager" | i18n }}

    @@ -400,7 +400,7 @@ : selectedPlan.PasswordManager.basePrice ) | currency: "$" }} - /{{ "year" | i18n }} + /{{ selectedPlanInterval | i18n }} @@ -427,7 +427,7 @@ {{ "members" | i18n }} × {{ selectedPlan.PasswordManager.seatPrice | currency: "$" }} - /{{ "year" | i18n }} + /{{ selectedPlanInterval | i18n }} @@ -444,7 +444,7 @@ {{ "additionalStorageGbMessage" | i18n }} × {{ additionalStoragePriceMonthly(selectedPlan) | currency: "$" }} - /{{ "year" | i18n }} + /{{ selectedPlanInterval | i18n }} {{ additionalStorageTotal(selectedPlan) | currency: "$" }}

    @@ -485,7 +485,7 @@ : selectedPlan.SecretsManager.basePrice ) | currency: "$" }} - /{{ "year" | i18n }} + /{{ selectedPlanInterval | i18n }}

    {{ "members" | i18n }} × {{ selectedPlan.SecretsManager.seatPrice | currency: "$" }} - /{{ "year" | i18n }} + /{{ selectedPlanInterval | i18n }} @@ -524,7 +524,7 @@ {{ "serviceAccounts" | i18n | lowercase }} × {{ selectedPlan?.SecretsManager?.additionalPricePerServiceAccount | currency: "$" }} - /{{ "year" | i18n }} + /{{ selectedPlanInterval | i18n }} {{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }}

    @@ -549,7 +549,7 @@

    - +

    {{ "passwordManager" | i18n }}

    @@ -580,7 +580,7 @@ {{ "members" | i18n }} × {{ selectedPlan.PasswordManager.seatPrice | currency: "$" }} - /{{ "month" | i18n }} + /{{ selectedPlanInterval | i18n }} {{ passwordManagerSeatTotal(selectedPlan) | currency: "$" }} @@ -596,7 +596,7 @@ {{ "additionalStorageGbMessage" | i18n }} × {{ additionalStoragePriceMonthly(selectedPlan) | currency: "$" }} - /{{ "month" | i18n }} + /{{ selectedPlanInterval | i18n }} {{ storageGb * selectedPlan.PasswordManager.additionalStoragePricePerGb | currency: "$" @@ -656,7 +656,7 @@ {{ "members" | i18n }} × {{ selectedPlan.SecretsManager.seatPrice | currency: "$" }} - /{{ "month" | i18n }} + /{{ selectedPlanInterval | i18n }} {{ secretsManagerSeatTotal(selectedPlan, sub?.smSeats) | currency: "$" }} @@ -675,7 +675,7 @@ {{ "serviceAccounts" | i18n | lowercase }} × {{ selectedPlan.SecretsManager.additionalPricePerServiceAccount | currency: "$" }} - /{{ "month" | i18n }} + /{{ selectedPlanInterval | i18n }} {{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }}

    @@ -705,8 +705,8 @@
    -
    - +
    +

    {{ "secretsManager" | i18n }} @@ -725,7 +725,7 @@ : selectedPlan.SecretsManager.basePrice ) | currency: "$" }} - /{{ "year" | i18n }} + /{{ selectedPlanInterval | i18n }}

    {{ "members" | i18n }} × {{ selectedPlan.SecretsManager.seatPrice | currency: "$" }} - /{{ "year" | i18n }} + /{{ selectedPlanInterval | i18n }} @@ -764,7 +764,7 @@ {{ "serviceAccounts" | i18n }} × {{ selectedPlan.SecretsManager.additionalPricePerServiceAccount | currency: "$" }} - /{{ "year" | i18n }} + /{{ selectedPlanInterval | i18n }} {{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }}

    @@ -786,7 +786,7 @@ : selectedPlan.PasswordManager.basePrice ) | currency: "$" }} - /{{ "year" | i18n }} + /{{ selectedPlanInterval | i18n }} @@ -813,7 +813,7 @@ {{ "members" | i18n }} × {{ selectedPlan.PasswordManager.seatPrice | currency: "$" }} - /{{ "year" | i18n }} + /{{ selectedPlanInterval | i18n }} @@ -825,7 +825,7 @@

    - +

    {{ "secretsManager" | i18n }} @@ -860,7 +860,7 @@ {{ "members" | i18n }} × {{ selectedPlan.SecretsManager.seatPrice | currency: "$" }} - /{{ "month" | i18n }} + /{{ selectedPlanInterval | i18n }} {{ secretsManagerSeatTotal(selectedPlan, sub?.smSeats) | currency: "$" }} @@ -879,7 +879,7 @@ {{ "serviceAccounts" | i18n }} × {{ selectedPlan.SecretsManager.additionalPricePerServiceAccount | currency: "$" }} - /{{ "month" | i18n }} + /{{ selectedPlanInterval | i18n }} {{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }}

    @@ -914,7 +914,7 @@ {{ "members" | i18n }} × {{ selectedPlan.PasswordManager.seatPrice | currency: "$" }} - /{{ "month" | i18n }} + /{{ selectedPlanInterval | i18n }} {{ "freeForOneYear" | i18n }} @@ -929,9 +929,9 @@
    - +

    -
    - +
    +

    @@ -962,8 +962,8 @@

    -
    - +
    +

    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 d7ac442c40c..536e7b7020e 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 @@ -13,17 +13,21 @@ import { } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; import { Router } from "@angular/router"; -import { Subject, takeUntil } from "rxjs"; +import { Subject, firstValueFrom, map, takeUntil } from "rxjs"; import { ManageTaxInformationComponent } from "@bitwarden/angular/billing/components"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request"; import { OrganizationUpgradeRequest } from "@bitwarden/common/admin-console/models/request/organization-upgrade.request"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingApiServiceAbstraction, BillingInformation, @@ -41,23 +45,21 @@ import { } from "@bitwarden/common/billing/enums"; import { TaxInformation } from "@bitwarden/common/billing/models/domain"; import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; -import { PaymentRequest } from "@bitwarden/common/billing/models/request/payment.request"; import { PreviewOrganizationInvoiceRequest } from "@bitwarden/common/billing/models/request/preview-organization-invoice.request"; import { UpdatePaymentMethodRequest } from "@bitwarden/common/billing/models/request/update-payment-method.request"; import { BillingResponse } from "@bitwarden/common/billing/models/response/billing.response"; import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response"; import { PaymentSourceResponse } from "@bitwarden/common/billing/models/response/payment-source.response"; import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { DialogService, ToastService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; +import { BillingNotificationService } from "../services/billing-notification.service"; import { BillingSharedModule } from "../shared/billing-shared.module"; -import { PaymentV2Component } from "../shared/payment/payment-v2.component"; import { PaymentComponent } from "../shared/payment/payment.component"; type ChangePlanDialogParams = { @@ -102,7 +104,6 @@ interface OnSuccessArgs { }) export class ChangePlanDialogComponent implements OnInit, OnDestroy { @ViewChild(PaymentComponent) paymentComponent: PaymentComponent; - @ViewChild(PaymentV2Component) paymentV2Component: PaymentV2Component; @ViewChild(ManageTaxInformationComponent) taxComponent: ManageTaxInformationComponent; @Input() acceptingSponsorship = false; @@ -178,14 +179,13 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { showPayment: boolean = false; totalOpened: boolean = false; currentPlan: PlanResponse; - currentFocusIndex = 0; isCardStateDisabled = false; focusedIndex: number | null = null; accountCredit: number; paymentSource?: PaymentSourceResponse; - - deprecateStripeSourcesAPI: boolean; + plans: ListResponse; isSubscriptionCanceled: boolean = false; + secretsManagerTotal: number; private destroy$ = new Subject(); @@ -205,17 +205,14 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { private messagingService: MessagingService, private formBuilder: FormBuilder, private organizationApiService: OrganizationApiServiceAbstraction, - private configService: ConfigService, private billingApiService: BillingApiServiceAbstraction, private taxService: TaxServiceAbstraction, + private accountService: AccountService, private organizationBillingService: OrganizationBillingService, + private billingNotificationService: BillingNotificationService, ) {} async ngOnInit(): Promise { - this.deprecateStripeSourcesAPI = await this.configService.getFeatureFlag( - FeatureFlag.AC2476_DeprecateStripeSourcesAPI, - ); - if (this.dialogParams.organizationId) { this.currentPlanName = this.resolvePlanName(this.dialogParams.productTierType); this.sub = @@ -225,21 +222,28 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { this.organizationId = this.dialogParams.organizationId; this.currentPlan = this.sub?.plan; this.selectedPlan = this.sub?.plan; - this.organization = await this.organizationService.get(this.organizationId); - if (this.deprecateStripeSourcesAPI) { + const userId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + this.organization = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(this.organizationId)), + ); + try { const { accountCredit, paymentSource } = await this.billingApiService.getOrganizationPaymentMethod(this.organizationId); this.accountCredit = accountCredit; this.paymentSource = paymentSource; - } else { - this.billing = await this.organizationApiService.getBilling(this.organizationId); + } catch (error) { + this.billingNotificationService.handleError(error); } } if (!this.selfHosted) { - const plans = await this.apiService.getPlans(); - this.passwordManagerPlans = plans.data.filter((plan) => !!plan.PasswordManager); - this.secretsManagerPlans = plans.data.filter((plan) => !!plan.SecretsManager); + this.plans = await this.apiService.getPlans(); + this.passwordManagerPlans = this.plans.data.filter((plan) => !!plan.PasswordManager); + this.secretsManagerPlans = this.plans.data.filter((plan) => !!plan.SecretsManager); if ( this.productTier === ProductTierType.Enterprise || @@ -292,7 +296,9 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { const taxInfo = await this.organizationApiService.getTaxInfo(this.organizationId); this.taxInformation = TaxInformation.from(taxInfo); - this.refreshSalesTax(); + if (!this.isSubscriptionCanceled) { + this.refreshSalesTax(); + } } resolveHeaderName(subscription: OrganizationSubscriptionResponse): string { @@ -311,23 +317,17 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { setInitialPlanSelection() { this.focusedIndex = this.selectableProducts.length - 1; - this.selectPlan(this.getPlanByType(ProductTierType.Enterprise)); + if (!this.isSubscriptionCanceled) { + this.selectPlan(this.getPlanByType(ProductTierType.Enterprise)); + } } getPlanByType(productTier: ProductTierType) { return this.selectableProducts.find((product) => product.productTier === productTier); } - secretsManagerTrialDiscount() { - return this.sub?.customerDiscount?.appliesTo?.includes("sm-standalone") - ? this.discountPercentage - : this.discountPercentageFromSub + this.discountPercentage; - } - isPaymentSourceEmpty() { - return this.deprecateStripeSourcesAPI - ? this.paymentSource === null || this.paymentSource === undefined - : this.billing?.paymentSource === null || this.billing?.paymentSource === undefined; + return this.paymentSource === null || this.paymentSource === undefined; } isSecretsManagerTrial(): boolean { @@ -471,18 +471,23 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { get upgradeRequiresPaymentMethod() { const isFreeTier = this.organization?.productTierType === ProductTierType.Free; const shouldHideFree = !this.showFree; - const hasNoPaymentSource = this.deprecateStripeSourcesAPI - ? !this.paymentSource - : !this.billing?.paymentSource; + const hasNoPaymentSource = !this.paymentSource; return isFreeTier && shouldHideFree && hasNoPaymentSource; } get selectedSecretsManagerPlan() { - return this.secretsManagerPlans.find((plan) => plan.type === this.selectedPlan.type); + let planResponse: PlanResponse; + if (this.secretsManagerPlans) { + return this.secretsManagerPlans.find((plan) => plan.type === this.selectedPlan.type); + } + return planResponse; } get selectedPlanInterval() { + if (this.isSubscriptionCanceled) { + return this.currentPlan.isAnnual ? "year" : "month"; + } return this.selectedPlan.isAnnual ? "year" : "month"; } @@ -617,18 +622,19 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { return subTotal - this.discount; } - get secretsManagerSubtotal() { + secretsManagerSubtotal() { + this.secretsManagerTotal = 0; const plan = this.selectedSecretsManagerPlan; if (!this.organization.useSecretsManager) { - return 0; + return this.secretsManagerTotal; } - return ( + this.secretsManagerTotal = plan.SecretsManager.basePrice + this.secretsManagerSeatTotal(plan, this.sub?.smSeats) + - this.additionalServiceAccountTotal(plan) - ); + this.additionalServiceAccountTotal(plan); + return this.secretsManagerTotal; } get passwordManagerSeats() { @@ -639,11 +645,11 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { } get total() { - if (this.organization.useSecretsManager) { + if (this.organization && this.organization.useSecretsManager) { return ( this.passwordManagerSubtotal + this.additionalStorageTotal(this.selectedPlan) + - this.secretsManagerSubtotal + + this.secretsManagerSubtotal() + this.estimatedTax ); } @@ -703,25 +709,13 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { } changedCountry() { - if (this.deprecateStripeSourcesAPI && this.paymentV2Component) { - this.paymentV2Component.showBankAccount = this.taxInformation.country === "US"; + this.paymentComponent.showBankAccount = this.taxInformation.country === "US"; - if ( - !this.paymentV2Component.showBankAccount && - this.paymentV2Component.selected === PaymentMethodType.BankAccount - ) { - this.paymentV2Component.select(PaymentMethodType.Card); - } - } else if (this.paymentComponent && this.taxInformation) { - this.paymentComponent!.hideBank = this.taxInformation.country !== "US"; - // Bank Account payments are only available for US customers - if ( - this.paymentComponent.hideBank && - this.paymentComponent.method === PaymentMethodType.BankAccount - ) { - this.paymentComponent.method = PaymentMethodType.Card; - this.paymentComponent.changeMethod(); - } + if ( + !this.paymentComponent.showBankAccount && + this.paymentComponent.selected === PaymentMethodType.BankAccount + ) { + this.paymentComponent.select(PaymentMethodType.Card); } } @@ -786,8 +780,15 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { billingEmail: org.billingEmail, }; + const filteredPlan = this.plans.data + .filter((plan) => plan.productTier === this.selectedPlan.productTier && !plan.legacyYear) + .find((plan) => { + const isSameBillingCycle = plan.isAnnual === this.selectedPlan.isAnnual; + return isSameBillingCycle; + }); + const plan: PlanInformation = { - type: this.selectedPlan.type, + type: filteredPlan.type, passwordManagerSeats: org.seats, }; @@ -796,14 +797,8 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { plan.secretsManagerSeats = org.smSeats; } - let paymentMethod: [string, PaymentMethodType]; - - if (this.deprecateStripeSourcesAPI) { - const { type, token } = await this.paymentV2Component.tokenize(); - paymentMethod = [token, type]; - } else { - paymentMethod = await this.paymentComponent.createPaymentToken(); - } + const { type, token } = await this.paymentComponent.tokenize(); + const paymentMethod: [string, PaymentMethodType] = [token, type]; const payment: PaymentInformation = { paymentMethod, @@ -839,27 +834,17 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { this.buildSecretsManagerRequest(request); if (this.upgradeRequiresPaymentMethod || this.showPayment || this.isPaymentSourceEmpty()) { - if (this.deprecateStripeSourcesAPI) { - const tokenizedPaymentSource = await this.paymentV2Component.tokenize(); - const updatePaymentMethodRequest = new UpdatePaymentMethodRequest(); - updatePaymentMethodRequest.paymentSource = tokenizedPaymentSource; - updatePaymentMethodRequest.taxInformation = ExpandedTaxInfoUpdateRequest.From( - this.taxInformation, - ); + const tokenizedPaymentSource = await this.paymentComponent.tokenize(); + const updatePaymentMethodRequest = new UpdatePaymentMethodRequest(); + updatePaymentMethodRequest.paymentSource = tokenizedPaymentSource; + updatePaymentMethodRequest.taxInformation = ExpandedTaxInfoUpdateRequest.From( + this.taxInformation, + ); - await this.billingApiService.updateOrganizationPaymentMethod( - this.organizationId, - updatePaymentMethodRequest, - ); - } else { - const tokenResult = await this.paymentComponent.createPaymentToken(); - const paymentRequest = new PaymentRequest(); - paymentRequest.paymentToken = tokenResult[0]; - paymentRequest.paymentMethodType = tokenResult[1]; - paymentRequest.country = this.taxInformation.country; - paymentRequest.postalCode = this.taxInformation.postalCode; - await this.organizationApiService.updatePayment(this.organizationId, paymentRequest); - } + await this.billingApiService.updateOrganizationPaymentMethod( + this.organizationId, + updatePaymentMethodRequest, + ); } // Backfill pub/priv key if necessary @@ -869,10 +854,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { request.keys = new OrganizationKeysRequest(orgKeys[0], orgKeys[1].encryptedString); } - const result = await this.organizationApiService.upgrade(this.organizationId, request); - if (!result.success && result.paymentIntentClientSecret != null) { - await this.paymentComponent.handleStripeCardPayment(result.paymentIntentClientSecret, null); - } + await this.organizationApiService.upgrade(this.organizationId, request); return this.organizationId; } @@ -969,38 +951,20 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { } get paymentSourceClasses() { - if (this.deprecateStripeSourcesAPI) { - if (this.paymentSource == null) { + if (this.paymentSource == null) { + return []; + } + switch (this.paymentSource.type) { + case PaymentMethodType.Card: + return ["bwi-credit-card"]; + case PaymentMethodType.BankAccount: + return ["bwi-bank"]; + case PaymentMethodType.Check: + return ["bwi-money"]; + case PaymentMethodType.PayPal: + return ["bwi-paypal text-primary"]; + default: return []; - } - switch (this.paymentSource.type) { - case PaymentMethodType.Card: - return ["bwi-credit-card"]; - case PaymentMethodType.BankAccount: - return ["bwi-bank"]; - case PaymentMethodType.Check: - return ["bwi-money"]; - case PaymentMethodType.PayPal: - return ["bwi-paypal text-primary"]; - default: - return []; - } - } else { - if (this.billing.paymentSource == null) { - return []; - } - switch (this.billing.paymentSource.type) { - case PaymentMethodType.Card: - return ["bwi-credit-card"]; - case PaymentMethodType.BankAccount: - return ["bwi-bank"]; - case PaymentMethodType.Check: - return ["bwi-money"]; - case PaymentMethodType.PayPal: - return ["bwi-paypal text-primary"]; - default: - return []; - } } } @@ -1060,7 +1024,11 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { } private refreshSalesTax(): void { - if (!this.taxInformation.country || !this.taxInformation.postalCode) { + if ( + this.taxInformation === undefined || + !this.taxInformation.country || + !this.taxInformation.postalCode + ) { return; } @@ -1081,7 +1049,8 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { if (this.organization.useSecretsManager) { request.secretsManager = { seats: this.sub.smSeats, - additionalMachineAccounts: this.sub.smServiceAccounts, + additionalMachineAccounts: + this.sub.smServiceAccounts - this.sub.plan.SecretsManager.baseServiceAccount, }; } @@ -1091,10 +1060,12 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { this.estimatedTax = invoice.taxAmount; }) .catch((error) => { + const translatedMessage = this.i18nService.t(error.message); this.toastService.showToast({ title: "", variant: "error", - message: this.i18nService.t(error.message), + message: + !translatedMessage || translatedMessage === "" ? error.message : translatedMessage, }); }); } diff --git a/apps/web/src/app/billing/organizations/organization-billing-routing.module.ts b/apps/web/src/app/billing/organizations/organization-billing-routing.module.ts index 3d4c8dd3870..1bfb9fc4912 100644 --- a/apps/web/src/app/billing/organizations/organization-billing-routing.module.ts +++ b/apps/web/src/app/billing/organizations/organization-billing-routing.module.ts @@ -1,14 +1,11 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; -import { featureFlaggedRoute } from "@bitwarden/angular/platform/utils/feature-flagged-route"; import { canAccessBillingTab } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { organizationPermissionsGuard } from "../../admin-console/organizations/guards/org-permissions.guard"; import { organizationIsUnmanaged } from "../../billing/guards/organization-is-unmanaged.guard"; import { WebPlatformUtilsService } from "../../core/web-platform-utils.service"; -import { PaymentMethodComponent } from "../shared"; import { OrgBillingHistoryViewComponent } from "./organization-billing-history-view.component"; import { OrganizationSubscriptionCloudComponent } from "./organization-subscription-cloud.component"; @@ -28,21 +25,17 @@ const routes: Routes = [ : OrganizationSubscriptionCloudComponent, data: { titleId: "subscription" }, }, - ...featureFlaggedRoute({ - defaultComponent: PaymentMethodComponent, - flaggedComponent: OrganizationPaymentMethodComponent, - featureFlag: FeatureFlag.AC2476_DeprecateStripeSourcesAPI, - routeOptions: { - path: "payment-method", - canActivate: [ - organizationPermissionsGuard((org) => org.canEditPaymentMethods), - organizationIsUnmanaged, - ], - data: { - titleId: "paymentMethod", - }, + { + path: "payment-method", + component: OrganizationPaymentMethodComponent, + canActivate: [ + organizationPermissionsGuard((org) => org.canEditPaymentMethods), + organizationIsUnmanaged, + ], + data: { + titleId: "paymentMethod", }, - }), + }, { path: "history", component: OrgBillingHistoryViewComponent, diff --git a/apps/web/src/app/billing/organizations/organization-billing.module.ts b/apps/web/src/app/billing/organizations/organization-billing.module.ts index 48ac613711d..d8f4b7393aa 100644 --- a/apps/web/src/app/billing/organizations/organization-billing.module.ts +++ b/apps/web/src/app/billing/organizations/organization-billing.module.ts @@ -1,5 +1,7 @@ import { NgModule } from "@angular/core"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { BannerModule } from "../../../../../../libs/components/src/banner/banner.module"; import { UserVerificationModule } from "../../auth/shared/components/user-verification"; import { LooseComponentsModule } from "../../shared"; diff --git a/apps/web/src/app/billing/organizations/organization-plans.component.html b/apps/web/src/app/billing/organizations/organization-plans.component.html index d37f95e3aa2..3b765927c3c 100644 --- a/apps/web/src/app/billing/organizations/organization-plans.component.html +++ b/apps/web/src/app/billing/organizations/organization-plans.component.html @@ -7,36 +7,7 @@ {{ "loading" | i18n }} - -

    {{ "uploadLicenseFileOrg" | i18n }}

    - - - {{ "licenseFile" | i18n }} -
    - - {{ selectedFile?.name ?? ("noFileChosen" | i18n) }} -
    - - {{ "licenseFileDesc" | i18n: "bitwarden_organization_license.json" }} -
    - - - @@ -245,7 +216,7 @@ formControlName="additionalSeats" placeholder="{{ 'userSeatsDesc' | i18n }}" /> - {{ "userSeatsAdditionalDesc" | i18n @@ -434,12 +405,10 @@ {{ paymentDesc }}

    - + *ngIf="createOrganization || upgradeRequiresPaymentMethod" + [showAccountCredit]="false" + > +
    - - - 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 4592f8de894..84f2467b1bd 100644 --- a/apps/web/src/app/billing/organizations/organization-plans.component.ts +++ b/apps/web/src/app/billing/organizations/organization-plans.component.ts @@ -11,13 +11,16 @@ import { } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; import { Router } from "@angular/router"; -import { Subject, takeUntil } from "rxjs"; -import { debounceTime } from "rxjs/operators"; +import { Subject, firstValueFrom, takeUntil } from "rxjs"; +import { debounceTime, map } from "rxjs/operators"; import { ManageTaxInformationComponent } from "@bitwarden/angular/billing/components"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; @@ -27,20 +30,19 @@ import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/ import { OrganizationUpgradeRequest } from "@bitwarden/common/admin-console/models/request/organization-upgrade.request"; import { ProviderOrganizationCreateRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-organization-create.request"; import { ProviderResponse } from "@bitwarden/common/admin-console/models/response/provider/provider.response"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; import { PaymentMethodType, PlanType, ProductTierType } from "@bitwarden/common/billing/enums"; import { TaxInformation } from "@bitwarden/common/billing/models/domain"; import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; -import { PaymentRequest } from "@bitwarden/common/billing/models/request/payment.request"; import { PreviewOrganizationInvoiceRequest } from "@bitwarden/common/billing/models/request/preview-organization-invoice.request"; import { UpdatePaymentMethodRequest } from "@bitwarden/common/billing/models/request/update-payment-method.request"; import { BillingResponse } from "@bitwarden/common/billing/models/response/billing.response"; import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response"; 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 { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.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"; @@ -53,7 +55,6 @@ import { KeyService } from "@bitwarden/key-management"; import { OrganizationCreateModule } from "../../admin-console/organizations/create/organization-create.module"; import { BillingSharedModule, secretsManagerSubscribeFormFactory } from "../shared"; -import { PaymentV2Component } from "../shared/payment/payment-v2.component"; import { PaymentComponent } from "../shared/payment/payment.component"; interface OnSuccessArgs { @@ -75,7 +76,6 @@ const Allowed2020PlansForLegacyProviders = [ }) export class OrganizationPlansComponent implements OnInit, OnDestroy { @ViewChild(PaymentComponent) paymentComponent: PaymentComponent; - @ViewChild(PaymentV2Component) paymentV2Component: PaymentV2Component; @ViewChild(ManageTaxInformationComponent) taxComponent: ManageTaxInformationComponent; @Input() organizationId?: string; @@ -109,6 +109,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { this._plan = plan; this.formGroup?.controls?.plan?.setValue(plan); } + @Input() enableSecretsManagerByDefault: boolean; private _plan = PlanType.Free; @Input() providerId?: string; @@ -124,11 +125,6 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { singleOrgPolicyAppliesToActiveUser = false; isInTrialFlow = false; discount = 0; - deprecateStripeSourcesAPI: boolean; - - protected useLicenseUploaderComponent$ = this.configService.getFeatureFlag$( - FeatureFlag.PM11901_RefactorSelfHostingLicenseUploader, - ); secretsManagerSubscription = secretsManagerSubscribeFormFactory(this.formBuilder); @@ -179,21 +175,25 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { private configService: ConfigService, private billingApiService: BillingApiServiceAbstraction, private taxService: TaxServiceAbstraction, + private accountService: AccountService, ) { this.selfHosted = this.platformUtilsService.isSelfHost(); } async ngOnInit() { - this.deprecateStripeSourcesAPI = await this.configService.getFeatureFlag( - FeatureFlag.AC2476_DeprecateStripeSourcesAPI, - ); - if (this.organizationId) { - this.organization = await this.organizationService.get(this.organizationId); + const userId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + this.organization = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(this.organizationId)), + ); this.billing = await this.organizationApiService.getBilling(this.organizationId); this.sub = await this.organizationApiService.getSubscription(this.organizationId); this.taxInformation = await this.organizationApiService.getTaxInfo(this.organizationId); - } else { + } else if (!this.selfHosted) { this.taxInformation = await this.apiService.getTaxInfo(); } @@ -265,6 +265,14 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { .subscribe(() => { this.refreshSalesTax(); }); + + if (this.enableSecretsManagerByDefault && this.selectedSecretsManagerPlan) { + this.secretsManagerSubscription.patchValue({ + enabled: true, + userSeats: 1, + additionalServiceAccounts: 0, + }); + } } ngOnDestroy() { @@ -568,23 +576,12 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { } protected changedCountry(): void { - if (this.deprecateStripeSourcesAPI) { - this.paymentV2Component.showBankAccount = this.taxInformation?.country === "US"; - if ( - !this.paymentV2Component.showBankAccount && - this.paymentV2Component.selected === PaymentMethodType.BankAccount - ) { - this.paymentV2Component.select(PaymentMethodType.Card); - } - } else { - this.paymentComponent.hideBank = this.taxInformation?.country !== "US"; - if ( - this.paymentComponent.hideBank && - this.paymentComponent.method === PaymentMethodType.BankAccount - ) { - this.paymentComponent.method = PaymentMethodType.Card; - this.paymentComponent.changeMethod(); - } + this.paymentComponent.showBankAccount = this.taxInformation?.country === "US"; + if ( + !this.paymentComponent.showBankAccount && + this.paymentComponent.selected === PaymentMethodType.BankAccount + ) { + this.paymentComponent.select(PaymentMethodType.Card); } } @@ -605,6 +602,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { submit = async () => { if (this.taxComponent && !this.taxComponent.validate()) { + this.taxComponent.markAllAsTouched(); return; } @@ -738,25 +736,15 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { this.buildSecretsManagerRequest(request); if (this.upgradeRequiresPaymentMethod) { - if (this.deprecateStripeSourcesAPI) { - const updatePaymentMethodRequest = new UpdatePaymentMethodRequest(); - updatePaymentMethodRequest.paymentSource = await this.paymentV2Component.tokenize(); - updatePaymentMethodRequest.taxInformation = ExpandedTaxInfoUpdateRequest.From( - this.taxInformation, - ); - await this.billingApiService.updateOrganizationPaymentMethod( - this.organizationId, - updatePaymentMethodRequest, - ); - } else { - const [paymentToken, paymentMethodType] = await this.paymentComponent.createPaymentToken(); - const paymentRequest = new PaymentRequest(); - paymentRequest.paymentToken = paymentToken; - paymentRequest.paymentMethodType = paymentMethodType; - paymentRequest.country = this.taxInformation?.country; - paymentRequest.postalCode = this.taxInformation?.postalCode; - await this.organizationApiService.updatePayment(this.organizationId, paymentRequest); - } + const updatePaymentMethodRequest = new UpdatePaymentMethodRequest(); + updatePaymentMethodRequest.paymentSource = await this.paymentComponent.tokenize(); + updatePaymentMethodRequest.taxInformation = ExpandedTaxInfoUpdateRequest.From( + this.taxInformation, + ); + await this.billingApiService.updateOrganizationPaymentMethod( + this.organizationId, + updatePaymentMethodRequest, + ); } // Backfill pub/priv key if necessary @@ -766,10 +754,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { request.keys = new OrganizationKeysRequest(orgKeys[0], orgKeys[1].encryptedString); } - const result = await this.organizationApiService.upgrade(this.organizationId, request); - if (!result.success && result.paymentIntentClientSecret != null) { - await this.paymentComponent.handleStripeCardPayment(result.paymentIntentClientSecret, null); - } + await this.organizationApiService.upgrade(this.organizationId, request); return this.organizationId; } @@ -790,14 +775,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { if (this.selectedPlan.type === PlanType.Free) { request.planType = PlanType.Free; } else { - let type: PaymentMethodType; - let token: string; - - if (this.deprecateStripeSourcesAPI) { - ({ type, token } = await this.paymentV2Component.tokenize()); - } else { - [token, type] = await this.paymentComponent.createPaymentToken(); - } + const { type, token } = await this.paymentComponent.tokenize(); request.paymentToken = token; request.paymentMethodType = type; 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 0cd21d0f688..385d9b8ae1a 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 @@ -1,61 +1,13 @@ - + {{ "loading" | i18n }} - - - {{ "subscriptionCanceled" | i18n }} - -

    {{ "subscriptionPendingCanceled" | i18n }}

    - -
    - -
    -
    {{ "billingPlan" | i18n }}
    -
    {{ sub.plan.name }}
    - -
    {{ "status" | i18n }}
    -
    - {{ - isSponsoredSubscription ? "sponsored" : subscription.status || "-" - }} - {{ - "pendingCancellation" | i18n - }} -
    -
    - {{ "subscriptionExpiration" | i18n }} -
    -
    - {{ nextInvoice ? (sub.subscription.periodEndDate | date: "mediumDate") : "-" }} -
    -
    -
    -
    @@ -294,6 +246,10 @@ + +

    {{ "manageSubscription" | i18n }}

    +

    {{ resellerSeatsRemainingMessage }}

    +

    {{ "selfHostingTitleProper" | 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 0805e92ee2a..f20d447b093 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 @@ -2,30 +2,34 @@ // @ts-strict-ignore import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; -import { firstValueFrom, lastValueFrom, Observable, Subject } from "rxjs"; +import { firstValueFrom, lastValueFrom, Subject } from "rxjs"; +import { OrganizationUserApiService } from "@bitwarden/admin-console/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 { OrganizationApiKeyType } from "@bitwarden/common/admin-console/enums"; +import { + getOrganizationById, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + OrganizationApiKeyType, + OrganizationUserStatusType, +} from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; import { PlanType, ProductTierType } from "@bitwarden/common/billing/enums"; import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response"; import { BillingSubscriptionItemResponse } from "@bitwarden/common/billing/models/response/subscription.response"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { DialogService, ToastService } from "@bitwarden/components"; import { - AdjustStorageDialogV2Component, - AdjustStorageDialogV2ResultType, -} from "../shared/adjust-storage-dialog/adjust-storage-dialog-v2.component"; -import { - AdjustStorageDialogResult, - openAdjustStorageDialog, + AdjustStorageDialogComponent, + AdjustStorageDialogResultType, } from "../shared/adjust-storage-dialog/adjust-storage-dialog.component"; import { OffboardingSurveyDialogResultType, @@ -50,25 +54,20 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy organizationId: string; userOrg: Organization; showChangePlan = false; - showDownloadLicense = false; hasBillingSyncToken: boolean; showAdjustSecretsManager = false; showSecretsManagerSubscribe = false; loading = true; locale: string; - showUpdatedSubscriptionStatusSection$: Observable; preSelectedProductTier: ProductTierType = ProductTierType.Free; showSubscription = true; showSelfHost = false; organizationIsManagedByConsolidatedBillingMSP = false; + resellerSeatsRemainingMessage: string; protected readonly subscriptionHiddenIcon = SubscriptionHiddenIcon; protected readonly teamsStarter = ProductTierType.TeamsStarter; - protected deprecateStripeSourcesAPI$ = this.configService.getFeatureFlag$( - FeatureFlag.AC2476_DeprecateStripeSourcesAPI, - ); - private destroy$ = new Subject(); constructor( @@ -76,12 +75,14 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy private i18nService: I18nService, private logService: LogService, private organizationService: OrganizationService, + private accountService: AccountService, private organizationApiService: OrganizationApiServiceAbstraction, private route: ActivatedRoute, private dialogService: DialogService, private configService: ConfigService, private toastService: ToastService, private billingApiService: BillingApiServiceAbstraction, + private organizationUserApiService: OrganizationUserApiService, ) {} async ngOnInit() { @@ -91,10 +92,6 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy ]; await this.load(); - this.showUpdatedSubscriptionStatusSection$ = this.configService.getFeatureFlag$( - FeatureFlag.AC1795_UpdatedSubscriptionStatusSection, - ); - if ( this.route.snapshot.queryParams[OrganizationSubscriptionCloudComponent.QUERY_PARAM_UPGRADE] ) { @@ -107,6 +104,28 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy } } } + + if (this.userOrg.hasReseller) { + const allUsers = await this.organizationUserApiService.getAllUsers(this.userOrg.id); + + const userCount = allUsers.data.filter((user) => + [ + OrganizationUserStatusType.Invited, + OrganizationUserStatusType.Accepted, + OrganizationUserStatusType.Confirmed, + ].includes(user.status), + ).length; + + const remainingSeats = this.userOrg.seats - userCount; + + const seatsRemaining = this.i18nService.t( + "seatsRemaining", + remainingSeats.toString(), + this.userOrg.seats.toString(), + ); + + this.resellerSeatsRemainingMessage = seatsRemaining; + } } ngOnDestroy() { @@ -117,7 +136,12 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy async load() { this.loading = true; this.locale = await firstValueFrom(this.i18nService.locale$); - this.userOrg = await this.organizationService.get(this.organizationId); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + this.userOrg = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(this.organizationId)), + ); const isIndependentOrganizationOwner = !this.userOrg.hasProvider && this.userOrg.isOwner; const isResoldOrganizationOwner = this.userOrg.hasReseller && this.userOrg.isOwner; @@ -415,36 +439,19 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy adjustStorage = (add: boolean) => { return async () => { - const deprecateStripeSourcesAPI = await firstValueFrom(this.deprecateStripeSourcesAPI$); + const dialogRef = AdjustStorageDialogComponent.open(this.dialogService, { + data: { + price: this.storageGbPrice, + cadence: this.billingInterval, + type: add ? "Add" : "Remove", + organizationId: this.organizationId, + }, + }); - if (deprecateStripeSourcesAPI) { - const dialogRef = AdjustStorageDialogV2Component.open(this.dialogService, { - data: { - price: this.storageGbPrice, - cadence: this.billingInterval, - type: add ? "Add" : "Remove", - organizationId: this.organizationId, - }, - }); + const result = await lastValueFrom(dialogRef.closed); - const result = await lastValueFrom(dialogRef.closed); - - if (result === AdjustStorageDialogV2ResultType.Submitted) { - await this.load(); - } - } else { - const dialogRef = openAdjustStorageDialog(this.dialogService, { - data: { - storageGbPrice: this.storageGbPrice, - add: add, - organizationId: this.organizationId, - interval: this.billingInterval, - }, - }); - const result = await lastValueFrom(dialogRef.closed); - if (result === AdjustStorageDialogResult.Adjusted) { - await this.load(); - } + if (result === AdjustStorageDialogResultType.Submitted) { + await this.load(); } }; }; 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 acbb8863c70..471912f83ab 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 @@ -2,7 +2,7 @@ - + {{ "loading" | i18n }} @@ -66,7 +66,7 @@
    -

    +

    {{ "licenseAndBillingManagement" | i18n }}

    => { this.loading = true; - const { accountCredit, paymentSource, subscriptionStatus } = - await this.billingApiService.getOrganizationPaymentMethod(this.organizationId); - this.accountCredit = accountCredit; - this.paymentSource = paymentSource; - this.subscriptionStatus = subscriptionStatus; + try { + const { accountCredit, paymentSource, subscriptionStatus } = + await this.billingApiService.getOrganizationPaymentMethod(this.organizationId); + this.accountCredit = accountCredit; + this.paymentSource = paymentSource; + this.subscriptionStatus = subscriptionStatus; - if (this.organizationId) { - const organizationSubscriptionPromise = this.organizationApiService.getSubscription( - this.organizationId, - ); - const organizationPromise = this.organizationService.get(this.organizationId); + if (this.organizationId) { + const organizationSubscriptionPromise = this.organizationApiService.getSubscription( + this.organizationId, + ); + const userId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + const organizationPromise = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(this.organizationId)), + ); - [this.organizationSubscriptionResponse, this.organization] = await Promise.all([ - organizationSubscriptionPromise, - organizationPromise, - ]); - this.freeTrialData = this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues( - this.organization, - this.organizationSubscriptionResponse, - paymentSource, - ); + [this.organizationSubscriptionResponse, this.organization] = await Promise.all([ + organizationSubscriptionPromise, + organizationPromise, + ]); + this.freeTrialData = this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues( + this.organization, + this.organizationSubscriptionResponse, + paymentSource, + ); + } + this.isUnpaid = this.subscriptionStatus === "unpaid" ?? false; + // If the flag `launchPaymentModalAutomatically` is set to true, + // we schedule a timeout (delay of 800ms) to automatically launch the payment modal. + // This delay ensures that any prior UI/rendering operations complete before triggering the modal. + if (this.launchPaymentModalAutomatically) { + window.setTimeout(async () => { + await this.changePayment(); + this.launchPaymentModalAutomatically = false; + this.location.replaceState(this.location.path(), "", {}); + }, 800); + } + } catch (error) { + this.billingNotificationService.handleError(error); + } finally { + this.loading = false; } - this.isUnpaid = this.subscriptionStatus === "unpaid" ?? false; - // If the flag `launchPaymentModalAutomatically` is set to true, - // we schedule a timeout (delay of 800ms) to automatically launch the payment modal. - // This delay ensures that any prior UI/rendering operations complete before triggering the modal. - if (this.launchPaymentModalAutomatically) { - window.setTimeout(async () => { - await this.changePayment(); - this.launchPaymentModalAutomatically = false; - this.location.replaceState(this.location.path(), "", {}); - }, 800); - } - this.loading = false; }; protected updatePaymentMethod = async (): Promise => { - const dialogRef = AdjustPaymentDialogV2Component.open(this.dialogService, { + const dialogRef = AdjustPaymentDialogComponent.open(this.dialogService, { data: { initialPaymentMethod: this.paymentSource?.type, organizationId: this.organizationId, @@ -157,13 +176,13 @@ export class OrganizationPaymentMethodComponent implements OnDestroy { const result = await lastValueFrom(dialogRef.closed); - if (result === AdjustPaymentDialogV2ResultType.Submitted) { + if (result === AdjustPaymentDialogResultType.Submitted) { await this.load(); } }; changePayment = async () => { - const dialogRef = AdjustPaymentDialogV2Component.open(this.dialogService, { + const dialogRef = AdjustPaymentDialogComponent.open(this.dialogService, { data: { initialPaymentMethod: this.paymentSource?.type, organizationId: this.organizationId, @@ -171,7 +190,7 @@ export class OrganizationPaymentMethodComponent implements OnDestroy { }, }); const result = await lastValueFrom(dialogRef.closed); - if (result === AdjustPaymentDialogV2ResultType.Submitted) { + if (result === AdjustPaymentDialogResultType.Submitted) { this.location.replaceState(this.location.path(), "", {}); if (this.launchPaymentModalAutomatically && !this.organization.enabled) { await this.syncService.fullSync(true); diff --git a/apps/web/src/app/billing/organizations/sm-adjust-subscription.component.ts b/apps/web/src/app/billing/organizations/sm-adjust-subscription.component.ts index fc7a188f967..c10c9abc9b6 100644 --- a/apps/web/src/app/billing/organizations/sm-adjust-subscription.component.ts +++ b/apps/web/src/app/billing/organizations/sm-adjust-subscription.component.ts @@ -2,11 +2,16 @@ // @ts-strict-ignore import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; -import { Subject, takeUntil } from "rxjs"; +import { Subject, firstValueFrom, takeUntil } from "rxjs"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + InternalOrganizationServiceAbstraction, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationData } from "@bitwarden/common/admin-console/models/data/organization.data"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { OrganizationSmSubscriptionUpdateRequest } from "@bitwarden/common/billing/models/request/organization-sm-subscription-update.request"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -107,6 +112,7 @@ export class SecretsManagerAdjustSubscriptionComponent implements OnInit, OnDest private platformUtilsService: PlatformUtilsService, private toastService: ToastService, private internalOrganizationService: InternalOrganizationServiceAbstraction, + private accountService: AccountService, ) {} ngOnInit() { @@ -165,14 +171,19 @@ export class SecretsManagerAdjustSubscriptionComponent implements OnInit, OnDest request, ); - const organization = await this.internalOrganizationService.get(this.organizationId); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + const organization = await firstValueFrom( + this.internalOrganizationService + .organizations$(userId) + .pipe(getOrganizationById(this.organizationId)), + ); const organizationData = new OrganizationData(response, { isMember: organization.isMember, isProviderUser: organization.isProviderUser, }); - await this.internalOrganizationService.upsert(organizationData); + await this.internalOrganizationService.upsert(organizationData, userId); this.toastService.showToast({ variant: "success", diff --git a/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts b/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts index 7ad0895809c..617b68abb37 100644 --- a/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts +++ b/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts @@ -2,12 +2,15 @@ // @ts-strict-ignore import { Component, EventEmitter, Input, Output } from "@angular/core"; import { FormBuilder } from "@angular/forms"; +import { firstValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationData } from "@bitwarden/common/admin-console/models/data/organization.data"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { SecretsManagerSubscribeRequest } from "@bitwarden/common/billing/models/request/sm-subscribe.request"; import { BillingCustomerDiscount } from "@bitwarden/common/billing/models/response/organization-subscription.response"; import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; @@ -37,6 +40,7 @@ export class SecretsManagerSubscribeStandaloneComponent { private organizationApiService: OrganizationApiServiceAbstraction, private organizationService: InternalOrganizationServiceAbstraction, private toastService: ToastService, + private accountService: AccountService, ) {} submit = async () => { @@ -56,7 +60,8 @@ export class SecretsManagerSubscribeStandaloneComponent { isMember: this.organization.isMember, isProviderUser: this.organization.isProviderUser, }); - await this.organizationService.upsert(organizationData); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + await this.organizationService.upsert(organizationData, userId); /* Because subscribing to Secrets Manager automatically provides access to Secrets Manager for the diff --git a/apps/web/src/app/billing/services/billing-notification.service.spec.ts b/apps/web/src/app/billing/services/billing-notification.service.spec.ts new file mode 100644 index 00000000000..d3a4d461574 --- /dev/null +++ b/apps/web/src/app/billing/services/billing-notification.service.spec.ts @@ -0,0 +1,76 @@ +import { mock, MockProxy } from "jest-mock-extended"; + +import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { ToastService } from "@bitwarden/components"; + +import { BillingNotificationService } from "./billing-notification.service"; + +describe("BillingNotificationService", () => { + let service: BillingNotificationService; + let logService: MockProxy; + let toastService: MockProxy; + + beforeEach(() => { + logService = mock(); + toastService = mock(); + service = new BillingNotificationService(logService, toastService); + }); + + describe("handleError", () => { + it("should log error and show toast for ErrorResponse", () => { + const error = new ErrorResponse(["test error"], 400); + + expect(() => service.handleError(error)).toThrow(); + expect(logService.error).toHaveBeenCalledWith(error); + expect(toastService.showToast).toHaveBeenCalledWith({ + variant: "error", + title: "", + message: error.getSingleMessage(), + }); + }); + + it("shows error toast with the provided error", () => { + const error = new ErrorResponse(["test error"], 400); + + expect(() => service.handleError(error, "Test Title")).toThrow(); + expect(toastService.showToast).toHaveBeenCalledWith({ + variant: "error", + title: "Test Title", + message: error.getSingleMessage(), + }); + }); + + it("should only log error for non-ErrorResponse", () => { + const error = new Error("test error"); + + expect(() => service.handleError(error)).toThrow(); + expect(logService.error).toHaveBeenCalledWith(error); + expect(toastService.showToast).not.toHaveBeenCalled(); + }); + }); + + describe("showSuccess", () => { + it("shows success toast with default title when provided title is empty", () => { + const message = "test message"; + service.showSuccess(message); + + expect(toastService.showToast).toHaveBeenCalledWith({ + variant: "success", + title: "", + message, + }); + }); + + it("should show success toast with custom title", () => { + const message = "test message"; + service.showSuccess(message, "Success Title"); + + expect(toastService.showToast).toHaveBeenCalledWith({ + variant: "success", + title: "Success Title", + message, + }); + }); + }); +}); diff --git a/apps/web/src/app/billing/services/billing-notification.service.ts b/apps/web/src/app/billing/services/billing-notification.service.ts new file mode 100644 index 00000000000..6695e516ca8 --- /dev/null +++ b/apps/web/src/app/billing/services/billing-notification.service.ts @@ -0,0 +1,35 @@ +import { Injectable } from "@angular/core"; + +import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { ToastService } from "@bitwarden/components"; + +@Injectable({ + providedIn: "root", +}) +export class BillingNotificationService { + constructor( + private logService: LogService, + private toastService: ToastService, + ) {} + + handleError(error: unknown, title: string = "") { + this.logService.error(error); + if (error instanceof ErrorResponse) { + this.toastService.showToast({ + variant: "error", + title: title, + message: error.getSingleMessage(), + }); + } + throw error; + } + + showSuccess(message: string, title: string = "") { + this.toastService.showToast({ + variant: "success", + title: title, + message: message, + }); + } +} diff --git a/apps/web/src/app/billing/services/billing-services.module.ts b/apps/web/src/app/billing/services/billing-services.module.ts index 7412d47c79c..56b906cdaae 100644 --- a/apps/web/src/app/billing/services/billing-services.module.ts +++ b/apps/web/src/app/billing/services/billing-services.module.ts @@ -1,4 +1,8 @@ import { NgModule } from "@angular/core"; -@NgModule({}) +import { BillingNotificationService } from "./billing-notification.service"; + +@NgModule({ + providers: [BillingNotificationService], +}) export class BillingServicesModule {} diff --git a/apps/web/src/app/billing/services/free-families-policy.service.ts b/apps/web/src/app/billing/services/free-families-policy.service.ts index cc53e0a32bc..da569ffc993 100644 --- a/apps/web/src/app/billing/services/free-families-policy.service.ts +++ b/apps/web/src/app/billing/services/free-families-policy.service.ts @@ -1,12 +1,12 @@ import { Injectable } from "@angular/core"; -import { combineLatest, filter, from, map, Observable, of, switchMap } from "rxjs"; +import { combineLatest, filter, map, Observable, of, switchMap } from "rxjs"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; interface EnterpriseOrgStatus { isFreeFamilyPolicyEnabled: boolean; @@ -25,43 +25,46 @@ export class FreeFamiliesPolicyService { constructor( private policyService: PolicyService, private organizationService: OrganizationService, - private configService: ConfigService, + private accountService: AccountService, ) {} + organizations$ = this.accountService.activeAccount$.pipe( + switchMap((account) => { + if (account?.id) { + return this.organizationService.organizations$(account?.id); + } else { + return of(); + } + }), + ); + get showFreeFamilies$(): Observable { - return this.isFreeFamilyFlagEnabled$.pipe( - switchMap((isFreeFamilyFlagEnabled) => - isFreeFamilyFlagEnabled - ? this.getFreeFamiliesVisibility$() - : this.organizationService.canManageSponsorships$, - ), - ); + return this.getFreeFamiliesVisibility$(); } private getFreeFamiliesVisibility$(): Observable { return combineLatest([ this.checkEnterpriseOrganizationsAndFetchPolicy(), - this.organizationService.canManageSponsorships$, + this.organizations$, ]).pipe( - map(([orgStatus, canManageSponsorships]) => - this.shouldShowFreeFamilyLink(orgStatus, canManageSponsorships), - ), + map(([orgStatus, organizations]) => this.shouldShowFreeFamilyLink(orgStatus, organizations)), ); } private shouldShowFreeFamilyLink( orgStatus: EnterpriseOrgStatus | null, - canManageSponsorships: boolean, + organizations: Organization[], ): boolean { if (!orgStatus) { return false; } const { belongToOneEnterpriseOrgs, isFreeFamilyPolicyEnabled } = orgStatus; + const canManageSponsorships = organizations.filter((org) => org.canManageSponsorships); return canManageSponsorships && !(belongToOneEnterpriseOrgs && isFreeFamilyPolicyEnabled); } checkEnterpriseOrganizationsAndFetchPolicy(): Observable { - return this.organizationService.organizations$.pipe( + return this.organizations$.pipe( filter((organizations) => Array.isArray(organizations) && organizations.length > 0), switchMap((organizations) => this.fetchEnterpriseOrganizationPolicy(organizations)), ); @@ -90,7 +93,11 @@ export class FreeFamiliesPolicyService { }); } - return this.policyService.getAll$(PolicyType.FreeFamiliesSponsorshipPolicy).pipe( + return this.accountService.activeAccount$.pipe( + getUserId, + switchMap((userId) => + this.policyService.getAll$(PolicyType.FreeFamiliesSponsorshipPolicy, userId), + ), map((policies) => ({ isFreeFamilyPolicyEnabled: policies.some( (policy) => policy.organizationId === organizationId && policy.enabled, @@ -118,8 +125,4 @@ export class FreeFamiliesPolicyService { const enterpriseOrganizations = organizations.filter((org) => org.canManageSponsorships); return enterpriseOrganizations.length === 1 ? enterpriseOrganizations[0].id : null; } - - private get isFreeFamilyFlagEnabled$(): Observable { - return from(this.configService.getFeatureFlag(FeatureFlag.DisableFreeFamiliesSponsorship)); - } } diff --git a/apps/web/src/app/billing/services/reseller-warning.service.ts b/apps/web/src/app/billing/services/reseller-warning.service.ts index bfd5be3233a..2c59ebafe05 100644 --- a/apps/web/src/app/billing/services/reseller-warning.service.ts +++ b/apps/web/src/app/billing/services/reseller-warning.service.ts @@ -33,7 +33,7 @@ export class ResellerWarningService { return { type: "warning", message: this.i18nService.t( - "resellerPastDueWarning", + "resellerPastDueWarningMsg", organization.providerName, this.formatDate(gracePeriodEnd), ), @@ -50,7 +50,7 @@ export class ResellerWarningService { return { type: "info", message: this.i18nService.t( - "resellerOpenInvoiceWarning", + "resellerOpenInvoiceWarningMgs", organization.providerName, this.formatDate(organizationBillingMetadata.invoiceCreatedDate), this.formatDate(organizationBillingMetadata.invoiceDueDate), @@ -68,7 +68,7 @@ export class ResellerWarningService { return { type: "info", message: this.i18nService.t( - "resellerRenewalWarning", + "resellerRenewalWarningMsg", organization.providerName, this.formatDate(organizationBillingMetadata.subPeriodEndDate), ), diff --git a/apps/web/src/app/billing/services/stripe.service.ts b/apps/web/src/app/billing/services/stripe.service.ts index 61bc0b6cdd2..aac86107e26 100644 --- a/apps/web/src/app/billing/services/stripe.service.ts +++ b/apps/web/src/app/billing/services/stripe.service.ts @@ -3,8 +3,6 @@ import { Injectable } from "@angular/core"; import { BankAccount } from "@bitwarden/common/billing/models/domain"; -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 { BillingServicesModule } from "./billing-services.module"; @@ -19,10 +17,7 @@ export class StripeService { cardCvc: string; }; - constructor( - private logService: LogService, - private configService: ConfigService, - ) {} + constructor(private logService: LogService) {} /** * Loads [Stripe JS]{@link https://docs.stripe.com/js} in the element of the current page and mounts @@ -43,19 +38,10 @@ export class StripeService { const window$ = window as any; this.stripe = window$.Stripe(process.env.STRIPE_KEY); this.elements = this.stripe.elements(); - const isExtensionRefresh = await this.configService.getFeatureFlag( - FeatureFlag.ExtensionRefresh, - ); setTimeout(() => { - this.elements.create( - "cardNumber", - this.getElementOptions("cardNumber", isExtensionRefresh), - ); - this.elements.create( - "cardExpiry", - this.getElementOptions("cardExpiry", isExtensionRefresh), - ); - this.elements.create("cardCvc", this.getElementOptions("cardCvc", isExtensionRefresh)); + this.elements.create("cardNumber", this.getElementOptions("cardNumber")); + this.elements.create("cardExpiry", this.getElementOptions("cardExpiry")); + this.elements.create("cardCvc", this.getElementOptions("cardCvc")); if (autoMount) { this.mountElements(); } @@ -69,15 +55,21 @@ export class StripeService { * Re-mounts previously created Stripe credit card [elements]{@link https://docs.stripe.com/js/elements_object/create} into the HTML elements * specified during the {@link loadStripe} call. This is useful for when those HTML elements are removed from the DOM by Angular. */ - mountElements() { + mountElements(i: number = 0) { setTimeout(() => { + if (!document.querySelector(this.elementIds.cardNumber) && i < 10) { + this.logService.warning("Stripe container missing, retrying..."); + this.mountElements(i + 1); + return; + } + const cardNumber = this.elements.getElement("cardNumber"); const cardExpiry = this.elements.getElement("cardExpiry"); const cardCvc = this.elements.getElement("cardCvc"); cardNumber.mount(this.elementIds.cardNumber); cardExpiry.mount(this.elementIds.cardExpiry); cardCvc.mount(this.elementIds.cardCvc); - }); + }, 50); } /** @@ -150,10 +142,7 @@ export class StripeService { }, 500); } - private getElementOptions( - element: "cardNumber" | "cardExpiry" | "cardCvc", - isExtensionRefresh: boolean, - ): any { + private getElementOptions(element: "cardNumber" | "cardExpiry" | "cardCvc"): any { const options: any = { style: { base: { @@ -178,15 +167,12 @@ export class StripeService { }, }; - // Unique settings that should only be applied when the extension refresh flag is active - if (isExtensionRefresh) { - options.style.base.fontWeight = "500"; - options.classes.base = "v2"; + options.style.base.fontWeight = "500"; + options.classes.base = "v2"; - // Remove the placeholder for number and CVC fields - if (["cardNumber", "cardCvc"].includes(element)) { - options.placeholder = ""; - } + // Remove the placeholder for number and CVC fields + if (["cardNumber", "cardCvc"].includes(element)) { + options.placeholder = ""; } const style = getComputedStyle(document.documentElement); diff --git a/apps/web/src/app/billing/services/trial-flow.service.ts b/apps/web/src/app/billing/services/trial-flow.service.ts index a3a4ba6bba1..eb08e5bd7ad 100644 --- a/apps/web/src/app/billing/services/trial-flow.service.ts +++ b/apps/web/src/app/billing/services/trial-flow.service.ts @@ -16,11 +16,11 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { DialogService } from "@bitwarden/components"; -import { FreeTrial } from "../../core/types/free-trial"; import { ChangePlanDialogResultType, openChangePlanDialog, } from "../organizations/change-plan-dialog.component"; +import { FreeTrial } from "../types/free-trial"; @Injectable({ providedIn: "root" }) export class TrialFlowService { diff --git a/apps/web/src/app/billing/settings/sponsored-families.component.html b/apps/web/src/app/billing/settings/sponsored-families.component.html index a712a8456d6..12e942aaf18 100644 --- a/apps/web/src/app/billing/settings/sponsored-families.component.html +++ b/apps/web/src/app/billing/settings/sponsored-families.component.html @@ -2,7 +2,7 @@ - + {{ "loading" | i18n }} diff --git a/apps/web/src/app/billing/settings/sponsored-families.component.ts b/apps/web/src/app/billing/settings/sponsored-families.component.ts index 5e26e80a30a..c35fd3a2e61 100644 --- a/apps/web/src/app/billing/settings/sponsored-families.component.ts +++ b/apps/web/src/app/billing/settings/sponsored-families.component.ts @@ -19,9 +19,8 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { PlanSponsorshipType } from "@bitwarden/common/billing/enums"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; @@ -40,7 +39,6 @@ interface RequestSponsorshipForm { }) export class SponsoredFamiliesComponent implements OnInit, OnDestroy { loading = false; - isFreeFamilyFlagEnabled: boolean; availableSponsorshipOrgs$: Observable; activeSponsorshipOrgs$: Observable; @@ -63,7 +61,6 @@ export class SponsoredFamiliesComponent implements OnInit, OnDestroy { private formBuilder: FormBuilder, private accountService: AccountService, private toastService: ToastService, - private configService: ConfigService, private policyService: PolicyService, private freeFamiliesPolicyService: FreeFamiliesPolicyService, private router: Router, @@ -86,36 +83,28 @@ export class SponsoredFamiliesComponent implements OnInit, OnDestroy { } async ngOnInit() { - this.isFreeFamilyFlagEnabled = await this.configService.getFeatureFlag( - FeatureFlag.DisableFreeFamiliesSponsorship, + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + + await this.preventAccessToFreeFamiliesPage(); + + this.availableSponsorshipOrgs$ = combineLatest([ + this.organizationService.organizations$(userId), + this.policyService.getAll$(PolicyType.FreeFamiliesSponsorshipPolicy, userId), + ]).pipe( + map(([organizations, policies]) => + organizations + .filter((org) => org.familySponsorshipAvailable) + .map((org) => ({ + organization: org, + isPolicyEnabled: policies.some( + (policy) => policy.organizationId === org.id && policy.enabled, + ), + })) + .filter(({ isPolicyEnabled }) => !isPolicyEnabled) + .map(({ organization }) => organization), + ), ); - if (this.isFreeFamilyFlagEnabled) { - await this.preventAccessToFreeFamiliesPage(); - - this.availableSponsorshipOrgs$ = combineLatest([ - this.organizationService.organizations$, - this.policyService.getAll$(PolicyType.FreeFamiliesSponsorshipPolicy), - ]).pipe( - map(([organizations, policies]) => - organizations - .filter((org) => org.familySponsorshipAvailable) - .map((org) => ({ - organization: org, - isPolicyEnabled: policies.some( - (policy) => policy.organizationId === org.id && policy.enabled, - ), - })) - .filter(({ isPolicyEnabled }) => !isPolicyEnabled) - .map(({ organization }) => organization), - ), - ); - } else { - this.availableSponsorshipOrgs$ = this.organizationService.organizations$.pipe( - map((orgs) => orgs.filter((o) => o.familySponsorshipAvailable)), - ); - } - this.availableSponsorshipOrgs$.pipe(takeUntil(this._destroy)).subscribe((orgs) => { if (orgs.length === 1) { this.sponsorshipForm.patchValue({ @@ -126,9 +115,9 @@ export class SponsoredFamiliesComponent implements OnInit, OnDestroy { this.anyOrgsAvailable$ = this.availableSponsorshipOrgs$.pipe(map((orgs) => orgs.length > 0)); - this.activeSponsorshipOrgs$ = this.organizationService.organizations$.pipe( - map((orgs) => orgs.filter((o) => o.familySponsorshipFriendlyName !== null)), - ); + this.activeSponsorshipOrgs$ = this.organizationService + .organizations$(userId) + .pipe(map((orgs) => orgs.filter((o) => o.familySponsorshipFriendlyName !== null))); this.anyActiveSponsorships$ = this.activeSponsorshipOrgs$.pipe(map((orgs) => orgs.length > 0)); diff --git a/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts b/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts index b40902112c8..e613b862922 100644 --- a/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts +++ b/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts @@ -2,13 +2,14 @@ // @ts-strict-ignore import { formatDate } from "@angular/common"; import { Component, EventEmitter, Input, Output, OnInit } from "@angular/core"; -import { firstValueFrom, map, Observable } from "rxjs"; +import { firstValueFrom, map, Observable, switchMap } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +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 { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -27,7 +28,6 @@ export class SponsoringOrgRowComponent implements OnInit { statusMessage = "loading"; statusClass: "tw-text-success" | "tw-text-danger" = "tw-text-success"; isFreeFamilyPolicyEnabled$: Observable; - isFreeFamilyFlagEnabled: boolean; private locale = ""; constructor( @@ -38,6 +38,7 @@ export class SponsoringOrgRowComponent implements OnInit { private toastService: ToastService, private configService: ConfigService, private policyService: PolicyService, + private accountService: AccountService, ) {} async ngOnInit() { @@ -49,23 +50,20 @@ export class SponsoringOrgRowComponent implements OnInit { this.sponsoringOrg.familySponsorshipValidUntil, this.sponsoringOrg.familySponsorshipLastSyncDate, ); - this.isFreeFamilyFlagEnabled = await this.configService.getFeatureFlag( - FeatureFlag.DisableFreeFamiliesSponsorship, - ); - if (this.isFreeFamilyFlagEnabled) { - this.isFreeFamilyPolicyEnabled$ = this.policyService - .getAll$(PolicyType.FreeFamiliesSponsorshipPolicy) - .pipe( - map( - (policies) => - Array.isArray(policies) && - policies.some( - (policy) => policy.organizationId === this.sponsoringOrg.id && policy.enabled, - ), + this.isFreeFamilyPolicyEnabled$ = this.accountService.activeAccount$.pipe( + getUserId, + switchMap((userId) => + this.policyService.getAll$(PolicyType.FreeFamiliesSponsorshipPolicy, userId), + ), + map( + (policies) => + Array.isArray(policies) && + policies.some( + (policy) => policy.organizationId === this.sponsoringOrg.id && policy.enabled, ), - ); - } + ), + ); } async revokeSponsorship() { diff --git a/apps/web/src/app/billing/shared/add-credit-dialog.component.ts b/apps/web/src/app/billing/shared/add-credit-dialog.component.ts index 71afde81ee3..7860d456685 100644 --- a/apps/web/src/app/billing/shared/add-credit-dialog.component.ts +++ b/apps/web/src/app/billing/shared/add-credit-dialog.component.ts @@ -6,7 +6,10 @@ import { FormControl, FormGroup, Validators } from "@angular/forms"; import { firstValueFrom, map } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { PaymentMethodType } from "@bitwarden/common/billing/enums"; import { BitPayInvoiceRequest } from "@bitwarden/common/billing/models/request/bit-pay-invoice.request"; @@ -77,7 +80,14 @@ export class AddCreditDialogComponent implements OnInit { this.creditAmount = "20.00"; } this.ppButtonCustomField = "organization_id:" + this.organizationId; - const org = await this.organizationService.get(this.organizationId); + const userId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + const org = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(this.organizationId)), + ); if (org != null) { this.subject = org.name; this.name = org.name; diff --git a/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog-v2.component.html b/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog-v2.component.html deleted file mode 100644 index bb06f87ca03..00000000000 --- a/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog-v2.component.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - diff --git a/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog-v2.component.ts b/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog-v2.component.ts deleted file mode 100644 index 0a72b8302bc..00000000000 --- a/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog-v2.component.ts +++ /dev/null @@ -1,177 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; -import { Component, forwardRef, Inject, OnInit, ViewChild } from "@angular/core"; - -import { ManageTaxInformationComponent } from "@bitwarden/angular/billing/components"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; -import { PaymentMethodType, ProductTierType } from "@bitwarden/common/billing/enums"; -import { TaxInformation } from "@bitwarden/common/billing/models/domain"; -import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; -import { PaymentRequest } from "@bitwarden/common/billing/models/request/payment.request"; -import { UpdatePaymentMethodRequest } from "@bitwarden/common/billing/models/request/update-payment-method.request"; -import { TaxInfoResponse } from "@bitwarden/common/billing/models/response/tax-info.response"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { DialogService, ToastService } from "@bitwarden/components"; - -import { PaymentV2Component } from "../payment/payment-v2.component"; - -export interface AdjustPaymentDialogV2Params { - initialPaymentMethod?: PaymentMethodType; - organizationId?: string; - productTier?: ProductTierType; -} - -export enum AdjustPaymentDialogV2ResultType { - Closed = "closed", - Submitted = "submitted", -} - -@Component({ - templateUrl: "./adjust-payment-dialog-v2.component.html", -}) -export class AdjustPaymentDialogV2Component implements OnInit { - @ViewChild(PaymentV2Component) paymentComponent: PaymentV2Component; - @ViewChild(forwardRef(() => ManageTaxInformationComponent)) - taxInfoComponent: ManageTaxInformationComponent; - - protected readonly PaymentMethodType = PaymentMethodType; - protected readonly ResultType = AdjustPaymentDialogV2ResultType; - - protected dialogHeader: string; - protected initialPaymentMethod: PaymentMethodType; - protected organizationId?: string; - protected productTier?: ProductTierType; - - protected taxInformation: TaxInformation; - - constructor( - private apiService: ApiService, - private billingApiService: BillingApiServiceAbstraction, - private organizationApiService: OrganizationApiServiceAbstraction, - @Inject(DIALOG_DATA) protected dialogParams: AdjustPaymentDialogV2Params, - private dialogRef: DialogRef, - private i18nService: I18nService, - private toastService: ToastService, - ) { - const key = this.dialogParams.initialPaymentMethod ? "changePaymentMethod" : "addPaymentMethod"; - this.dialogHeader = this.i18nService.t(key); - this.initialPaymentMethod = this.dialogParams.initialPaymentMethod ?? PaymentMethodType.Card; - this.organizationId = this.dialogParams.organizationId; - this.productTier = this.dialogParams.productTier; - } - - ngOnInit(): void { - if (this.organizationId) { - this.organizationApiService - .getTaxInfo(this.organizationId) - .then((response: TaxInfoResponse) => { - this.taxInformation = TaxInformation.from(response); - }) - .catch(() => { - this.taxInformation = new TaxInformation(); - }); - } else { - this.apiService - .getTaxInfo() - .then((response: TaxInfoResponse) => { - this.taxInformation = TaxInformation.from(response); - }) - .catch(() => { - this.taxInformation = new TaxInformation(); - }); - } - } - - taxInformationChanged(event: TaxInformation) { - this.taxInformation = event; - if (event.country === "US") { - this.paymentComponent.showBankAccount = !!this.organizationId; - } else { - this.paymentComponent.showBankAccount = false; - if (this.paymentComponent.selected === PaymentMethodType.BankAccount) { - this.paymentComponent.select(PaymentMethodType.Card); - } - } - } - - submit = async (): Promise => { - if (!this.taxInfoComponent.validate()) { - return; - } - - try { - if (!this.organizationId) { - await this.updatePremiumUserPaymentMethod(); - } else { - await this.updateOrganizationPaymentMethod(); - } - - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("updatedPaymentMethod"), - }); - - this.dialogRef.close(AdjustPaymentDialogV2ResultType.Submitted); - } catch (error) { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t(error.message) || error.message, - }); - } - }; - - private updateOrganizationPaymentMethod = async () => { - const paymentSource = await this.paymentComponent.tokenize(); - - const request = new UpdatePaymentMethodRequest(); - request.paymentSource = paymentSource; - request.taxInformation = ExpandedTaxInfoUpdateRequest.From(this.taxInformation); - - await this.billingApiService.updateOrganizationPaymentMethod(this.organizationId, request); - }; - - protected get showTaxIdField(): boolean { - if (!this.organizationId) { - return false; - } - - switch (this.productTier) { - case ProductTierType.Free: - case ProductTierType.Families: - return false; - default: - return true; - } - } - - private updatePremiumUserPaymentMethod = async () => { - const { type, token } = await this.paymentComponent.tokenize(); - - const request = new PaymentRequest(); - request.paymentMethodType = type; - request.paymentToken = token; - request.country = this.taxInformation.country; - request.postalCode = this.taxInformation.postalCode; - request.taxId = this.taxInformation.taxId; - request.state = this.taxInformation.state; - request.line1 = this.taxInformation.line1; - request.line2 = this.taxInformation.line2; - request.city = this.taxInformation.city; - request.state = this.taxInformation.state; - await this.apiService.postAccountPayment(request); - }; - - static open = ( - dialogService: DialogService, - dialogConfig: DialogConfig, - ) => - dialogService.open( - AdjustPaymentDialogV2Component, - dialogConfig, - ); -} diff --git a/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.html b/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.html index de607314354..4409ab56d60 100644 --- a/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.html +++ b/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.html @@ -1,30 +1,29 @@ - - - - - - - - - - - - + + + + + + + + + + diff --git a/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.ts b/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.ts index bbae5099afa..c29bb15c0b2 100644 --- a/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.ts +++ b/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.ts @@ -1,59 +1,69 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; -import { Component, Inject, OnInit, ViewChild } from "@angular/core"; -import { FormGroup } from "@angular/forms"; +import { Component, forwardRef, Inject, OnInit, ViewChild } from "@angular/core"; import { ManageTaxInformationComponent } from "@bitwarden/angular/billing/components"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { PaymentMethodType } from "@bitwarden/common/billing/enums"; +import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; +import { PaymentMethodType, ProductTierType } from "@bitwarden/common/billing/enums"; import { TaxInformation } from "@bitwarden/common/billing/models/domain"; +import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; import { PaymentRequest } from "@bitwarden/common/billing/models/request/payment.request"; +import { UpdatePaymentMethodRequest } from "@bitwarden/common/billing/models/request/update-payment-method.request"; import { TaxInfoResponse } from "@bitwarden/common/billing/models/response/tax-info.response"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { DialogService, ToastService } from "@bitwarden/components"; import { PaymentComponent } from "../payment/payment.component"; -export interface AdjustPaymentDialogData { - organizationId: string; - currentType: PaymentMethodType; +export interface AdjustPaymentDialogParams { + initialPaymentMethod?: PaymentMethodType; + organizationId?: string; + productTier?: ProductTierType; + providerId?: string; } -export enum AdjustPaymentDialogResult { - Adjusted = "adjusted", - Cancelled = "cancelled", +export enum AdjustPaymentDialogResultType { + Closed = "closed", + Submitted = "submitted", } @Component({ - templateUrl: "adjust-payment-dialog.component.html", + templateUrl: "./adjust-payment-dialog.component.html", }) export class AdjustPaymentDialogComponent implements OnInit { - @ViewChild(PaymentComponent, { static: true }) paymentComponent: PaymentComponent; - @ViewChild(ManageTaxInformationComponent) taxInfoComponent: ManageTaxInformationComponent; + @ViewChild(PaymentComponent) paymentComponent: PaymentComponent; + @ViewChild(forwardRef(() => ManageTaxInformationComponent)) + taxInfoComponent: ManageTaxInformationComponent; - organizationId: string; - currentType: PaymentMethodType; - paymentMethodType = PaymentMethodType; + protected readonly PaymentMethodType = PaymentMethodType; + protected readonly ResultType = AdjustPaymentDialogResultType; - protected DialogResult = AdjustPaymentDialogResult; - protected formGroup = new FormGroup({}); + protected dialogHeader: string; + protected initialPaymentMethod: PaymentMethodType; + protected organizationId?: string; + protected productTier?: ProductTierType; + protected providerId?: string; protected taxInformation: TaxInformation; constructor( - private dialogRef: DialogRef, - @Inject(DIALOG_DATA) protected data: AdjustPaymentDialogData, private apiService: ApiService, - private i18nService: I18nService, + private billingApiService: BillingApiServiceAbstraction, private organizationApiService: OrganizationApiServiceAbstraction, - private configService: ConfigService, + @Inject(DIALOG_DATA) protected dialogParams: AdjustPaymentDialogParams, + private dialogRef: DialogRef, + private i18nService: I18nService, private toastService: ToastService, ) { - this.organizationId = data.organizationId; - this.currentType = data.currentType; + const key = this.dialogParams.initialPaymentMethod ? "changePaymentMethod" : "addPaymentMethod"; + this.dialogHeader = this.i18nService.t(key); + this.initialPaymentMethod = this.dialogParams.initialPaymentMethod ?? PaymentMethodType.Card; + this.organizationId = this.dialogParams.organizationId; + this.productTier = this.dialogParams.productTier; + this.providerId = this.dialogParams.providerId; } ngOnInit(): void { @@ -66,6 +76,13 @@ export class AdjustPaymentDialogComponent implements OnInit { .catch(() => { this.taxInformation = new TaxInformation(); }); + } else if (this.providerId) { + this.billingApiService + .getProviderTaxInformation(this.providerId) + .then((response) => (this.taxInformation = TaxInformation.from(response))) + .catch(() => { + this.taxInformation = new TaxInformation(); + }); } else { this.apiService .getTaxInfo() @@ -78,65 +95,104 @@ export class AdjustPaymentDialogComponent implements OnInit { } } - submit = async () => { - if (!this.taxInfoComponent?.validate()) { - return; - } - - const request = new PaymentRequest(); - const response = this.paymentComponent.createPaymentToken().then((result) => { - request.paymentToken = result[0]; - request.paymentMethodType = result[1]; - request.postalCode = this.taxInformation?.postalCode; - request.country = this.taxInformation?.country; - request.taxId = this.taxInformation?.taxId; - if (this.organizationId == null) { - return this.apiService.postAccountPayment(request); - } else { - request.taxId = this.taxInformation?.taxId; - request.state = this.taxInformation?.state; - request.line1 = this.taxInformation?.line1; - request.line2 = this.taxInformation?.line2; - request.city = this.taxInformation?.city; - request.state = this.taxInformation?.state; - return this.organizationApiService.updatePayment(this.organizationId, request); - } - }); - await response; - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("updatedPaymentMethod"), - }); - this.dialogRef.close(AdjustPaymentDialogResult.Adjusted); - }; - taxInformationChanged(event: TaxInformation) { this.taxInformation = event; if (event.country === "US") { - this.paymentComponent.hideBank = !this.organizationId; + this.paymentComponent.showBankAccount = !!this.organizationId; } else { - this.paymentComponent.hideBank = true; - if (this.paymentComponent.method === PaymentMethodType.BankAccount) { - this.paymentComponent.method = PaymentMethodType.Card; - this.paymentComponent.changeMethod(); + this.paymentComponent.showBankAccount = false; + if (this.paymentComponent.selected === PaymentMethodType.BankAccount) { + this.paymentComponent.select(PaymentMethodType.Card); } } } - protected get showTaxIdField(): boolean { - return !!this.organizationId; - } -} + submit = async (): Promise => { + if (!this.taxInfoComponent.validate()) { + this.taxInfoComponent.markAllAsTouched(); + return; + } -/** - * Strongly typed helper to open a AdjustPaymentDialog - * @param dialogService Instance of the dialog service that will be used to open the dialog - * @param config Configuration for the dialog - */ -export function openAdjustPaymentDialog( - dialogService: DialogService, - config: DialogConfig, -) { - return dialogService.open(AdjustPaymentDialogComponent, config); + try { + if (this.organizationId) { + await this.updateOrganizationPaymentMethod(); + } else if (this.providerId) { + await this.updateProviderPaymentMethod(); + } else { + await this.updatePremiumUserPaymentMethod(); + } + + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("updatedPaymentMethod"), + }); + + this.dialogRef.close(AdjustPaymentDialogResultType.Submitted); + } catch (error) { + const msg = typeof error == "object" ? error.message : error; + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t(msg) || msg, + }); + } + }; + + private updateOrganizationPaymentMethod = async () => { + const paymentSource = await this.paymentComponent.tokenize(); + + const request = new UpdatePaymentMethodRequest(); + request.paymentSource = paymentSource; + request.taxInformation = ExpandedTaxInfoUpdateRequest.From(this.taxInformation); + + await this.billingApiService.updateOrganizationPaymentMethod(this.organizationId, request); + }; + + private updatePremiumUserPaymentMethod = async () => { + const { type, token } = await this.paymentComponent.tokenize(); + + const request = new PaymentRequest(); + request.paymentMethodType = type; + request.paymentToken = token; + request.country = this.taxInformation.country; + request.postalCode = this.taxInformation.postalCode; + request.taxId = this.taxInformation.taxId; + request.state = this.taxInformation.state; + request.line1 = this.taxInformation.line1; + request.line2 = this.taxInformation.line2; + request.city = this.taxInformation.city; + request.state = this.taxInformation.state; + await this.apiService.postAccountPayment(request); + }; + + private updateProviderPaymentMethod = async () => { + const paymentSource = await this.paymentComponent.tokenize(); + + const request = new UpdatePaymentMethodRequest(); + request.paymentSource = paymentSource; + request.taxInformation = ExpandedTaxInfoUpdateRequest.From(this.taxInformation); + + await this.billingApiService.updateProviderPaymentMethod(this.providerId, request); + }; + + protected get showTaxIdField(): boolean { + if (this.organizationId) { + switch (this.productTier) { + case ProductTierType.Free: + case ProductTierType.Families: + return false; + default: + return true; + } + } else { + return !!this.providerId; + } + } + + static open = ( + dialogService: DialogService, + dialogConfig: DialogConfig, + ) => + dialogService.open(AdjustPaymentDialogComponent, dialogConfig); } diff --git a/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog-v2.component.html b/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog-v2.component.html deleted file mode 100644 index 7b74379acb6..00000000000 --- a/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog-v2.component.html +++ /dev/null @@ -1,34 +0,0 @@ -
    - - -

    {{ body }}

    -
    - - {{ storageFieldLabel }} - - - - {{ "total" | i18n }} - {{ this.formGroup.value.storage }} GB × {{ this.price | currency: "$" }} = - {{ this.price * this.formGroup.value.storage | currency: "$" }} / - {{ this.cadence | i18n }} - - -
    -
    - - - - -
    -
    diff --git a/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog-v2.component.ts b/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog-v2.component.ts deleted file mode 100644 index ba7619729bf..00000000000 --- a/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog-v2.component.ts +++ /dev/null @@ -1,106 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; -import { Component, Inject } from "@angular/core"; -import { FormControl, FormGroup, Validators } from "@angular/forms"; - -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { StorageRequest } from "@bitwarden/common/models/request/storage.request"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { DialogService, ToastService } from "@bitwarden/components"; - -export interface AdjustStorageDialogV2Params { - price: number; - cadence: "month" | "year"; - type: "Add" | "Remove"; - organizationId?: string; -} - -export enum AdjustStorageDialogV2ResultType { - Submitted = "submitted", - Closed = "closed", -} - -@Component({ - templateUrl: "./adjust-storage-dialog-v2.component.html", -}) -export class AdjustStorageDialogV2Component { - protected formGroup = new FormGroup({ - storage: new FormControl(0, [ - Validators.required, - Validators.min(0), - Validators.max(99), - ]), - }); - - protected organizationId?: string; - protected price: number; - protected cadence: "month" | "year"; - - protected title: string; - protected body: string; - protected storageFieldLabel: string; - - protected ResultType = AdjustStorageDialogV2ResultType; - - constructor( - private apiService: ApiService, - @Inject(DIALOG_DATA) protected dialogParams: AdjustStorageDialogV2Params, - private dialogRef: DialogRef, - private i18nService: I18nService, - private organizationApiService: OrganizationApiServiceAbstraction, - private toastService: ToastService, - ) { - this.price = this.dialogParams.price; - this.cadence = this.dialogParams.cadence; - this.organizationId = this.dialogParams.organizationId; - switch (this.dialogParams.type) { - case "Add": - this.title = this.i18nService.t("addStorage"); - this.body = this.i18nService.t("storageAddNote"); - this.storageFieldLabel = this.i18nService.t("gbStorageAdd"); - break; - case "Remove": - this.title = this.i18nService.t("removeStorage"); - this.body = this.i18nService.t("storageRemoveNote"); - this.storageFieldLabel = this.i18nService.t("gbStorageRemove"); - break; - } - } - - submit = async () => { - const request = new StorageRequest(); - switch (this.dialogParams.type) { - case "Add": - request.storageGbAdjustment = this.formGroup.value.storage; - break; - case "Remove": - request.storageGbAdjustment = this.formGroup.value.storage * -1; - break; - } - - if (this.organizationId) { - await this.organizationApiService.updateStorage(this.organizationId, request); - } else { - await this.apiService.postAccountStorage(request); - } - - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("adjustedStorage", request.storageGbAdjustment.toString()), - }); - - this.dialogRef.close(this.ResultType.Submitted); - }; - - static open = ( - dialogService: DialogService, - dialogConfig: DialogConfig, - ) => - dialogService.open( - AdjustStorageDialogV2Component, - dialogConfig, - ); -} diff --git a/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.html b/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.html index a597a3ae5ea..832356477c4 100644 --- a/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.html +++ b/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.html @@ -1,17 +1,17 @@
    - + -

    {{ (add ? "storageAddNote" : "storageRemoveNote") | i18n }}

    +

    {{ body }}

    - {{ (add ? "gbStorageAdd" : "gbStorageRemove") | i18n }} - - - {{ "total" | i18n }}: - {{ formGroup.get("storageAdjustment").value || 0 }} GB × - {{ storageGbPrice | currency: "$" }} = {{ adjustedStorageTotal | currency: "$" }} /{{ - interval | i18n - }} + {{ storageFieldLabel }} + + + + {{ "total" | i18n }} + {{ this.formGroup.value.storage }} GB × {{ this.price | currency: "$" }} = + {{ this.price * this.formGroup.value.storage | currency: "$" }} / + {{ this.cadence | i18n }}
    @@ -25,11 +25,10 @@ bitButton bitFormButton buttonType="secondary" - [bitDialogClose]="DialogResult.Cancelled" + [bitDialogClose]="ResultType.Closed" > {{ "cancel" | i18n }}
    - diff --git a/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.ts b/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.ts index f69f9e3eaad..4362e36f857 100644 --- a/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.ts +++ b/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.ts @@ -1,132 +1,103 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; -import { Component, Inject, ViewChild } from "@angular/core"; +import { Component, Inject } from "@angular/core"; import { FormControl, FormGroup, Validators } from "@angular/forms"; -import { ActivatedRoute, Router } from "@angular/router"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { PaymentResponse } from "@bitwarden/common/billing/models/response/payment.response"; import { StorageRequest } from "@bitwarden/common/models/request/storage.request"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { DialogService, ToastService } from "@bitwarden/components"; -import { PaymentComponent } from "../payment/payment.component"; - -export interface AdjustStorageDialogData { - storageGbPrice: number; - add: boolean; +export interface AdjustStorageDialogParams { + price: number; + cadence: "month" | "year"; + type: "Add" | "Remove"; organizationId?: string; - interval?: string; } -export enum AdjustStorageDialogResult { - Adjusted = "adjusted", - Cancelled = "cancelled", +export enum AdjustStorageDialogResultType { + Submitted = "submitted", + Closed = "closed", } @Component({ - templateUrl: "adjust-storage-dialog.component.html", + templateUrl: "./adjust-storage-dialog.component.html", }) export class AdjustStorageDialogComponent { - storageGbPrice: number; - add: boolean; - organizationId: string; - interval: string; - - @ViewChild(PaymentComponent, { static: true }) paymentComponent: PaymentComponent; - - protected DialogResult = AdjustStorageDialogResult; protected formGroup = new FormGroup({ - storageAdjustment: new FormControl(0, [ + storage: new FormControl(0, [ Validators.required, Validators.min(0), Validators.max(99), ]), }); + protected organizationId?: string; + protected price: number; + protected cadence: "month" | "year"; + + protected title: string; + protected body: string; + protected storageFieldLabel: string; + + protected ResultType = AdjustStorageDialogResultType; + constructor( - private dialogRef: DialogRef, - @Inject(DIALOG_DATA) protected data: AdjustStorageDialogData, private apiService: ApiService, + @Inject(DIALOG_DATA) protected dialogParams: AdjustStorageDialogParams, + private dialogRef: DialogRef, private i18nService: I18nService, - private platformUtilsService: PlatformUtilsService, - private router: Router, - private activatedRoute: ActivatedRoute, - private logService: LogService, private organizationApiService: OrganizationApiServiceAbstraction, private toastService: ToastService, ) { - this.storageGbPrice = data.storageGbPrice; - this.add = data.add; - this.organizationId = data.organizationId; - this.interval = data.interval || "year"; + this.price = this.dialogParams.price; + this.cadence = this.dialogParams.cadence; + this.organizationId = this.dialogParams.organizationId; + switch (this.dialogParams.type) { + case "Add": + this.title = this.i18nService.t("addStorage"); + this.body = this.i18nService.t("storageAddNote"); + this.storageFieldLabel = this.i18nService.t("gbStorageAdd"); + break; + case "Remove": + this.title = this.i18nService.t("removeStorage"); + this.body = this.i18nService.t("storageRemoveNote"); + this.storageFieldLabel = this.i18nService.t("gbStorageRemove"); + break; + } } submit = async () => { const request = new StorageRequest(); - request.storageGbAdjustment = this.formGroup.value.storageAdjustment; - if (!this.add) { - request.storageGbAdjustment *= -1; + switch (this.dialogParams.type) { + case "Add": + request.storageGbAdjustment = this.formGroup.value.storage; + break; + case "Remove": + request.storageGbAdjustment = this.formGroup.value.storage * -1; + break; } - let paymentFailed = false; - const action = async () => { - let response: Promise; - if (this.organizationId == null) { - response = this.apiService.postAccountStorage(request); - } else { - response = this.organizationApiService.updateStorage(this.organizationId, request); - } - const result = await response; - if (result != null && result.paymentIntentClientSecret != null) { - try { - await this.paymentComponent.handleStripeCardPayment( - result.paymentIntentClientSecret, - null, - ); - } catch { - paymentFailed = true; - } - } - }; - await action(); - this.dialogRef.close(AdjustStorageDialogResult.Adjusted); - if (paymentFailed) { - this.toastService.showToast({ - variant: "warning", - title: null, - message: this.i18nService.t("couldNotChargeCardPayInvoice"), - timeout: 10000, - }); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["../billing"], { relativeTo: this.activatedRoute }); + if (this.organizationId) { + await this.organizationApiService.updateStorage(this.organizationId, request); } else { - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("adjustedStorage", request.storageGbAdjustment.toString()), - }); + await this.apiService.postAccountStorage(request); } + + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("adjustedStorage", request.storageGbAdjustment.toString()), + }); + + this.dialogRef.close(this.ResultType.Submitted); }; - get adjustedStorageTotal(): number { - return this.storageGbPrice * this.formGroup.value.storageAdjustment; - } -} - -/** - * Strongly typed helper to open an AdjustStorageDialog - * @param dialogService Instance of the dialog service that will be used to open the dialog - * @param config Configuration for the dialog - */ -export function openAdjustStorageDialog( - dialogService: DialogService, - config: DialogConfig, -) { - return dialogService.open(AdjustStorageDialogComponent, config); + static open = ( + dialogService: DialogService, + dialogConfig: DialogConfig, + ) => + dialogService.open(AdjustStorageDialogComponent, dialogConfig); } diff --git a/apps/web/src/app/billing/shared/billing-shared.module.ts b/apps/web/src/app/billing/shared/billing-shared.module.ts index b9c235943ad..9a69755b209 100644 --- a/apps/web/src/app/billing/shared/billing-shared.module.ts +++ b/apps/web/src/app/billing/shared/billing-shared.module.ts @@ -6,13 +6,10 @@ import { HeaderModule } from "../../layouts/header/header.module"; import { SharedModule } from "../../shared"; import { AddCreditDialogComponent } from "./add-credit-dialog.component"; -import { AdjustPaymentDialogV2Component } from "./adjust-payment-dialog/adjust-payment-dialog-v2.component"; import { AdjustPaymentDialogComponent } from "./adjust-payment-dialog/adjust-payment-dialog.component"; -import { AdjustStorageDialogV2Component } from "./adjust-storage-dialog/adjust-storage-dialog-v2.component"; import { AdjustStorageDialogComponent } from "./adjust-storage-dialog/adjust-storage-dialog.component"; import { BillingHistoryComponent } from "./billing-history.component"; import { OffboardingSurveyComponent } from "./offboarding-survey.component"; -import { PaymentV2Component } from "./payment/payment-v2.component"; import { PaymentComponent } from "./payment/payment.component"; import { PaymentMethodComponent } from "./payment-method.component"; import { IndividualSelfHostingLicenseUploaderComponent } from "./self-hosting-license-uploader/individual-self-hosting-license-uploader.component"; @@ -26,40 +23,35 @@ import { VerifyBankAccountComponent } from "./verify-bank-account/verify-bank-ac @NgModule({ imports: [ SharedModule, - PaymentComponent, TaxInfoComponent, HeaderModule, BannerModule, - PaymentV2Component, + PaymentComponent, VerifyBankAccountComponent, ], declarations: [ AddCreditDialogComponent, - AdjustPaymentDialogComponent, - AdjustStorageDialogComponent, BillingHistoryComponent, PaymentMethodComponent, SecretsManagerSubscribeComponent, UpdateLicenseComponent, UpdateLicenseDialogComponent, OffboardingSurveyComponent, - AdjustPaymentDialogV2Component, - AdjustStorageDialogV2Component, + AdjustPaymentDialogComponent, + AdjustStorageDialogComponent, IndividualSelfHostingLicenseUploaderComponent, OrganizationSelfHostingLicenseUploaderComponent, ], exports: [ SharedModule, - PaymentComponent, TaxInfoComponent, - AdjustStorageDialogComponent, BillingHistoryComponent, SecretsManagerSubscribeComponent, UpdateLicenseComponent, UpdateLicenseDialogComponent, OffboardingSurveyComponent, VerifyBankAccountComponent, - PaymentV2Component, + PaymentComponent, IndividualSelfHostingLicenseUploaderComponent, OrganizationSelfHostingLicenseUploaderComponent, ], diff --git a/apps/web/src/app/billing/shared/index.ts b/apps/web/src/app/billing/shared/index.ts index 69a4b93bec8..54ab5bc0a2a 100644 --- a/apps/web/src/app/billing/shared/index.ts +++ b/apps/web/src/app/billing/shared/index.ts @@ -1,5 +1,4 @@ export * from "./billing-shared.module"; export * from "./payment-method.component"; -export * from "./payment/payment.component"; export * from "./sm-subscribe.component"; export * from "./tax-info.component"; diff --git a/apps/web/src/app/billing/shared/payment-method.component.ts b/apps/web/src/app/billing/shared/payment-method.component.ts index 149b4adf520..113a0eab6ca 100644 --- a/apps/web/src/app/billing/shared/payment-method.component.ts +++ b/apps/web/src/app/billing/shared/payment-method.component.ts @@ -4,12 +4,16 @@ import { Location } from "@angular/common"; import { Component, OnDestroy, OnInit } from "@angular/core"; import { FormBuilder, FormControl, Validators } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; -import { lastValueFrom } from "rxjs"; +import { firstValueFrom, lastValueFrom, map } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { PaymentMethodType } from "@bitwarden/common/billing/enums"; import { BillingPaymentResponse } from "@bitwarden/common/billing/models/response/billing-payment.response"; import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response"; @@ -20,19 +24,18 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { SyncService } from "@bitwarden/common/platform/sync"; import { DialogService, ToastService } from "@bitwarden/components"; -import { FreeTrial } from "../../core/types/free-trial"; import { TrialFlowService } from "../services/trial-flow.service"; +import { FreeTrial } from "../types/free-trial"; import { AddCreditDialogResult, openAddCreditDialog } from "./add-credit-dialog.component"; import { - AdjustPaymentDialogResult, - openAdjustPaymentDialog, + AdjustPaymentDialogComponent, + AdjustPaymentDialogResultType, } from "./adjust-payment-dialog/adjust-payment-dialog.component"; @Component({ templateUrl: "payment-method.component.html", }) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil export class PaymentMethodComponent implements OnInit, OnDestroy { loading = false; firstLoaded = false; @@ -73,6 +76,7 @@ export class PaymentMethodComponent implements OnInit, OnDestroy { private toastService: ToastService, private trialFlowService: TrialFlowService, private organizationService: OrganizationService, + private accountService: AccountService, protected syncService: SyncService, ) { const state = this.router.getCurrentNavigation()?.extras?.state; @@ -117,7 +121,14 @@ export class PaymentMethodComponent implements OnInit, OnDestroy { const organizationSubscriptionPromise = this.organizationApiService.getSubscription( this.organizationId, ); - const organizationPromise = this.organizationService.get(this.organizationId); + const userId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + const organizationPromise = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(this.organizationId)), + ); [this.billing, this.org, this.organization] = await Promise.all([ billingPromise, @@ -158,14 +169,16 @@ export class PaymentMethodComponent implements OnInit, OnDestroy { }; changePayment = async () => { - const dialogRef = openAdjustPaymentDialog(this.dialogService, { + const dialogRef = AdjustPaymentDialogComponent.open(this.dialogService, { data: { organizationId: this.organizationId, - currentType: this.paymentSource !== null ? this.paymentSource.type : null, + initialPaymentMethod: this.paymentSource !== null ? this.paymentSource.type : null, }, }); + const result = await lastValueFrom(dialogRef.closed); - if (result === AdjustPaymentDialogResult.Adjusted) { + + if (result === AdjustPaymentDialogResultType.Submitted) { this.location.replaceState(this.location.path(), "", {}); if (this.launchPaymentModalAutomatically && !this.organization.enabled) { await this.syncService.fullSync(true); diff --git a/apps/web/src/app/billing/shared/payment/payment-label-v2.component.html b/apps/web/src/app/billing/shared/payment/payment-label-v2.component.html deleted file mode 100644 index a2e1c92a050..00000000000 --- a/apps/web/src/app/billing/shared/payment/payment-label-v2.component.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - -
    - - - ({{ "required" | i18n }}) - -
    -
    - - - - diff --git a/apps/web/src/app/billing/shared/payment/payment-label-v2.component.ts b/apps/web/src/app/billing/shared/payment/payment-label-v2.component.ts deleted file mode 100644 index f4d0f097766..00000000000 --- a/apps/web/src/app/billing/shared/payment/payment-label-v2.component.ts +++ /dev/null @@ -1,38 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { booleanAttribute, Component, Input, OnInit } from "@angular/core"; - -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { FormFieldModule } from "@bitwarden/components"; - -import { SharedModule } from "../../../shared"; - -/** - * Label that should be used for elements loaded via Stripe API. - * - * Applies the same label styles from CL form-field component when - * the `ExtensionRefresh` flag is set. - */ -@Component({ - selector: "app-payment-label-v2", - templateUrl: "./payment-label-v2.component.html", - standalone: true, - imports: [FormFieldModule, SharedModule], -}) -export class PaymentLabelV2 implements OnInit { - /** `id` of the associated input */ - @Input({ required: true }) for: string; - /** Displays required text on the label */ - @Input({ transform: booleanAttribute }) required = false; - - protected extensionRefreshFlag = false; - - constructor(private configService: ConfigService) {} - - async ngOnInit(): Promise { - this.extensionRefreshFlag = await this.configService.getFeatureFlag( - FeatureFlag.ExtensionRefresh, - ); - } -} diff --git a/apps/web/src/app/billing/shared/payment/payment-label.component.html b/apps/web/src/app/billing/shared/payment/payment-label.component.html new file mode 100644 index 00000000000..a931b0524e3 --- /dev/null +++ b/apps/web/src/app/billing/shared/payment/payment-label.component.html @@ -0,0 +1,13 @@ + + + + +
    + + + ({{ "required" | i18n }}) + +
    diff --git a/apps/web/src/app/billing/shared/payment/payment-label.component.ts b/apps/web/src/app/billing/shared/payment/payment-label.component.ts new file mode 100644 index 00000000000..80a4087456d --- /dev/null +++ b/apps/web/src/app/billing/shared/payment/payment-label.component.ts @@ -0,0 +1,27 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { booleanAttribute, Component, Input } from "@angular/core"; + +import { FormFieldModule } from "@bitwarden/components"; + +import { SharedModule } from "../../../shared"; + +/** + * Label that should be used for elements loaded via Stripe API. + * + * Applies the same label styles from CL form-field component + */ +@Component({ + selector: "app-payment-label", + templateUrl: "./payment-label.component.html", + standalone: true, + imports: [FormFieldModule, SharedModule], +}) +export class PaymentLabelComponent { + /** `id` of the associated input */ + @Input({ required: true }) for: string; + /** Displays required text on the label */ + @Input({ transform: booleanAttribute }) required = false; + + constructor() {} +} diff --git a/apps/web/src/app/billing/shared/payment/payment-v2.component.html b/apps/web/src/app/billing/shared/payment/payment-v2.component.html deleted file mode 100644 index 9804e6bc86f..00000000000 --- a/apps/web/src/app/billing/shared/payment/payment-v2.component.html +++ /dev/null @@ -1,152 +0,0 @@ -
    -
    - - - - - {{ "creditCard" | i18n }} - - - - - - {{ "bankAccount" | i18n }} - - - - - - {{ "payPal" | i18n }} - - - - - - {{ "accountCredit" | i18n }} - - - -
    - - -
    -
    - - {{ "number" | i18n }} - -
    -
    -
    - Visa, MasterCard, Discover, AmEx, JCB, Diners Club, UnionPay -
    -
    - - {{ "expiration" | i18n }} - -
    -
    -
    - - {{ "securityCodeSlashCVV" | i18n }} - - - - -
    -
    -
    -
    - - - - {{ "verifyBankAccountWithStatementDescriptorWarning" | i18n }} - -
    - - {{ "routingNumber" | i18n }} - - - - {{ "accountNumber" | i18n }} - - - - {{ "accountHolderName" | i18n }} - - - - {{ "bankAccountType" | i18n }} - - - - - - -
    -
    - - -
    -
    - {{ "paypalClickSubmit" | i18n }} -
    -
    - - - - {{ "makeSureEnoughCredit" | i18n }} - - - -
    diff --git a/apps/web/src/app/billing/shared/payment/payment-v2.component.ts b/apps/web/src/app/billing/shared/payment/payment-v2.component.ts deleted file mode 100644 index 10cf7ccb702..00000000000 --- a/apps/web/src/app/billing/shared/payment/payment-v2.component.ts +++ /dev/null @@ -1,193 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; -import { FormControl, FormGroup, Validators } from "@angular/forms"; -import { Subject } from "rxjs"; -import { takeUntil } from "rxjs/operators"; - -import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; -import { PaymentMethodType } from "@bitwarden/common/billing/enums"; -import { TokenizedPaymentSourceRequest } from "@bitwarden/common/billing/models/request/tokenized-payment-source.request"; - -import { SharedModule } from "../../../shared"; -import { BillingServicesModule, BraintreeService, StripeService } from "../../services"; - -import { PaymentLabelV2 } from "./payment-label-v2.component"; - -/** - * Render a form that allows the user to enter their payment method, tokenize it against one of our payment providers and, - * optionally, submit it using the {@link onSubmit} function if it is provided. - * - * This component is meant to replace the existing {@link PaymentComponent} which is using the deprecated Stripe Sources API. - */ -@Component({ - selector: "app-payment-v2", - templateUrl: "./payment-v2.component.html", - standalone: true, - imports: [BillingServicesModule, SharedModule, PaymentLabelV2], -}) -export class PaymentV2Component implements OnInit, OnDestroy { - /** Show account credit as a payment option. */ - @Input() showAccountCredit: boolean = true; - /** Show bank account as a payment option. */ - @Input() showBankAccount: boolean = true; - /** Show PayPal as a payment option. */ - @Input() showPayPal: boolean = true; - - /** The payment method selected by default when the component renders. */ - @Input() private initialPaymentMethod: PaymentMethodType = PaymentMethodType.Card; - /** If provided, will be invoked with the tokenized payment source during form submission. */ - @Input() protected onSubmit?: (request: TokenizedPaymentSourceRequest) => Promise; - - @Output() submitted = new EventEmitter(); - - private destroy$ = new Subject(); - - protected formGroup = new FormGroup({ - paymentMethod: new FormControl(null), - bankInformation: new FormGroup({ - routingNumber: new FormControl("", [Validators.required]), - accountNumber: new FormControl("", [Validators.required]), - accountHolderName: new FormControl("", [Validators.required]), - accountHolderType: new FormControl("", [Validators.required]), - }), - }); - - protected PaymentMethodType = PaymentMethodType; - - constructor( - private billingApiService: BillingApiServiceAbstraction, - private braintreeService: BraintreeService, - private stripeService: StripeService, - ) {} - - ngOnInit(): void { - this.formGroup.controls.paymentMethod.patchValue(this.initialPaymentMethod); - - this.stripeService.loadStripe( - { - cardNumber: "#stripe-card-number", - cardExpiry: "#stripe-card-expiry", - cardCvc: "#stripe-card-cvc", - }, - this.initialPaymentMethod === PaymentMethodType.Card, - ); - - if (this.showPayPal) { - this.braintreeService.loadBraintree( - "#braintree-container", - this.initialPaymentMethod === PaymentMethodType.PayPal, - ); - } - - this.formGroup - .get("paymentMethod") - .valueChanges.pipe(takeUntil(this.destroy$)) - .subscribe((type) => { - this.onPaymentMethodChange(type); - }); - } - - /** Programmatically select the provided payment method. */ - select = (paymentMethod: PaymentMethodType) => { - this.formGroup.get("paymentMethod").patchValue(paymentMethod); - }; - - protected submit = async () => { - const { type, token } = await this.tokenize(); - await this.onSubmit?.({ type, token }); - this.submitted.emit(type); - }; - - /** - * Tokenize the payment method information entered by the user against one of our payment providers. - * - * - {@link PaymentMethodType.Card} => [Stripe.confirmCardSetup]{@link https://docs.stripe.com/js/setup_intents/confirm_card_setup} - * - {@link PaymentMethodType.BankAccount} => [Stripe.confirmUsBankAccountSetup]{@link https://docs.stripe.com/js/setup_intents/confirm_us_bank_account_setup} - * - {@link PaymentMethodType.PayPal} => [Braintree.requestPaymentMethod]{@link https://braintree.github.io/braintree-web-drop-in/docs/current/Dropin.html#requestPaymentMethod} - * */ - async tokenize(): Promise<{ type: PaymentMethodType; token: string }> { - const type = this.selected; - - if (this.usingStripe) { - const clientSecret = await this.billingApiService.createSetupIntent(type); - - if (this.usingBankAccount) { - const token = await this.stripeService.setupBankAccountPaymentMethod(clientSecret, { - accountHolderName: this.formGroup.value.bankInformation.accountHolderName, - routingNumber: this.formGroup.value.bankInformation.routingNumber, - accountNumber: this.formGroup.value.bankInformation.accountNumber, - accountHolderType: this.formGroup.value.bankInformation.accountHolderType, - }); - return { - type, - token, - }; - } - - if (this.usingCard) { - const token = await this.stripeService.setupCardPaymentMethod(clientSecret); - return { - type, - token, - }; - } - } - - if (this.usingPayPal) { - const token = await this.braintreeService.requestPaymentMethod(); - return { - type, - token, - }; - } - - return null; - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - this.stripeService.unloadStripe(); - if (this.showPayPal) { - this.braintreeService.unloadBraintree(); - } - } - - private onPaymentMethodChange(type: PaymentMethodType): void { - switch (type) { - case PaymentMethodType.Card: { - this.stripeService.mountElements(); - break; - } - case PaymentMethodType.PayPal: { - this.braintreeService.createDropin(); - break; - } - } - } - - get selected(): PaymentMethodType { - return this.formGroup.value.paymentMethod; - } - - protected get usingAccountCredit(): boolean { - return this.selected === PaymentMethodType.Credit; - } - - protected get usingBankAccount(): boolean { - return this.selected === PaymentMethodType.BankAccount; - } - - protected get usingCard(): boolean { - return this.selected === PaymentMethodType.Card; - } - - protected get usingPayPal(): boolean { - return this.selected === PaymentMethodType.PayPal; - } - - private get usingStripe(): boolean { - return this.usingBankAccount || this.usingCard; - } -} diff --git a/apps/web/src/app/billing/shared/payment/payment.component.html b/apps/web/src/app/billing/shared/payment/payment.component.html index d4853713579..af261155171 100644 --- a/apps/web/src/app/billing/shared/payment/payment.component.html +++ b/apps/web/src/app/billing/shared/payment/payment.component.html @@ -1,96 +1,125 @@ -
    -
    - - +
    +
    + + - {{ "creditCard" | i18n }} + {{ "creditCard" | i18n }} + - + - {{ "bankAccount" | i18n }} + {{ "bankAccount" | i18n }} + - - PayPal + + + + {{ "payPal" | i18n }} + - + - {{ "accountCredit" | i18n }} + {{ "accountCredit" | i18n }} +
    - -
    -
    - {{ - "number" | i18n - }} -
    + + +
    +
    + + {{ "number" | i18n }} + +
    -
    +
    Visa, MasterCard, Discover, AmEx, JCB, Diners Club, UnionPay
    -
    - {{ - "expiration" | i18n - }} -
    +
    + + {{ "expiration" | i18n }} + +
    -
    - +
    + {{ "securityCodeSlashCVV" | i18n }} - -
    +
    +
    - + + - {{ "verifyBankAccountInitialDesc" | i18n }} {{ "verifyBankAccountFailureWarning" | i18n }} + {{ "verifyBankAccountWithStatementDescriptorWarning" | i18n }} -
    - +
    + {{ "routingNumber" | i18n }} - - - - {{ "accountNumber" | i18n }} - - - - {{ "accountHolderName" | i18n }} - - + + {{ "accountNumber" | i18n }} + + + + {{ "accountHolderName" | i18n }} + + + {{ "bankAccountType" | i18n }} - +
    - + +
    -
    +
    {{ "paypalClickSubmit" | i18n }}
    - - + + + {{ "makeSureEnoughCredit" | i18n }} - + -
    + + diff --git a/apps/web/src/app/billing/shared/payment/payment.component.ts b/apps/web/src/app/billing/shared/payment/payment.component.ts index e067a5ee490..c7c3e31c89f 100644 --- a/apps/web/src/app/billing/shared/payment/payment.component.ts +++ b/apps/web/src/app/billing/shared/payment/payment.component.ts @@ -1,330 +1,203 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Component, Input, OnDestroy, OnInit } from "@angular/core"; +import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; import { FormControl, FormGroup, Validators } from "@angular/forms"; -import { Subject, takeUntil } from "rxjs"; +import { Subject } from "rxjs"; +import { takeUntil } from "rxjs/operators"; -import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; import { PaymentMethodType } 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 { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { TokenizedPaymentSourceRequest } from "@bitwarden/common/billing/models/request/tokenized-payment-source.request"; import { SharedModule } from "../../../shared"; +import { BillingServicesModule, BraintreeService, StripeService } from "../../services"; -import { PaymentLabelV2 } from "./payment-label-v2.component"; +import { PaymentLabelComponent } from "./payment-label.component"; +/** + * Render a form that allows the user to enter their payment method, tokenize it against one of our payment providers and, + * optionally, submit it using the {@link onSubmit} function if it is provided. + */ @Component({ selector: "app-payment", - templateUrl: "payment.component.html", + templateUrl: "./payment.component.html", standalone: true, - imports: [SharedModule, PaymentLabelV2], + imports: [BillingServicesModule, SharedModule, PaymentLabelComponent], }) export class PaymentComponent implements OnInit, OnDestroy { - @Input() showMethods = true; - @Input() showOptions = true; - @Input() hideBank = false; - @Input() hidePaypal = false; - @Input() hideCredit = false; - @Input() trialFlow = false; + /** Show account credit as a payment option. */ + @Input() showAccountCredit: boolean = true; + /** Show bank account as a payment option. */ + @Input() showBankAccount: boolean = true; + /** Show PayPal as a payment option. */ + @Input() showPayPal: boolean = true; - @Input() - set method(value: PaymentMethodType) { - this._method = value; - this.paymentForm?.controls.method.setValue(value, { emitEvent: false }); - } + /** The payment method selected by default when the component renders. */ + @Input() private initialPaymentMethod: PaymentMethodType = PaymentMethodType.Card; + /** If provided, will be invoked with the tokenized payment source during form submission. */ + @Input() protected onSubmit?: (request: TokenizedPaymentSourceRequest) => Promise; - get method(): PaymentMethodType { - return this._method; - } - private _method: PaymentMethodType = PaymentMethodType.Card; + @Output() submitted = new EventEmitter(); private destroy$ = new Subject(); - protected paymentForm = new FormGroup({ - method: new FormControl(this.method), - bank: new FormGroup({ - routing_number: new FormControl(null, [Validators.required]), - account_number: new FormControl(null, [Validators.required]), - account_holder_name: new FormControl(null, [Validators.required]), - account_holder_type: new FormControl("", [Validators.required]), - currency: new FormControl("USD"), - country: new FormControl("US"), + + protected formGroup = new FormGroup({ + paymentMethod: new FormControl(null), + bankInformation: new FormGroup({ + routingNumber: new FormControl("", [Validators.required]), + accountNumber: new FormControl("", [Validators.required]), + accountHolderName: new FormControl("", [Validators.required]), + accountHolderType: new FormControl("", [Validators.required]), }), }); - paymentMethodType = PaymentMethodType; - private btScript: HTMLScriptElement; - private btInstance: any = null; - private stripeScript: HTMLScriptElement; - private stripe: any = null; - private stripeElements: any = null; - private stripeCardNumberElement: any = null; - private stripeCardExpiryElement: any = null; - private stripeCardCvcElement: any = null; - private StripeElementStyle: any; - private StripeElementClasses: any; + protected PaymentMethodType = PaymentMethodType; constructor( - private apiService: ApiService, - private logService: LogService, - private themingService: AbstractThemingService, - private configService: ConfigService, - ) { - this.stripeScript = window.document.createElement("script"); - this.stripeScript.src = "https://js.stripe.com/v3/?advancedFraudSignals=false"; - this.stripeScript.async = true; - this.stripeScript.onload = async () => { - this.stripe = (window as any).Stripe(process.env.STRIPE_KEY); - this.stripeElements = this.stripe.elements(); - await this.setStripeElement(); - }; - this.btScript = window.document.createElement("script"); - this.btScript.src = `scripts/dropin.js?cache=${process.env.CACHE_TAG}`; - this.btScript.async = true; - this.StripeElementStyle = { - base: { - color: null, - fontFamily: - '"DM Sans", "Helvetica Neue", Helvetica, Arial, sans-serif, ' + - '"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"', - fontSize: "16px", - fontSmoothing: "antialiased", - "::placeholder": { - color: null, - }, + private billingApiService: BillingApiServiceAbstraction, + private braintreeService: BraintreeService, + private stripeService: StripeService, + ) {} + + ngOnInit(): void { + this.formGroup.controls.paymentMethod.patchValue(this.initialPaymentMethod); + + this.stripeService.loadStripe( + { + cardNumber: "#stripe-card-number", + cardExpiry: "#stripe-card-expiry", + cardCvc: "#stripe-card-cvc", }, - invalid: { - color: null, - }, - }; - this.StripeElementClasses = { - focus: "is-focused", - empty: "is-empty", - invalid: "is-invalid", - }; - } - async ngOnInit() { - if (!this.showOptions) { - this.hidePaypal = this.method !== PaymentMethodType.PayPal; - this.hideBank = this.method !== PaymentMethodType.BankAccount; - this.hideCredit = this.method !== PaymentMethodType.Credit; - } - this.subscribeToTheme(); - window.document.head.appendChild(this.stripeScript); - if (!this.hidePaypal) { - window.document.head.appendChild(this.btScript); - } - this.paymentForm - .get("method") - .valueChanges.pipe(takeUntil(this.destroy$)) - .subscribe((v) => { - this.method = v; - this.changeMethod(); - }); - } - - ngOnDestroy() { - this.destroy$.next(); - this.destroy$.complete(); - window.document.head.removeChild(this.stripeScript); - window.setTimeout(() => { - Array.from(window.document.querySelectorAll("iframe")).forEach((el) => { - if (el.src != null && el.src.indexOf("stripe") > -1) { - try { - window.document.body.removeChild(el); - } catch (e) { - this.logService.error(e); - } - } - }); - }, 500); - if (!this.hidePaypal) { - window.document.head.removeChild(this.btScript); - window.setTimeout(() => { - Array.from(window.document.head.querySelectorAll("script")).forEach((el) => { - if (el.src != null && el.src.indexOf("paypal") > -1) { - try { - window.document.head.removeChild(el); - } catch (e) { - this.logService.error(e); - } - } - }); - const btStylesheet = window.document.head.querySelector("#braintree-dropin-stylesheet"); - if (btStylesheet != null) { - try { - window.document.head.removeChild(btStylesheet); - } catch (e) { - this.logService.error(e); - } - } - }, 500); - } - } - - changeMethod() { - this.btInstance = null; - if (this.method === PaymentMethodType.PayPal) { - window.setTimeout(() => { - (window as any).braintree.dropin.create( - { - authorization: process.env.BRAINTREE_KEY, - container: "#bt-dropin-container", - paymentOptionPriority: ["paypal"], - paypal: { - flow: "vault", - buttonStyle: { - label: "pay", - size: "medium", - shape: "pill", - color: "blue", - tagline: "false", - }, - }, - }, - (createErr: any, instance: any) => { - if (createErr != null) { - // eslint-disable-next-line - console.error(createErr); - return; - } - this.btInstance = instance; - }, - ); - }, 250); - } else { - void this.setStripeElement(); - } - } - - createPaymentToken(): Promise<[string, PaymentMethodType]> { - return new Promise((resolve, reject) => { - if (this.method === PaymentMethodType.Credit) { - resolve([null, this.method]); - } else if (this.method === PaymentMethodType.PayPal) { - this.btInstance - .requestPaymentMethod() - .then((payload: any) => { - resolve([payload.nonce, this.method]); - }) - .catch((err: any) => { - reject(err.message); - }); - } else if ( - this.method === PaymentMethodType.Card || - this.method === PaymentMethodType.BankAccount - ) { - if (this.method === PaymentMethodType.Card) { - // 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.apiService - .postSetupPayment() - .then((clientSecret) => - this.stripe.handleCardSetup(clientSecret, this.stripeCardNumberElement), - ) - .then((result: any) => { - if (result.error) { - reject(result.error.message); - } else if (result.setupIntent && result.setupIntent.status === "succeeded") { - resolve([result.setupIntent.payment_method, this.method]); - } else { - reject(); - } - }); - } else { - this.stripe - .createToken("bank_account", this.paymentForm.get("bank").value) - .then((result: any) => { - if (result.error) { - reject(result.error.message); - } else if (result.token && result.token.id != null) { - resolve([result.token.id, this.method]); - } else { - reject(); - } - }); - } - } - }); - } - - handleStripeCardPayment(clientSecret: string, successCallback: () => Promise): Promise { - return new Promise((resolve, reject) => { - if (this.showMethods && this.stripeCardNumberElement == null) { - reject(); - return; - } - const handleCardPayment = () => - this.showMethods - ? this.stripe.handleCardSetup(clientSecret, this.stripeCardNumberElement) - : this.stripe.handleCardSetup(clientSecret); - return handleCardPayment().then(async (result: any) => { - if (result.error) { - reject(result.error.message); - } else if (result.paymentIntent && result.paymentIntent.status === "succeeded") { - if (successCallback != null) { - await successCallback(); - } - resolve(); - } else { - reject(); - } - }); - }); - } - - private async setStripeElement() { - const extensionRefreshFlag = await this.configService.getFeatureFlag( - FeatureFlag.ExtensionRefresh, + this.initialPaymentMethod === PaymentMethodType.Card, ); - // Apply unique styles for extension refresh - if (extensionRefreshFlag) { - this.StripeElementStyle.base.fontWeight = "500"; - this.StripeElementClasses.base = "v2"; + if (this.showPayPal) { + this.braintreeService.loadBraintree( + "#braintree-container", + this.initialPaymentMethod === PaymentMethodType.PayPal, + ); } - window.setTimeout(() => { - if (this.showMethods && this.method === PaymentMethodType.Card) { - if (this.stripeCardNumberElement == null) { - this.stripeCardNumberElement = this.stripeElements.create("cardNumber", { - style: this.StripeElementStyle, - classes: this.StripeElementClasses, - placeholder: "", - }); - } - if (this.stripeCardExpiryElement == null) { - this.stripeCardExpiryElement = this.stripeElements.create("cardExpiry", { - style: this.StripeElementStyle, - classes: this.StripeElementClasses, - }); - } - if (this.stripeCardCvcElement == null) { - this.stripeCardCvcElement = this.stripeElements.create("cardCvc", { - style: this.StripeElementStyle, - classes: this.StripeElementClasses, - placeholder: "", - }); - } - this.stripeCardNumberElement.mount("#stripe-card-number-element"); - this.stripeCardExpiryElement.mount("#stripe-card-expiry-element"); - this.stripeCardCvcElement.mount("#stripe-card-cvc-element"); - } - }, 50); + this.formGroup + .get("paymentMethod") + .valueChanges.pipe(takeUntil(this.destroy$)) + .subscribe((type) => { + this.onPaymentMethodChange(type); + }); } - private subscribeToTheme() { - this.themingService.theme$.pipe(takeUntil(this.destroy$)).subscribe(() => { - const style = getComputedStyle(document.documentElement); - this.StripeElementStyle.base.color = `rgb(${style.getPropertyValue("--color-text-main")})`; - this.StripeElementStyle.base["::placeholder"].color = `rgb(${style.getPropertyValue( - "--color-text-muted", - )})`; - this.StripeElementStyle.invalid.color = `rgb(${style.getPropertyValue("--color-text-main")})`; - this.StripeElementStyle.invalid.borderColor = `rgb(${style.getPropertyValue( - "--color-danger-600", - )})`; - }); + /** Programmatically select the provided payment method. */ + select = (paymentMethod: PaymentMethodType) => { + this.formGroup.get("paymentMethod").patchValue(paymentMethod); + }; + + protected submit = async () => { + const { type, token } = await this.tokenize(); + await this.onSubmit?.({ type, token }); + this.submitted.emit(type); + }; + + /** + * Tokenize the payment method information entered by the user against one of our payment providers. + * + * - {@link PaymentMethodType.Card} => [Stripe.confirmCardSetup]{@link https://docs.stripe.com/js/setup_intents/confirm_card_setup} + * - {@link PaymentMethodType.BankAccount} => [Stripe.confirmUsBankAccountSetup]{@link https://docs.stripe.com/js/setup_intents/confirm_us_bank_account_setup} + * - {@link PaymentMethodType.PayPal} => [Braintree.requestPaymentMethod]{@link https://braintree.github.io/braintree-web-drop-in/docs/current/Dropin.html#requestPaymentMethod} + * */ + async tokenize(): Promise<{ type: PaymentMethodType; token: string }> { + const type = this.selected; + + if (this.usingStripe) { + const clientSecret = await this.billingApiService.createSetupIntent(type); + + if (this.usingBankAccount) { + this.formGroup.markAllAsTouched(); + if (this.formGroup.valid) { + const token = await this.stripeService.setupBankAccountPaymentMethod(clientSecret, { + accountHolderName: this.formGroup.value.bankInformation.accountHolderName, + routingNumber: this.formGroup.value.bankInformation.routingNumber, + accountNumber: this.formGroup.value.bankInformation.accountNumber, + accountHolderType: this.formGroup.value.bankInformation.accountHolderType, + }); + return { + type, + token, + }; + } else { + throw "Invalid input provided. Please ensure all required fields are filled out correctly and try again."; + } + } + + if (this.usingCard) { + const token = await this.stripeService.setupCardPaymentMethod(clientSecret); + return { + type, + token, + }; + } + } + + if (this.usingPayPal) { + const token = await this.braintreeService.requestPaymentMethod(); + return { + type, + token, + }; + } + + if (this.usingAccountCredit) { + return { + type: PaymentMethodType.Credit, + token: null, + }; + } + + return null; + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + this.stripeService.unloadStripe(); + if (this.showPayPal) { + this.braintreeService.unloadBraintree(); + } + } + + private onPaymentMethodChange(type: PaymentMethodType): void { + switch (type) { + case PaymentMethodType.Card: { + this.stripeService.mountElements(); + break; + } + case PaymentMethodType.PayPal: { + this.braintreeService.createDropin(); + break; + } + } + } + + get selected(): PaymentMethodType { + return this.formGroup.value.paymentMethod; + } + + protected get usingAccountCredit(): boolean { + return this.selected === PaymentMethodType.Credit; + } + + protected get usingBankAccount(): boolean { + return this.selected === PaymentMethodType.BankAccount; + } + + protected get usingCard(): boolean { + return this.selected === PaymentMethodType.Card; + } + + protected get usingPayPal(): boolean { + return this.selected === PaymentMethodType.PayPal; + } + + private get usingStripe(): boolean { + return this.usingBankAccount || this.usingCard; } } diff --git a/apps/web/src/app/billing/shared/self-hosting-license-uploader/organization-self-hosting-license-uploader.component.ts b/apps/web/src/app/billing/shared/self-hosting-license-uploader/organization-self-hosting-license-uploader.component.ts index 41cc977d46f..c8d5eac2099 100644 --- a/apps/web/src/app/billing/shared/self-hosting-license-uploader/organization-self-hosting-license-uploader.component.ts +++ b/apps/web/src/app/billing/shared/self-hosting-license-uploader/organization-self-hosting-license-uploader.component.ts @@ -7,7 +7,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.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"; diff --git a/apps/web/src/app/billing/shared/tax-info.component.ts b/apps/web/src/app/billing/shared/tax-info.component.ts index 214364e4cf2..74e2ab35cb9 100644 --- a/apps/web/src/app/billing/shared/tax-info.component.ts +++ b/apps/web/src/app/billing/shared/tax-info.component.ts @@ -15,13 +15,15 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { SharedModule } from "../../shared"; +/** + * @deprecated Use `ManageTaxInformationComponent` instead. + */ @Component({ selector: "app-tax-info", templateUrl: "tax-info.component.html", standalone: true, imports: [SharedModule], }) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil export class TaxInfoComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); @@ -88,7 +90,7 @@ export class TaxInfoComponent implements OnInit, OnDestroy { async ngOnInit() { // Provider setup - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe + // eslint-disable-next-line rxjs-angular/prefer-takeuntil this.route.queryParams.subscribe((params) => { this.providerId = params.providerId; }); diff --git a/apps/web/src/app/auth/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.html b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.html rename to apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.html diff --git a/apps/web/src/app/auth/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 similarity index 99% rename from apps/web/src/app/auth/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts rename to apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts index 96c50a81319..873ceea2ada 100644 --- a/apps/web/src/app/auth/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 @@ -25,13 +25,13 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { ToastService } from "@bitwarden/components"; +import { AcceptOrganizationInviteService } from "../../../auth/organization-invite/accept-organization.service"; import { OrganizationCreatedEvent, SubscriptionProduct, TrialOrganizationType, } from "../../../billing/accounts/trial-initiation/trial-billing-step.component"; import { RouterService } from "../../../core/router.service"; -import { AcceptOrganizationInviteService } from "../../organization-invite/accept-organization.service"; import { VerticalStepperComponent } from "../vertical-stepper/vertical-stepper.component"; export type InitiationPath = diff --git a/apps/web/src/app/auth/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver.spec.ts b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver.spec.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver.spec.ts rename to apps/web/src/app/billing/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver.spec.ts diff --git a/apps/web/src/app/auth/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver.ts b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver.ts rename to apps/web/src/app/billing/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver.ts diff --git a/apps/web/src/app/auth/trial-initiation/confirmation-details.component.html b/apps/web/src/app/billing/trial-initiation/confirmation-details.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/confirmation-details.component.html rename to apps/web/src/app/billing/trial-initiation/confirmation-details.component.html diff --git a/apps/web/src/app/auth/trial-initiation/confirmation-details.component.ts b/apps/web/src/app/billing/trial-initiation/confirmation-details.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/confirmation-details.component.ts rename to apps/web/src/app/billing/trial-initiation/confirmation-details.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/abm-enterprise-content.component.html b/apps/web/src/app/billing/trial-initiation/content/abm-enterprise-content.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/abm-enterprise-content.component.html rename to apps/web/src/app/billing/trial-initiation/content/abm-enterprise-content.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/abm-enterprise-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/abm-enterprise-content.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/abm-enterprise-content.component.ts rename to apps/web/src/app/billing/trial-initiation/content/abm-enterprise-content.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/abm-teams-content.component.html b/apps/web/src/app/billing/trial-initiation/content/abm-teams-content.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/abm-teams-content.component.html rename to apps/web/src/app/billing/trial-initiation/content/abm-teams-content.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/abm-teams-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/abm-teams-content.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/abm-teams-content.component.ts rename to apps/web/src/app/billing/trial-initiation/content/abm-teams-content.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/cnet-enterprise-content.component.html b/apps/web/src/app/billing/trial-initiation/content/cnet-enterprise-content.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/cnet-enterprise-content.component.html rename to apps/web/src/app/billing/trial-initiation/content/cnet-enterprise-content.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/cnet-enterprise-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/cnet-enterprise-content.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/cnet-enterprise-content.component.ts rename to apps/web/src/app/billing/trial-initiation/content/cnet-enterprise-content.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/cnet-individual-content.component.html b/apps/web/src/app/billing/trial-initiation/content/cnet-individual-content.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/cnet-individual-content.component.html rename to apps/web/src/app/billing/trial-initiation/content/cnet-individual-content.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/cnet-individual-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/cnet-individual-content.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/cnet-individual-content.component.ts rename to apps/web/src/app/billing/trial-initiation/content/cnet-individual-content.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/cnet-teams-content.component.html b/apps/web/src/app/billing/trial-initiation/content/cnet-teams-content.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/cnet-teams-content.component.html rename to apps/web/src/app/billing/trial-initiation/content/cnet-teams-content.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/cnet-teams-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/cnet-teams-content.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/cnet-teams-content.component.ts rename to apps/web/src/app/billing/trial-initiation/content/cnet-teams-content.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/default-content.component.html b/apps/web/src/app/billing/trial-initiation/content/default-content.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/default-content.component.html rename to apps/web/src/app/billing/trial-initiation/content/default-content.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/default-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/default-content.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/default-content.component.ts rename to apps/web/src/app/billing/trial-initiation/content/default-content.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/enterprise-content.component.html b/apps/web/src/app/billing/trial-initiation/content/enterprise-content.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/enterprise-content.component.html rename to apps/web/src/app/billing/trial-initiation/content/enterprise-content.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/enterprise-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/enterprise-content.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/enterprise-content.component.ts rename to apps/web/src/app/billing/trial-initiation/content/enterprise-content.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/enterprise1-content.component.html b/apps/web/src/app/billing/trial-initiation/content/enterprise1-content.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/enterprise1-content.component.html rename to apps/web/src/app/billing/trial-initiation/content/enterprise1-content.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/enterprise1-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/enterprise1-content.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/enterprise1-content.component.ts rename to apps/web/src/app/billing/trial-initiation/content/enterprise1-content.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/enterprise2-content.component.html b/apps/web/src/app/billing/trial-initiation/content/enterprise2-content.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/enterprise2-content.component.html rename to apps/web/src/app/billing/trial-initiation/content/enterprise2-content.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/enterprise2-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/enterprise2-content.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/enterprise2-content.component.ts rename to apps/web/src/app/billing/trial-initiation/content/enterprise2-content.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/logo-badges.component.html b/apps/web/src/app/billing/trial-initiation/content/logo-badges.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/logo-badges.component.html rename to apps/web/src/app/billing/trial-initiation/content/logo-badges.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/logo-badges.component.ts b/apps/web/src/app/billing/trial-initiation/content/logo-badges.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/logo-badges.component.ts rename to apps/web/src/app/billing/trial-initiation/content/logo-badges.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/logo-cnet-5-stars.component.html b/apps/web/src/app/billing/trial-initiation/content/logo-cnet-5-stars.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/logo-cnet-5-stars.component.html rename to apps/web/src/app/billing/trial-initiation/content/logo-cnet-5-stars.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/logo-cnet-5-stars.component.ts b/apps/web/src/app/billing/trial-initiation/content/logo-cnet-5-stars.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/logo-cnet-5-stars.component.ts rename to apps/web/src/app/billing/trial-initiation/content/logo-cnet-5-stars.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/logo-cnet.component.html b/apps/web/src/app/billing/trial-initiation/content/logo-cnet.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/logo-cnet.component.html rename to apps/web/src/app/billing/trial-initiation/content/logo-cnet.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/logo-cnet.component.ts b/apps/web/src/app/billing/trial-initiation/content/logo-cnet.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/logo-cnet.component.ts rename to apps/web/src/app/billing/trial-initiation/content/logo-cnet.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/logo-company-testimonial.component.html b/apps/web/src/app/billing/trial-initiation/content/logo-company-testimonial.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/logo-company-testimonial.component.html rename to apps/web/src/app/billing/trial-initiation/content/logo-company-testimonial.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/logo-company-testimonial.component.ts b/apps/web/src/app/billing/trial-initiation/content/logo-company-testimonial.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/logo-company-testimonial.component.ts rename to apps/web/src/app/billing/trial-initiation/content/logo-company-testimonial.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/logo-forbes.component.html b/apps/web/src/app/billing/trial-initiation/content/logo-forbes.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/logo-forbes.component.html rename to apps/web/src/app/billing/trial-initiation/content/logo-forbes.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/logo-forbes.component.ts b/apps/web/src/app/billing/trial-initiation/content/logo-forbes.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/logo-forbes.component.ts rename to apps/web/src/app/billing/trial-initiation/content/logo-forbes.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/logo-us-news.component.html b/apps/web/src/app/billing/trial-initiation/content/logo-us-news.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/logo-us-news.component.html rename to apps/web/src/app/billing/trial-initiation/content/logo-us-news.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/logo-us-news.component.ts b/apps/web/src/app/billing/trial-initiation/content/logo-us-news.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/logo-us-news.component.ts rename to apps/web/src/app/billing/trial-initiation/content/logo-us-news.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/review-blurb.component.html b/apps/web/src/app/billing/trial-initiation/content/review-blurb.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/review-blurb.component.html rename to apps/web/src/app/billing/trial-initiation/content/review-blurb.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/review-blurb.component.ts b/apps/web/src/app/billing/trial-initiation/content/review-blurb.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/review-blurb.component.ts rename to apps/web/src/app/billing/trial-initiation/content/review-blurb.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/review-logo.component.html b/apps/web/src/app/billing/trial-initiation/content/review-logo.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/review-logo.component.html rename to apps/web/src/app/billing/trial-initiation/content/review-logo.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/review-logo.component.ts b/apps/web/src/app/billing/trial-initiation/content/review-logo.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/review-logo.component.ts rename to apps/web/src/app/billing/trial-initiation/content/review-logo.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/secrets-manager-content.component.html b/apps/web/src/app/billing/trial-initiation/content/secrets-manager-content.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/secrets-manager-content.component.html rename to apps/web/src/app/billing/trial-initiation/content/secrets-manager-content.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/secrets-manager-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/secrets-manager-content.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/secrets-manager-content.component.ts rename to apps/web/src/app/billing/trial-initiation/content/secrets-manager-content.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/teams-content.component.html b/apps/web/src/app/billing/trial-initiation/content/teams-content.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/teams-content.component.html rename to apps/web/src/app/billing/trial-initiation/content/teams-content.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/teams-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/teams-content.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/teams-content.component.ts rename to apps/web/src/app/billing/trial-initiation/content/teams-content.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/teams1-content.component.html b/apps/web/src/app/billing/trial-initiation/content/teams1-content.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/teams1-content.component.html rename to apps/web/src/app/billing/trial-initiation/content/teams1-content.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/teams1-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/teams1-content.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/teams1-content.component.ts rename to apps/web/src/app/billing/trial-initiation/content/teams1-content.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/teams2-content.component.html b/apps/web/src/app/billing/trial-initiation/content/teams2-content.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/teams2-content.component.html rename to apps/web/src/app/billing/trial-initiation/content/teams2-content.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/teams2-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/teams2-content.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/teams2-content.component.ts rename to apps/web/src/app/billing/trial-initiation/content/teams2-content.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/teams3-content.component.html b/apps/web/src/app/billing/trial-initiation/content/teams3-content.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/teams3-content.component.html rename to apps/web/src/app/billing/trial-initiation/content/teams3-content.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/teams3-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/teams3-content.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/teams3-content.component.ts rename to apps/web/src/app/billing/trial-initiation/content/teams3-content.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component.html b/apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component.html similarity index 78% rename from apps/web/src/app/auth/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component.html rename to apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component.html index 0b6e44d4eb6..dddac598a46 100644 --- a/apps/web/src/app/auth/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component.html +++ b/apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component.html @@ -1,17 +1,4 @@ - - - - - - - - (); constructor( @@ -33,10 +29,10 @@ export abstract class BaseAcceptComponent implements OnInit { protected i18nService: I18nService, protected route: ActivatedRoute, protected authService: AuthService, - protected registerRouteService: RegisterRouteService, ) {} abstract authedHandler(qParams: Params): Promise; + abstract unauthedHandler(qParams: Params): Promise; async ngOnInit() { diff --git a/apps/web/src/app/components/environment-selector/environment-selector.component.html b/apps/web/src/app/components/environment-selector/environment-selector.component.html index a1fb1a8a0f3..0c93865c900 100644 --- a/apps/web/src/app/components/environment-selector/environment-selector.component.html +++ b/apps/web/src/app/components/environment-selector/environment-selector.component.html @@ -6,21 +6,20 @@ [attr.href]=" region == currentRegion ? 'javascript:void(0)' : region.urls.webVault + routeAndParams " - class="pr-4" > {{ region.domain }} - diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts index 8f21dfa2c8b..cc1e481d39b 100644 --- a/apps/web/src/app/core/core.module.ts +++ b/apps/web/src/app/core/core.module.ts @@ -33,6 +33,8 @@ import { SetPasswordJitService, SsoComponentService, LoginDecryptionOptionsService, + TwoFactorAuthComponentService, + TwoFactorAuthDuoComponentService, } from "@bitwarden/auth/angular"; import { InternalUserDecryptionOptionsServiceAbstraction, @@ -48,14 +50,18 @@ import { import { AccountApiService as AccountApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/account-api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; +import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { ClientType } from "@bitwarden/common/enums"; import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; +import { + VaultTimeout, + VaultTimeoutStringType, +} from "@bitwarden/common/key-management/vault-timeout"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { EnvironmentService, Urls, @@ -66,14 +72,21 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { 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 { ThemeType } from "@bitwarden/common/platform/enums"; +import { ThemeTypes } from "@bitwarden/common/platform/enums"; +// eslint-disable-next-line no-restricted-imports -- Needed for DI +import { + UnsupportedWebPushConnectionService, + WebPushConnectionService, +} from "@bitwarden/common/platform/notifications/internal"; import { AppIdService as DefaultAppIdService } from "@bitwarden/common/platform/services/app-id.service"; import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service"; -// eslint-disable-next-line import/no-restricted-paths -- Implementation for memory storage import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service"; import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner"; +import { DefaultSdkClientFactory } from "@bitwarden/common/platform/services/sdk/default-sdk-client-factory"; import { NoopSdkClientFactory } from "@bitwarden/common/platform/services/sdk/noop-sdk-client-factory"; +import { NoopSdkLoadService } from "@bitwarden/common/platform/services/sdk/noop-sdk-load.service"; import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider"; /* eslint-disable import/no-restricted-paths -- Implementation for memory storage */ import { GlobalStateProvider, StateProvider } from "@bitwarden/common/platform/state"; @@ -84,14 +97,15 @@ import { DefaultThemeStateService, ThemeStateService, } from "@bitwarden/common/platform/theming/theme-state.service"; -import { VaultTimeout, VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type"; +import { DialogService, ToastService } from "@bitwarden/components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { KdfConfigService, KeyService as KeyServiceAbstraction, BiometricsService, } from "@bitwarden/key-management"; -import { LockComponentService } from "@bitwarden/key-management/angular"; +import { LockComponentService } from "@bitwarden/key-management-ui"; +import { DefaultSshImportPromptService, SshImportPromptService } from "@bitwarden/vault"; import { flagEnabled } from "../../utils/flags"; import { PolicyListService } from "../admin-console/core/policy-list.service"; @@ -100,6 +114,8 @@ import { WebRegistrationFinishService, WebLoginComponentService, WebLoginDecryptionOptionsService, + WebTwoFactorAuthComponentService, + WebTwoFactorAuthDuoComponentService, } from "../auth"; import { WebSsoComponentService } from "../auth/core/services/login/web-sso-component.service"; import { AcceptOrganizationInviteService } from "../auth/organization-invite/accept-organization.service"; @@ -110,7 +126,7 @@ import { WebProcessReloadService } from "../key-management/services/web-process- import { WebBiometricsService } from "../key-management/web-biometric.service"; import { WebEnvironmentService } from "../platform/web-environment.service"; import { WebMigrationRunner } from "../platform/web-migration-runner"; -import { WebSdkClientFactory } from "../platform/web-sdk-client-factory"; +import { WebSdkLoadService } from "../platform/web-sdk-load.service"; import { WebStorageServiceProvider } from "../platform/web-storage-service.provider"; import { EventService } from "./event.service"; @@ -221,10 +237,10 @@ const safeProviders: SafeProvider[] = [ }), safeProvider({ provide: ThemeStateService, - useFactory: (globalStateProvider: GlobalStateProvider, configService: ConfigService) => + useFactory: (globalStateProvider: GlobalStateProvider) => // Web chooses to have Light as the default theme - new DefaultThemeStateService(globalStateProvider, configService, ThemeType.Light), - deps: [GlobalStateProvider, ConfigService], + new DefaultThemeStateService(globalStateProvider, ThemeTypes.Light), + deps: [GlobalStateProvider], }), safeProvider({ provide: CLIENT_TYPE, @@ -242,16 +258,29 @@ const safeProviders: SafeProvider[] = [ PolicyService, ], }), + safeProvider({ + provide: WebPushConnectionService, + // We can support web in the future by creating a worker + useClass: UnsupportedWebPushConnectionService, + deps: [], + }), safeProvider({ provide: LockComponentService, useClass: WebLockComponentService, deps: [], }), + // TODO: PM-18182 - Refactor component services into lazy loaded modules + safeProvider({ + provide: TwoFactorAuthComponentService, + useClass: WebTwoFactorAuthComponentService, + deps: [], + }), safeProvider({ provide: SetPasswordJitService, useClass: WebSetPasswordJitService, deps: [ ApiService, + MasterPasswordApiService, KeyServiceAbstraction, EncryptService, I18nServiceAbstraction, @@ -281,6 +310,7 @@ const safeProviders: SafeProvider[] = [ PasswordGenerationServiceAbstraction, PlatformUtilsService, SsoLoginServiceAbstraction, + Router, ], }), safeProvider({ @@ -288,9 +318,14 @@ const safeProviders: SafeProvider[] = [ useClass: DefaultCollectionAdminService, deps: [ApiService, KeyServiceAbstraction, EncryptService, CollectionService], }), + safeProvider({ + provide: SdkLoadService, + useClass: flagEnabled("sdk") ? WebSdkLoadService : NoopSdkLoadService, + deps: [], + }), safeProvider({ provide: SdkClientFactory, - useClass: flagEnabled("sdk") ? WebSdkClientFactory : NoopSdkClientFactory, + useClass: flagEnabled("sdk") ? DefaultSdkClientFactory : NoopSdkClientFactory, deps: [], }), safeProvider({ @@ -308,11 +343,21 @@ const safeProviders: SafeProvider[] = [ useClass: WebSsoComponentService, deps: [I18nServiceAbstraction], }), + safeProvider({ + provide: TwoFactorAuthDuoComponentService, + useClass: WebTwoFactorAuthDuoComponentService, + deps: [PlatformUtilsService], + }), safeProvider({ provide: LoginDecryptionOptionsService, useClass: WebLoginDecryptionOptionsService, deps: [MessagingService, RouterService, AcceptOrganizationInviteService], }), + safeProvider({ + provide: SshImportPromptService, + useClass: DefaultSshImportPromptService, + deps: [DialogService, ToastService, PlatformUtilsService, I18nServiceAbstraction], + }), ]; @NgModule({ diff --git a/apps/web/src/app/core/init.service.ts b/apps/web/src/app/core/init.service.ts index b3e6d691f75..1990ce1e1ce 100644 --- a/apps/web/src/app/core/init.service.ts +++ b/apps/web/src/app/core/init.service.ts @@ -5,16 +5,17 @@ import { firstValueFrom } from "rxjs"; import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction"; import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service"; -import { NotificationsService as NotificationsServiceAbstraction } from "@bitwarden/common/abstractions/notifications.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { DefaultVaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout"; import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service"; import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service"; +import { NotificationsService } from "@bitwarden/common/platform/notifications"; import { ContainerService } from "@bitwarden/common/platform/services/container.service"; import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service"; import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service"; -import { VaultTimeoutService } from "@bitwarden/common/services/vault-timeout/vault-timeout.service"; import { KeyService as KeyServiceAbstraction } from "@bitwarden/key-management"; import { VersionService } from "../platform/version.service"; @@ -23,8 +24,8 @@ import { VersionService } from "../platform/version.service"; export class InitService { constructor( @Inject(WINDOW) private win: Window, - private notificationsService: NotificationsServiceAbstraction, - private vaultTimeoutService: VaultTimeoutService, + private notificationsService: NotificationsService, + private vaultTimeoutService: DefaultVaultTimeoutService, private i18nService: I18nServiceAbstraction, private eventUploadService: EventUploadServiceAbstraction, private twoFactorService: TwoFactorServiceAbstraction, @@ -35,11 +36,13 @@ export class InitService { private userAutoUnlockKeyService: UserAutoUnlockKeyService, private accountService: AccountService, private versionService: VersionService, + private sdkLoadService: SdkLoadService, @Inject(DOCUMENT) private document: Document, ) {} init() { return async () => { + await this.sdkLoadService.loadAndInit(); await this.stateService.init(); const activeAccount = await firstValueFrom(this.accountService.activeAccount$); @@ -49,7 +52,7 @@ export class InitService { await this.userAutoUnlockKeyService.setUserKeyInMemoryIfAutoUserKeySet(activeAccount.id); } - setTimeout(() => this.notificationsService.init(), 3000); + this.notificationsService.startListening(); await this.vaultTimeoutService.init(true); await this.i18nService.init(); (this.eventUploadService as EventUploadService).init(true); diff --git a/apps/web/src/app/key-management/key-rotation/request/account-keys.request.ts b/apps/web/src/app/key-management/key-rotation/request/account-keys.request.ts new file mode 100644 index 00000000000..1c9b6c9ceca --- /dev/null +++ b/apps/web/src/app/key-management/key-rotation/request/account-keys.request.ts @@ -0,0 +1,10 @@ +export class AccountKeysRequest { + // Other keys encrypted by the userkey + userKeyEncryptedAccountPrivateKey: string; + accountPublicKey: string; + + constructor(userKeyEncryptedAccountPrivateKey: string, accountPublicKey: string) { + this.userKeyEncryptedAccountPrivateKey = userKeyEncryptedAccountPrivateKey; + this.accountPublicKey = accountPublicKey; + } +} 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 new file mode 100644 index 00000000000..95e54e16464 --- /dev/null +++ b/apps/web/src/app/key-management/key-rotation/request/master-password-unlock-data.request.ts @@ -0,0 +1,35 @@ +import { Argon2KdfConfig, KdfConfig, KdfType } from "@bitwarden/key-management"; + +export class MasterPasswordUnlockDataRequest { + kdfType: KdfType = KdfType.PBKDF2_SHA256; + kdfIterations: number = 0; + kdfMemory?: number; + kdfParallelism?: number; + + email: string; + masterKeyAuthenticationHash: string; + + masterKeyEncryptedUserKey: string; + + masterPasswordHint?: string; + + constructor( + kdfConfig: KdfConfig, + email: string, + masterKeyAuthenticationHash: string, + masterKeyEncryptedUserKey: string, + masterPasswordHash?: string, + ) { + this.kdfType = kdfConfig.kdfType; + this.kdfIterations = kdfConfig.iterations; + if (kdfConfig.kdfType === KdfType.Argon2id) { + this.kdfMemory = (kdfConfig as Argon2KdfConfig).memory; + this.kdfParallelism = (kdfConfig as Argon2KdfConfig).parallelism; + } + + this.email = email; + this.masterKeyAuthenticationHash = masterKeyAuthenticationHash; + this.masterKeyEncryptedUserKey = masterKeyEncryptedUserKey; + this.masterPasswordHint = masterPasswordHash; + } +} diff --git a/apps/web/src/app/key-management/key-rotation/request/rotate-user-account-keys.request.ts b/apps/web/src/app/key-management/key-rotation/request/rotate-user-account-keys.request.ts new file mode 100644 index 00000000000..f335bb66bbb --- /dev/null +++ b/apps/web/src/app/key-management/key-rotation/request/rotate-user-account-keys.request.ts @@ -0,0 +1,29 @@ +import { AccountKeysRequest } from "./account-keys.request"; +import { UnlockDataRequest } from "./unlock-data.request"; +import { UserDataRequest as AccountDataRequest } from "./userdata.request"; + +export class RotateUserAccountKeysRequest { + constructor( + accountUnlockData: UnlockDataRequest, + accountKeys: AccountKeysRequest, + accountData: AccountDataRequest, + oldMasterKeyAuthenticationHash: string, + ) { + this.accountUnlockData = accountUnlockData; + this.accountKeys = accountKeys; + this.accountData = accountData; + this.oldMasterKeyAuthenticationHash = oldMasterKeyAuthenticationHash; + } + + // Authentication for the request + oldMasterKeyAuthenticationHash: string; + + // All methods to get to the userkey + accountUnlockData: UnlockDataRequest; + + // Other keys encrypted by the userkey + accountKeys: AccountKeysRequest; + + // User vault data encrypted by the userkey + accountData: AccountDataRequest; +} diff --git a/apps/web/src/app/key-management/key-rotation/request/unlock-data.request.ts b/apps/web/src/app/key-management/key-rotation/request/unlock-data.request.ts new file mode 100644 index 00000000000..5cdb56a3e23 --- /dev/null +++ b/apps/web/src/app/key-management/key-rotation/request/unlock-data.request.ts @@ -0,0 +1,26 @@ +import { OrganizationUserResetPasswordWithIdRequest } from "@bitwarden/admin-console/common"; +import { WebauthnRotateCredentialRequest } from "@bitwarden/common/auth/models/request/webauthn-rotate-credential.request"; + +import { EmergencyAccessWithIdRequest } from "../../../auth/emergency-access/request/emergency-access-update.request"; + +import { MasterPasswordUnlockDataRequest } from "./master-password-unlock-data.request"; + +export class UnlockDataRequest { + // All methods to get to the userkey + masterPasswordUnlockData: MasterPasswordUnlockDataRequest; + emergencyAccessUnlockData: EmergencyAccessWithIdRequest[]; + organizationAccountRecoveryUnlockData: OrganizationUserResetPasswordWithIdRequest[]; + passkeyUnlockData: WebauthnRotateCredentialRequest[]; + + constructor( + masterPasswordUnlockData: MasterPasswordUnlockDataRequest, + emergencyAccessUnlockData: EmergencyAccessWithIdRequest[], + organizationAccountRecoveryUnlockData: OrganizationUserResetPasswordWithIdRequest[], + passkeyUnlockData: WebauthnRotateCredentialRequest[], + ) { + this.masterPasswordUnlockData = masterPasswordUnlockData; + this.emergencyAccessUnlockData = emergencyAccessUnlockData; + this.organizationAccountRecoveryUnlockData = organizationAccountRecoveryUnlockData; + this.passkeyUnlockData = passkeyUnlockData; + } +} diff --git a/apps/web/src/app/key-management/key-rotation/request/update-key.request.ts b/apps/web/src/app/key-management/key-rotation/request/update-key.request.ts index d407a709d4c..e8d9f0c4d09 100644 --- a/apps/web/src/app/key-management/key-rotation/request/update-key.request.ts +++ b/apps/web/src/app/key-management/key-rotation/request/update-key.request.ts @@ -1,10 +1,8 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { OrganizationUserResetPasswordWithIdRequest } from "@bitwarden/admin-console/common"; import { WebauthnRotateCredentialRequest } from "@bitwarden/common/auth/models/request/webauthn-rotate-credential.request"; -import { SendWithIdRequest } from "@bitwarden/common/src/tools/send/models/request/send-with-id.request"; -import { CipherWithIdRequest } from "@bitwarden/common/src/vault/models/request/cipher-with-id.request"; -import { FolderWithIdRequest } from "@bitwarden/common/src/vault/models/request/folder-with-id.request"; +import { SendWithIdRequest } from "@bitwarden/common/tools/send/models/request/send-with-id.request"; +import { CipherWithIdRequest } from "@bitwarden/common/vault/models/request/cipher-with-id.request"; +import { FolderWithIdRequest } from "@bitwarden/common/vault/models/request/folder-with-id.request"; import { EmergencyAccessWithIdRequest } from "../../../auth/emergency-access/request/emergency-access-update.request"; @@ -18,4 +16,10 @@ export class UpdateKeyRequest { emergencyAccessKeys: EmergencyAccessWithIdRequest[] = []; resetPasswordKeys: OrganizationUserResetPasswordWithIdRequest[] = []; webauthnKeys: WebauthnRotateCredentialRequest[] = []; + + constructor(masterPasswordHash: string, key: string, privateKey: string) { + this.masterPasswordHash = masterPasswordHash; + this.key = key; + this.privateKey = privateKey; + } } diff --git a/apps/web/src/app/key-management/key-rotation/request/userdata.request.ts b/apps/web/src/app/key-management/key-rotation/request/userdata.request.ts new file mode 100644 index 00000000000..02204428686 --- /dev/null +++ b/apps/web/src/app/key-management/key-rotation/request/userdata.request.ts @@ -0,0 +1,19 @@ +import { SendWithIdRequest } from "@bitwarden/common/tools/send/models/request/send-with-id.request"; +import { CipherWithIdRequest } from "@bitwarden/common/vault/models/request/cipher-with-id.request"; +import { FolderWithIdRequest } from "@bitwarden/common/vault/models/request/folder-with-id.request"; + +export class UserDataRequest { + ciphers: CipherWithIdRequest[]; + folders: FolderWithIdRequest[]; + sends: SendWithIdRequest[]; + + constructor( + ciphers: CipherWithIdRequest[], + folders: FolderWithIdRequest[], + sends: SendWithIdRequest[], + ) { + this.ciphers = ciphers; + this.folders = folders; + this.sends = sends; + } +} diff --git a/apps/web/src/app/key-management/key-rotation/user-key-rotation-api.service.ts b/apps/web/src/app/key-management/key-rotation/user-key-rotation-api.service.ts index 3c8adc886df..2a947359bcb 100644 --- a/apps/web/src/app/key-management/key-rotation/user-key-rotation-api.service.ts +++ b/apps/web/src/app/key-management/key-rotation/user-key-rotation-api.service.ts @@ -2,6 +2,7 @@ import { inject, Injectable } from "@angular/core"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { RotateUserAccountKeysRequest } from "./request/rotate-user-account-keys.request"; import { UpdateKeyRequest } from "./request/update-key.request"; @Injectable() @@ -11,4 +12,14 @@ export class UserKeyRotationApiService { postUserKeyUpdate(request: UpdateKeyRequest): Promise { return this.apiService.send("POST", "/accounts/key", request, true, false); } + + postUserKeyUpdateV2(request: RotateUserAccountKeysRequest): Promise { + return this.apiService.send( + "POST", + "/accounts/key-management/rotate-user-account-keys", + request, + true, + false, + ); + } } diff --git a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts index 4f2ae8f77e0..4aa0a753f63 100644 --- a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts +++ b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts @@ -1,32 +1,35 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; import { OrganizationUserResetPasswordWithIdRequest } from "@bitwarden/admin-console/common"; -import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { WebauthnRotateCredentialRequest } from "@bitwarden/common/auth/models/request/webauthn-rotate-credential.request"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; +import { VaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { SendWithIdRequest } from "@bitwarden/common/tools/send/models/request/send-with-id.request"; import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; import { UserId } from "@bitwarden/common/types/guid"; -import { UserKey, UserPrivateKey } from "@bitwarden/common/types/key"; +import { UserKey, UserPrivateKey, UserPublicKey } from "@bitwarden/common/types/key"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherWithIdRequest } from "@bitwarden/common/vault/models/request/cipher-with-id.request"; import { FolderWithIdRequest } from "@bitwarden/common/vault/models/request/folder-with-id.request"; -import { KeyService } from "@bitwarden/key-management"; +import { ToastService } from "@bitwarden/components"; +import { DEFAULT_KDF_CONFIG, KeyService } from "@bitwarden/key-management"; import { OrganizationUserResetPasswordService } from "../../admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service"; -import { WebauthnLoginAdminService } from "../core"; -import { EmergencyAccessService } from "../emergency-access"; -import { EmergencyAccessWithIdRequest } from "../emergency-access/request/emergency-access-update.request"; +import { WebauthnLoginAdminService } from "../../auth/core"; +import { EmergencyAccessService } from "../../auth/emergency-access"; +import { EmergencyAccessWithIdRequest } from "../../auth/emergency-access/request/emergency-access-update.request"; import { UserKeyRotationApiService } from "./user-key-rotation-api.service"; import { UserKeyRotationService } from "./user-key-rotation.service"; @@ -48,6 +51,9 @@ describe("KeyRotationService", () => { let mockSyncService: MockProxy; let mockWebauthnLoginAdminService: MockProxy; let mockLogService: MockProxy; + let mockVaultTimeoutService: MockProxy; + let mockToastService: MockProxy; + let mockI18nService: MockProxy; const mockUser = { id: "mockUserId" as UserId, @@ -71,6 +77,9 @@ describe("KeyRotationService", () => { mockSyncService = mock(); mockWebauthnLoginAdminService = mock(); mockLogService = mock(); + mockVaultTimeoutService = mock(); + mockToastService = mock(); + mockI18nService = mock(); keyRotationService = new UserKeyRotationService( mockUserVerificationService, @@ -86,6 +95,9 @@ describe("KeyRotationService", () => { mockSyncService, mockWebauthnLoginAdminService, mockLogService, + mockVaultTimeoutService, + mockToastService, + mockI18nService, ); }); @@ -94,7 +106,8 @@ describe("KeyRotationService", () => { }); describe("rotateUserKeyAndEncryptedData", () => { - let privateKey: BehaviorSubject; + let privateKey: BehaviorSubject; + let keyPair: BehaviorSubject<{ privateKey: UserPrivateKey; publicKey: UserPublicKey }>; beforeEach(() => { mockKeyService.makeUserKey.mockResolvedValue([ @@ -113,6 +126,8 @@ describe("KeyRotationService", () => { // Mock user verification mockUserVerificationService.verifyUserByMasterPassword.mockResolvedValue({ masterKey: "mockMasterKey" as any, + kdfConfig: DEFAULT_KDF_CONFIG, + email: "mockEmail", policyOptions: null, }); @@ -123,6 +138,12 @@ describe("KeyRotationService", () => { privateKey = new BehaviorSubject("mockPrivateKey" as any); mockKeyService.userPrivateKeyWithLegacySupport$.mockReturnValue(privateKey); + keyPair = new BehaviorSubject({ + privateKey: "mockPrivateKey", + publicKey: "mockPublicKey", + } as any); + mockKeyService.userEncryptionKeyPair$.mockReturnValue(keyPair); + // Mock ciphers const mockCiphers = [createMockCipher("1", "Cipher 1"), createMockCipher("2", "Cipher 2")]; mockCipherService.getRotatedData.mockResolvedValue(mockCiphers); @@ -148,8 +169,8 @@ describe("KeyRotationService", () => { mockWebauthnLoginAdminService.getRotatedData.mockResolvedValue(webauthn); }); - it("rotates the user key and encrypted data", async () => { - await keyRotationService.rotateUserKeyAndEncryptedData("mockMasterPassword", mockUser); + it("rotates the user key and encrypted data legacy", async () => { + await keyRotationService.rotateUserKeyAndEncryptedDataLegacy("mockMasterPassword", mockUser); expect(mockApiService.postUserKeyUpdate).toHaveBeenCalled(); const arg = mockApiService.postUserKeyUpdate.mock.calls[0][0]; @@ -163,25 +184,92 @@ describe("KeyRotationService", () => { expect(arg.webauthnKeys.length).toBe(2); }); + it("rotates the user key and encrypted data", async () => { + await keyRotationService.rotateUserKeyMasterPasswordAndEncryptedData( + "mockMasterPassword", + "mockNewMasterPassword", + mockUser, + ); + + expect(mockApiService.postUserKeyUpdateV2).toHaveBeenCalled(); + const arg = mockApiService.postUserKeyUpdateV2.mock.calls[0][0]; + expect(arg.oldMasterKeyAuthenticationHash).toBe("mockMasterPasswordHash"); + expect(arg.accountUnlockData.masterPasswordUnlockData.email).toBe("mockEmail"); + expect(arg.accountUnlockData.masterPasswordUnlockData.kdfType).toBe( + DEFAULT_KDF_CONFIG.kdfType, + ); + expect(arg.accountUnlockData.masterPasswordUnlockData.kdfIterations).toBe( + DEFAULT_KDF_CONFIG.iterations, + ); + expect(arg.accountKeys.accountPublicKey).toBe(Utils.fromUtf8ToB64("mockPublicKey")); + expect(arg.accountKeys.userKeyEncryptedAccountPrivateKey).toBe("mockEncryptedData"); + expect(arg.accountData.ciphers.length).toBe(2); + expect(arg.accountData.folders.length).toBe(2); + expect(arg.accountData.sends.length).toBe(2); + }); + + it("legacy throws if master password provided is falsey", async () => { + await expect( + keyRotationService.rotateUserKeyAndEncryptedDataLegacy("", mockUser), + ).rejects.toThrow(); + }); + it("throws if master password provided is falsey", async () => { await expect( - keyRotationService.rotateUserKeyAndEncryptedData("", mockUser), + keyRotationService.rotateUserKeyMasterPasswordAndEncryptedData("", "", mockUser), + ).rejects.toThrow(); + }); + + it("legacy throws if user key creation fails", async () => { + mockKeyService.makeUserKey.mockResolvedValueOnce([null, null]); + + await expect( + keyRotationService.rotateUserKeyAndEncryptedDataLegacy("mockMasterPassword", mockUser), ).rejects.toThrow(); }); it("throws if user key creation fails", async () => { - mockKeyService.makeUserKey.mockResolvedValueOnce([null, null]); + mockKeyService.makeUserKey.mockResolvedValueOnce([ + null as unknown as UserKey, + null as unknown as EncString, + ]); await expect( - keyRotationService.rotateUserKeyAndEncryptedData("mockMasterPassword", mockUser), + keyRotationService.rotateUserKeyMasterPasswordAndEncryptedData( + "mockMasterPassword", + "mockMasterPassword1", + mockUser, + ), + ).rejects.toThrow(); + }); + + it("legacy throws if no private key is found", async () => { + privateKey.next(null); + + await expect( + keyRotationService.rotateUserKeyAndEncryptedDataLegacy("mockMasterPassword", mockUser), ).rejects.toThrow(); }); it("throws if no private key is found", async () => { - privateKey.next(null); + keyPair.next(null); await expect( - keyRotationService.rotateUserKeyAndEncryptedData("mockMasterPassword", mockUser), + keyRotationService.rotateUserKeyMasterPasswordAndEncryptedData( + "mockMasterPassword", + "mockMasterPassword1", + mockUser, + ), + ).rejects.toThrow(); + }); + + it("legacy throws if master password is incorrect", async () => { + mockUserVerificationService.verifyUserByMasterPassword.mockRejectedValueOnce( + new Error("Invalid master password"), + ); + + await expect( + keyRotationService.rotateUserKeyAndEncryptedDataLegacy("mockMasterPassword", mockUser), ).rejects.toThrow(); }); @@ -191,15 +279,31 @@ describe("KeyRotationService", () => { ); await expect( - keyRotationService.rotateUserKeyAndEncryptedData("mockMasterPassword", mockUser), + keyRotationService.rotateUserKeyMasterPasswordAndEncryptedData( + "mockMasterPassword", + "mockMasterPassword1", + mockUser, + ), + ).rejects.toThrow(); + }); + + it("legacy throws if server rotation fails", async () => { + mockApiService.postUserKeyUpdate.mockRejectedValueOnce(new Error("mockError")); + + await expect( + keyRotationService.rotateUserKeyAndEncryptedDataLegacy("mockMasterPassword", mockUser), ).rejects.toThrow(); }); it("throws if server rotation fails", async () => { - mockApiService.postUserKeyUpdate.mockRejectedValueOnce(new Error("mockError")); + mockApiService.postUserKeyUpdateV2.mockRejectedValueOnce(new Error("mockError")); await expect( - keyRotationService.rotateUserKeyAndEncryptedData("mockMasterPassword", mockUser), + keyRotationService.rotateUserKeyMasterPasswordAndEncryptedData( + "mockMasterPassword", + "mockMasterPassword1", + mockUser, + ), ).rejects.toThrow(); }); }); diff --git a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts index ae47798420e..c1b7a04d62b 100644 --- a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts +++ b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts @@ -1,15 +1,17 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Injectable } from "@angular/core"; import { firstValueFrom } from "rxjs"; import { Account } from "@bitwarden/common/auth/abstractions/account.service"; -import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { VerificationType } from "@bitwarden/common/auth/enums/verification-type"; import { MasterPasswordVerification } from "@bitwarden/common/auth/types/verification"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; +import { VaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { HashPurpose } from "@bitwarden/common/platform/enums"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncryptedString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; import { UserId } from "@bitwarden/common/types/guid"; @@ -17,13 +19,19 @@ import { UserKey } from "@bitwarden/common/types/key"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; +import { ToastService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; import { OrganizationUserResetPasswordService } from "../../admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service"; import { WebauthnLoginAdminService } from "../../auth/core"; import { EmergencyAccessService } from "../../auth/emergency-access"; +import { AccountKeysRequest } from "./request/account-keys.request"; +import { MasterPasswordUnlockDataRequest } from "./request/master-password-unlock-data.request"; +import { RotateUserAccountKeysRequest } from "./request/rotate-user-account-keys.request"; +import { UnlockDataRequest } from "./request/unlock-data.request"; import { UpdateKeyRequest } from "./request/update-key.request"; +import { UserDataRequest } from "./request/userdata.request"; import { UserKeyRotationApiService } from "./user-key-rotation-api.service"; @Injectable() @@ -42,14 +50,180 @@ export class UserKeyRotationService { private syncService: SyncService, private webauthnLoginAdminService: WebauthnLoginAdminService, private logService: LogService, + private vaultTimeoutService: VaultTimeoutService, + private toastService: ToastService, + private i18nService: I18nService, ) {} /** * Creates a new user key and re-encrypts all required data with the it. - * @param masterPassword current master password (used for validation) + * @param oldMasterPassword: The current master password + * @param newMasterPassword: The new master password + * @param user: The user account + * @param newMasterPasswordHint: The hint for the new master password */ - async rotateUserKeyAndEncryptedData(masterPassword: string, user: Account): Promise { + async rotateUserKeyMasterPasswordAndEncryptedData( + oldMasterPassword: string, + newMasterPassword: string, + user: Account, + newMasterPasswordHint?: string, + ): Promise { this.logService.info("[Userkey rotation] Starting user key rotation..."); + if (!newMasterPassword) { + this.logService.info("[Userkey rotation] Invalid master password provided. Aborting!"); + throw new Error("Invalid master password"); + } + + if ((await this.syncService.getLastSync()) === null) { + this.logService.info("[Userkey rotation] Client was never synced. Aborting!"); + throw new Error( + "The local vault is de-synced and the keys cannot be rotated. Please log out and log back in to resolve this issue.", + ); + } + + const { + masterKey: oldMasterKey, + email, + kdfConfig, + } = await this.userVerificationService.verifyUserByMasterPassword( + { + type: VerificationType.MasterPassword, + secret: oldMasterPassword, + }, + user.id, + user.email, + ); + + const newMasterKey = await this.keyService.makeMasterKey(newMasterPassword, email, kdfConfig); + + const [newUnencryptedUserKey, newMasterKeyEncryptedUserKey] = + await this.keyService.makeUserKey(newMasterKey); + + if (!newUnencryptedUserKey || !newMasterKeyEncryptedUserKey) { + this.logService.info("[Userkey rotation] User key could not be created. Aborting!"); + throw new Error("User key could not be created"); + } + + const newMasterKeyAuthenticationHash = await this.keyService.hashMasterKey( + newMasterPassword, + newMasterKey, + HashPurpose.ServerAuthorization, + ); + const masterPasswordUnlockData = new MasterPasswordUnlockDataRequest( + kdfConfig, + email, + newMasterKeyAuthenticationHash, + newMasterKeyEncryptedUserKey.encryptedString!, + newMasterPasswordHint, + ); + + const keyPair = await firstValueFrom(this.keyService.userEncryptionKeyPair$(user.id)); + if (keyPair == null) { + this.logService.info("[Userkey rotation] Key pair is null. Aborting!"); + throw new Error("Key pair is null"); + } + const { privateKey, publicKey } = keyPair; + + const accountKeysRequest = new AccountKeysRequest( + (await this.encryptService.encrypt(privateKey, newUnencryptedUserKey)).encryptedString!, + Utils.fromBufferToB64(publicKey), + ); + + const originalUserKey = await firstValueFrom(this.keyService.userKey$(user.id)); + if (originalUserKey == null) { + this.logService.info("[Userkey rotation] Userkey is null. Aborting!"); + throw new Error("Userkey key is null"); + } + + const rotatedCiphers = await this.cipherService.getRotatedData( + originalUserKey, + newUnencryptedUserKey, + user.id, + ); + const rotatedFolders = await this.folderService.getRotatedData( + originalUserKey, + newUnencryptedUserKey, + user.id, + ); + const rotatedSends = await this.sendService.getRotatedData( + originalUserKey, + newUnencryptedUserKey, + user.id, + ); + if (rotatedCiphers == null || rotatedFolders == null || rotatedSends == null) { + this.logService.info("[Userkey rotation] ciphers, folders, or sends are null. Aborting!"); + throw new Error("ciphers, folders, or sends are null"); + } + const accountDataRequest = new UserDataRequest(rotatedCiphers, rotatedFolders, rotatedSends); + + const emergencyAccessUnlockData = await this.emergencyAccessService.getRotatedData( + originalUserKey, + newUnencryptedUserKey, + user.id, + ); + // Note: Reset password keys request model has user verification + // properties, but the rotation endpoint uses its own MP hash. + const organizationAccountRecoveryUnlockData = await this.resetPasswordService.getRotatedData( + originalUserKey, + newUnencryptedUserKey, + user.id, + ); + if (organizationAccountRecoveryUnlockData == null) { + this.logService.info( + "[Userkey rotation] Organization account recovery data is null. Aborting!", + ); + throw new Error("Organization account recovery data is null"); + } + + const passkeyUnlockData = await this.webauthnLoginAdminService.getRotatedData( + originalUserKey, + newUnencryptedUserKey, + user.id, + ); + const unlockDataRequest = new UnlockDataRequest( + masterPasswordUnlockData, + emergencyAccessUnlockData, + organizationAccountRecoveryUnlockData, + passkeyUnlockData, + ); + + const request = new RotateUserAccountKeysRequest( + unlockDataRequest, + accountKeysRequest, + accountDataRequest, + await this.keyService.hashMasterKey(oldMasterPassword, oldMasterKey), + ); + + this.logService.info("[Userkey rotation] Posting user key rotation request to server"); + await this.apiService.postUserKeyUpdateV2(request); + this.logService.info("[Userkey rotation] Userkey rotation request posted to server"); + + // TODO PM-2199: Add device trust rotation support to the user key rotation endpoint + this.logService.info("[Userkey rotation] Rotating device trust..."); + await this.deviceTrustService.rotateDevicesTrust( + user.id, + newUnencryptedUserKey, + newMasterKeyAuthenticationHash, + ); + this.logService.info("[Userkey rotation] Device trust rotation completed"); + this.toastService.showToast({ + variant: "success", + title: this.i18nService.t("rotationCompletedTitle"), + message: this.i18nService.t("rotationCompletedDesc"), + timeout: 15000, + }); + + // temporary until userkey can be better verified + await this.vaultTimeoutService.logOut(); + } + + /** + * Creates a new user key and re-encrypts all required data with the it. + * @param masterPassword current master password (used for validation) + * @deprecated + */ + async rotateUserKeyAndEncryptedDataLegacy(masterPassword: string, user: Account): Promise { + this.logService.info("[Userkey rotation] Starting legacy user key rotation..."); if (!masterPassword) { this.logService.info("[Userkey rotation] Invalid master password provided. Aborting!"); throw new Error("Invalid master password"); @@ -77,20 +251,16 @@ export class UserKeyRotationService { const [newUserKey, newEncUserKey] = await this.keyService.makeUserKey(masterKey); - if (!newUserKey || !newEncUserKey) { + if (newUserKey == null || newEncUserKey == null || newEncUserKey.encryptedString == null) { this.logService.info("[Userkey rotation] User key could not be created. Aborting!"); throw new Error("User key could not be created"); } - // Create new request - const request = new UpdateKeyRequest(); - - // Add new user key - request.key = newEncUserKey.encryptedString; + // New user key + const key = newEncUserKey.encryptedString; // Add master key hash const masterPasswordHash = await this.keyService.hashMasterKey(masterPassword, masterKey); - request.masterPasswordHash = masterPasswordHash; // Get original user key // Note: We distribute the legacy key, but not all domains actually use it. If any of those @@ -101,7 +271,14 @@ export class UserKeyRotationService { this.logService.info("[Userkey rotation] Is legacy user: " + isMasterKey); // Add re-encrypted data - request.privateKey = await this.encryptPrivateKey(newUserKey, user.id); + const privateKey = await this.encryptPrivateKey(newUserKey, user.id); + if (privateKey == null) { + this.logService.info("[Userkey rotation] Private key could not be encrypted. Aborting!"); + throw new Error("Private key could not be encrypted"); + } + + // Create new request + const request = new UpdateKeyRequest(masterPasswordHash, key, privateKey); const rotatedCiphers = await this.cipherService.getRotatedData( originalUserKey, @@ -167,16 +344,17 @@ export class UserKeyRotationService { this.logService.info("[Userkey rotation] Rotating device trust..."); await this.deviceTrustService.rotateDevicesTrust(user.id, newUserKey, masterPasswordHash); this.logService.info("[Userkey rotation] Device trust rotation completed"); + await this.vaultTimeoutService.logOut(); } private async encryptPrivateKey( newUserKey: UserKey, userId: UserId, - ): Promise { + ): Promise { const privateKey = await firstValueFrom( this.keyService.userPrivateKeyWithLegacySupport$(userId), ); - if (!privateKey) { + if (privateKey == null) { throw new Error("No private key found for user key rotation"); } return (await this.encryptService.encrypt(privateKey, newUserKey)).encryptedString; diff --git a/apps/web/src/app/key-management/lock/services/web-lock-component.service.ts b/apps/web/src/app/key-management/lock/services/web-lock-component.service.ts index 02910966d6e..dd9f5138dba 100644 --- a/apps/web/src/app/key-management/lock/services/web-lock-component.service.ts +++ b/apps/web/src/app/key-management/lock/services/web-lock-component.service.ts @@ -7,7 +7,7 @@ import { } from "@bitwarden/auth/common"; import { UserId } from "@bitwarden/common/types/guid"; import { BiometricsStatus } from "@bitwarden/key-management"; -import { LockComponentService, UnlockOptions } from "@bitwarden/key-management/angular"; +import { LockComponentService, UnlockOptions } from "@bitwarden/key-management-ui"; export class WebLockComponentService implements LockComponentService { private readonly userDecryptionOptionsService = inject(UserDecryptionOptionsServiceAbstraction); @@ -34,8 +34,8 @@ export class WebLockComponentService implements LockComponentService { ); } - getAvailableUnlockOptions$(userId: UserId): Observable { - return this.userDecryptionOptionsService.userDecryptionOptionsById$(userId).pipe( + getAvailableUnlockOptions$(userId: UserId): Observable { + return this.userDecryptionOptionsService.userDecryptionOptionsById$(userId)?.pipe( map((userDecryptionOptions: UserDecryptionOptions) => { const unlockOpts: UnlockOptions = { masterPassword: { diff --git a/apps/web/src/app/key-management/migrate-encryption/migrate-legacy-encryption.component.ts b/apps/web/src/app/key-management/migrate-encryption/migrate-legacy-encryption.component.ts index 2361293fe80..62456d96401 100644 --- a/apps/web/src/app/key-management/migrate-encryption/migrate-legacy-encryption.component.ts +++ b/apps/web/src/app/key-management/migrate-encryption/migrate-legacy-encryption.component.ts @@ -1,10 +1,9 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Component } from "@angular/core"; import { FormControl, FormGroup, Validators } from "@angular/forms"; import { firstValueFrom } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -50,6 +49,9 @@ export class MigrateFromLegacyEncryptionComponent { } const activeUser = await firstValueFrom(this.accountService.activeAccount$); + if (activeUser == null) { + throw new Error("No active user."); + } const hasUserKey = await this.keyService.hasUserKey(activeUser.id); if (hasUserKey) { @@ -57,12 +59,12 @@ export class MigrateFromLegacyEncryptionComponent { throw new Error("User key already exists, cannot migrate legacy encryption."); } - const masterPassword = this.formGroup.value.masterPassword; + const masterPassword = this.formGroup.value.masterPassword!; try { await this.syncService.fullSync(false, true); - await this.keyRotationService.rotateUserKeyAndEncryptedData(masterPassword, activeUser); + await this.keyRotationService.rotateUserKeyAndEncryptedDataLegacy(masterPassword, activeUser); this.toastService.showToast({ variant: "success", @@ -73,7 +75,10 @@ export class MigrateFromLegacyEncryptionComponent { this.messagingService.send("logout"); } catch (e) { // If the error is due to missing folders, we can delete all folders and try again - if (e.message === "All existing folders must be included in the rotation.") { + if ( + e instanceof ErrorResponse && + e.message === "All existing folders must be included in the rotation." + ) { const deleteFolders = await this.dialogService.openSimpleDialog({ type: "warning", title: { key: "encryptionKeyUpdateCannotProceed" }, diff --git a/apps/web/src/app/layouts/frontend-layout.component.html b/apps/web/src/app/layouts/frontend-layout.component.html index 72f0f1f1da3..d19af54f5df 100644 --- a/apps/web/src/app/layouts/frontend-layout.component.html +++ b/apps/web/src/app/layouts/frontend-layout.component.html @@ -1,6 +1,8 @@ -
    + +
    - © {{ year }} Bitwarden Inc.
    - {{ "versionNumber" | i18n: version }} -
    + +
    © {{ year }} Bitwarden Inc.
    +
    {{ version }}
    + 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 7cba19b29ad..28d786f2d64 100644 --- a/apps/web/src/app/layouts/header/web-header.component.html +++ b/apps/web/src/app/layouts/header/web-header.component.html @@ -12,12 +12,14 @@

    - - {{ title || (routeData.titleId | i18n) }} - +
    + + {{ title || (routeData.titleId | i18n) }} +
    +

    diff --git a/apps/web/src/app/layouts/header/web-header.component.ts b/apps/web/src/app/layouts/header/web-header.component.ts index 2f0c9d4772b..e17d059160f 100644 --- a/apps/web/src/app/layouts/header/web-header.component.ts +++ b/apps/web/src/app/layouts/header/web-header.component.ts @@ -5,9 +5,11 @@ import { ActivatedRoute } from "@angular/router"; import { map, Observable } from "rxjs"; import { User } from "@bitwarden/angular/pipes/user-name.pipe"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; +import { + VaultTimeoutAction, + VaultTimeoutSettingsService, +} from "@bitwarden/common/key-management/vault-timeout"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { UserId } from "@bitwarden/common/types/guid"; diff --git a/apps/web/src/app/layouts/header/web-header.skip-stories.ts b/apps/web/src/app/layouts/header/web-header.skip-stories.ts deleted file mode 100644 index 8db4aea3061..00000000000 --- a/apps/web/src/app/layouts/header/web-header.skip-stories.ts +++ /dev/null @@ -1,232 +0,0 @@ -// import { CommonModule } from "@angular/common"; -// import { Component, importProvidersFrom, Injectable, Input } from "@angular/core"; -// import { RouterModule } from "@angular/router"; -// import { -// applicationConfig, -// componentWrapperDecorator, -// Meta, -// moduleMetadata, -// Story, -// } from "@storybook/angular"; -// import { BehaviorSubject, combineLatest, map, of } from "rxjs"; - -// import { JslibModule } from "@bitwarden/angular/jslib.module"; -// import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; -// import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; -// import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -// import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -// import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -// import { -// AvatarModule, -// BreadcrumbsModule, -// ButtonModule, -// IconButtonModule, -// IconModule, -// InputModule, -// MenuModule, -// NavigationModule, -// TabsModule, -// TypographyModule, -// } from "@bitwarden/components"; - -// import { DynamicAvatarComponent } from "../../components/dynamic-avatar.component"; -// import { PreloadedEnglishI18nModule } from "../../core/tests"; -// import { WebHeaderComponent } from "../header/web-header.component"; - -// import { WebLayoutMigrationBannerService } from "./web-layout-migration-banner.service"; - -// @Injectable({ -// providedIn: "root", -// }) -// class MockStateService { -// activeAccount$ = new BehaviorSubject("1").asObservable(); -// accounts$ = new BehaviorSubject({ "1": { profile: { name: "Foo" } } }).asObservable(); -// } - -// class MockMessagingService implements MessagingService { -// send(subscriber: string, arg?: any) { -// alert(subscriber); -// } -// } - -// class MockVaultTimeoutService { -// availableVaultTimeoutActions$() { -// return new BehaviorSubject([VaultTimeoutAction.Lock]).asObservable(); -// } -// } - -// class MockPlatformUtilsService { -// isSelfHost() { -// return false; -// } -// } - -// @Component({ -// selector: "product-switcher", -// template: ``, -// }) -// class MockProductSwitcher {} - -// @Component({ -// selector: "dynamic-avatar", -// template: ``, -// standalone: true, -// imports: [CommonModule, AvatarModule], -// }) -// class MockDynamicAvatar implements Partial { -// protected name$ = combineLatest([ -// this.stateService.accounts$, -// this.stateService.activeAccount$, -// ]).pipe( -// map( -// ([accounts, activeAccount]) => accounts[activeAccount as keyof typeof accounts].profile.name, -// ), -// ); - -// @Input() -// text: string; - -// constructor(private stateService: MockStateService) {} -// } - -// export default { -// title: "Web/Header", -// component: WebHeaderComponent, -// decorators: [ -// componentWrapperDecorator( -// (story) => `
    ${story}
    `, -// ), -// moduleMetadata({ -// imports: [ -// JslibModule, -// AvatarModule, -// BreadcrumbsModule, -// ButtonModule, -// IconButtonModule, -// IconModule, -// InputModule, -// MenuModule, -// TabsModule, -// TypographyModule, -// NavigationModule, -// MockDynamicAvatar, -// ], -// declarations: [WebHeaderComponent, MockProductSwitcher], -// providers: [ -// { provide: StateService, useClass: MockStateService }, -// { -// provide: WebLayoutMigrationBannerService, -// useValue: { -// showBanner$: of(false), -// } as Partial, -// }, -// { provide: PlatformUtilsService, useClass: MockPlatformUtilsService }, -// { provide: VaultTimeoutSettingsService, useClass: MockVaultTimeoutService }, -// { -// provide: MessagingService, -// useFactory: () => { -// return new MockMessagingService(); -// }, -// }, -// ], -// }), -// applicationConfig({ -// providers: [ -// importProvidersFrom(RouterModule.forRoot([], { useHash: true })), -// importProvidersFrom(PreloadedEnglishI18nModule), -// ], -// }), -// ], -// } as Meta; - -// export const KitchenSink: Story = (args) => ({ -// props: args, -// template: ` -// -// -// Foo -// Bar -// -// -// -// -// -// Foo -// Bar -// -// -// `, -// }); - -// export const Basic: Story = (args) => ({ -// props: args, -// template: ` -// -// `, -// }); - -// export const WithLongTitle: Story = (args) => ({ -// props: args, -// template: ` -// -// `, -// }); - -// export const WithBreadcrumbs: Story = (args) => ({ -// props: args, -// template: ` -// -// -// Foo -// Bar -// -// -// `, -// }); - -// export const WithSearch: Story = (args) => ({ -// props: args, -// template: ` -// -// -// -// `, -// }); - -// export const WithSecondaryContent: Story = (args) => ({ -// props: args, -// template: ` -// -// -// -// `, -// }); - -// export const WithTabs: Story = (args) => ({ -// props: args, -// template: ` -// -// -// Foo -// Bar -// -// -// `, -// }); - -// export const WithTitleSuffixComponent: Story = (args) => ({ -// props: args, -// template: ` -// -// -// -// `, -// }); diff --git a/apps/web/src/app/layouts/header/web-header.stories.ts b/apps/web/src/app/layouts/header/web-header.stories.ts new file mode 100644 index 00000000000..571e78aab59 --- /dev/null +++ b/apps/web/src/app/layouts/header/web-header.stories.ts @@ -0,0 +1,262 @@ +import { CommonModule } from "@angular/common"; +import { Component, importProvidersFrom, Injectable, Input } from "@angular/core"; +import { RouterModule } from "@angular/router"; +import { + applicationConfig, + componentWrapperDecorator, + Meta, + moduleMetadata, + StoryObj, +} from "@storybook/angular"; +import { BehaviorSubject, combineLatest, map, of } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { + VaultTimeoutAction, + VaultTimeoutSettingsService, +} from "@bitwarden/common/key-management/vault-timeout"; +import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; +import { + AvatarModule, + BreadcrumbsModule, + ButtonModule, + IconButtonModule, + IconModule, + InputModule, + MenuModule, + NavigationModule, + TabsModule, + TypographyModule, +} from "@bitwarden/components"; + +import { DynamicAvatarComponent } from "../../components/dynamic-avatar.component"; +import { PreloadedEnglishI18nModule } from "../../core/tests"; +import { WebHeaderComponent } from "../header/web-header.component"; + +import { WebLayoutMigrationBannerService } from "./web-layout-migration-banner.service"; + +@Injectable({ + providedIn: "root", +}) +class MockStateService { + activeAccount$ = new BehaviorSubject("1").asObservable(); + accounts$ = new BehaviorSubject({ "1": { profile: { name: "Foo" } } }).asObservable(); +} + +@Component({ + selector: "product-switcher", + template: ``, +}) +class MockProductSwitcher {} + +@Component({ + selector: "dynamic-avatar", + template: ``, + standalone: true, + imports: [CommonModule, AvatarModule], +}) +class MockDynamicAvatar implements Partial { + protected name$ = combineLatest([ + this.stateService.accounts$, + this.stateService.activeAccount$, + ]).pipe( + map( + ([accounts, activeAccount]) => accounts[activeAccount as keyof typeof accounts].profile.name, + ), + ); + + @Input() + text?: string; + + constructor(private stateService: MockStateService) {} +} + +export default { + title: "Web/Header", + component: WebHeaderComponent, + decorators: [ + componentWrapperDecorator( + (story) => `
    ${story}
    `, + ), + moduleMetadata({ + imports: [ + JslibModule, + AvatarModule, + BreadcrumbsModule, + ButtonModule, + IconButtonModule, + IconModule, + InputModule, + MenuModule, + TabsModule, + TypographyModule, + NavigationModule, + MockDynamicAvatar, + ], + declarations: [WebHeaderComponent, MockProductSwitcher], + providers: [ + { provide: StateService, useClass: MockStateService }, + { + provide: AccountService, + useValue: { + activeAccount$: of({ + name: "Foobar Warden", + }), + } as Partial, + }, + { + provide: WebLayoutMigrationBannerService, + useValue: { + showBanner$: of(false), + } as Partial, + }, + { + provide: PlatformUtilsService, + useValue: { + isSelfHost() { + return false; + }, + } as Partial, + }, + { + provide: VaultTimeoutSettingsService, + useValue: { + availableVaultTimeoutActions$() { + return new BehaviorSubject([VaultTimeoutAction.Lock]).asObservable(); + }, + } as Partial, + }, + { + provide: MessagingService, + useValue: { + send: (...args: any[]) => { + // eslint-disable-next-line no-console + console.log("MessagingService.send", args); + }, + } as Partial, + }, + ], + }), + applicationConfig({ + providers: [ + importProvidersFrom(RouterModule.forRoot([], { useHash: true })), + importProvidersFrom(PreloadedEnglishI18nModule), + ], + }), + ], +} as Meta; + +type Story = StoryObj; + +export const KitchenSink: Story = { + render: (args) => ({ + props: args, + template: ` + + + Foo + Bar + + + + + + Foo + Bar + + + `, + }), +}; + +export const Basic: Story = { + render: (args: any) => ({ + props: args, + template: ` + + `, + }), +}; + +export const WithLongTitle: Story = { + render: (arg: any) => ({ + props: arg, + template: ` + + + + `, + }), +}; + +export const WithBreadcrumbs: Story = { + render: (args: any) => ({ + props: args, + template: ` + + + Foo + Bar + + + `, + }), +}; + +export const WithSearch: Story = { + render: (args: any) => ({ + props: args, + template: ` + + + + `, + }), +}; + +export const WithSecondaryContent: Story = { + render: (args) => ({ + props: args, + template: ` + + + + `, + }), +}; + +export const WithTabs: Story = { + render: (args) => ({ + props: args, + template: ` + + + Foo + Bar + + + `, + }), +}; + +export const WithTitleSuffixComponent: Story = { + render: (args) => ({ + props: args, + template: ` + + + + `, + }), +}; diff --git a/apps/web/src/app/layouts/org-switcher/org-switcher.component.html b/apps/web/src/app/layouts/org-switcher/org-switcher.component.html index f34d32f5983..ef702c7e593 100644 --- a/apps/web/src/app/layouts/org-switcher/org-switcher.component.html +++ b/apps/web/src/app/layouts/org-switcher/org-switcher.component.html @@ -10,7 +10,7 @@ @@ -27,7 +27,7 @@ diff --git a/apps/web/src/app/layouts/org-switcher/org-switcher.component.ts b/apps/web/src/app/layouts/org-switcher/org-switcher.component.ts index b09b32d060e..d64e1b817c1 100644 --- a/apps/web/src/app/layouts/org-switcher/org-switcher.component.ts +++ b/apps/web/src/app/layouts/org-switcher/org-switcher.component.ts @@ -3,11 +3,12 @@ import { CommonModule } from "@angular/common"; import { Component, EventEmitter, Input, Output } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; -import { combineLatest, map, Observable } from "rxjs"; +import { combineLatest, map, Observable, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import type { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; import { DialogService, NavigationModule } from "@bitwarden/components"; @@ -20,12 +21,17 @@ import { TrialFlowService } from "./../../billing/services/trial-flow.service"; imports: [CommonModule, JslibModule, NavigationModule], }) export class OrgSwitcherComponent { - protected organizations$: Observable = - this.organizationService.organizations$.pipe( - map((orgs) => - orgs.filter((org) => this.filter(org)).sort((a, b) => a.name.localeCompare(b.name)), - ), - ); + protected organizations$: Observable = this.accountService.activeAccount$.pipe( + switchMap((account) => + this.organizationService + .organizations$(account?.id) + .pipe( + map((orgs) => + orgs.filter((org) => this.filter(org)).sort((a, b) => a.name.localeCompare(b.name)), + ), + ), + ), + ); protected activeOrganization$: Observable = combineLatest([ this.route.paramMap, @@ -61,6 +67,7 @@ export class OrgSwitcherComponent { private organizationService: OrganizationService, private trialFlowService: TrialFlowService, protected billingApiService: BillingApiServiceAbstraction, + private accountService: AccountService, ) {} protected toggle(event?: MouseEvent) { diff --git a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.spec.ts b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.spec.ts index 382ce8e026b..6e21c6c142a 100644 --- a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.spec.ts +++ b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.spec.ts @@ -7,6 +7,8 @@ import { BehaviorSubject } from "rxjs"; import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { IconButtonModule, NavigationModule } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { NavItemComponent } from "@bitwarden/components/src/navigation/nav-item.component"; import { ProductSwitcherItem, ProductSwitcherService } from "../shared/product-switcher.service"; diff --git a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts index a7ff50b4264..fca9063c8cf 100644 --- a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts +++ b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts @@ -1,16 +1,21 @@ import { Component, Directive, importProvidersFrom, Input } from "@angular/core"; import { RouterModule } from "@angular/router"; import { applicationConfig, Meta, moduleMetadata, StoryObj } from "@storybook/angular"; -import { BehaviorSubject, firstValueFrom } from "rxjs"; +import { BehaviorSubject, firstValueFrom, Observable, of } from "rxjs"; import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; +import { AccountService, Account } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SyncService } from "@bitwarden/common/platform/sync"; +import { UserId } from "@bitwarden/common/types/guid"; import { LayoutComponent, NavigationModule } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { I18nMockService } from "@bitwarden/components/src/utils/i18n-mock.service"; import { ProductSwitcherService } from "../shared/product-switcher.service"; @@ -22,11 +27,14 @@ import { NavigationProductSwitcherComponent } from "./navigation-switcher.compon }) class MockOrganizationService implements Partial { private static _orgs = new BehaviorSubject([]); - organizations$ = MockOrganizationService._orgs; // eslint-disable-line rxjs/no-exposed-subjects + + organizations$(): Observable { + return MockOrganizationService._orgs.asObservable(); + } @Input() set mockOrgs(orgs: Organization[]) { - this.organizations$.next(orgs); + MockOrganizationService._orgs.next(orgs); } } @@ -52,6 +60,21 @@ class MockSyncService implements Partial { } } +class MockAccountService implements Partial { + activeAccount$?: Observable = of({ + id: "test-user-id" as UserId, + name: "Test User 1", + email: "test@email.com", + emailVerified: true, + }); +} + +class MockPlatformUtilsService implements Partial { + isSelfHost() { + return false; + } +} + @Component({ selector: "story-layout", template: ``, @@ -86,8 +109,10 @@ export default { imports: [NavigationModule, RouterModule, LayoutComponent], providers: [ { provide: OrganizationService, useClass: MockOrganizationService }, + { provide: AccountService, useClass: MockAccountService }, { provide: ProviderService, useClass: MockProviderService }, { provide: SyncService, useClass: MockSyncService }, + { provide: PlatformUtilsService, useClass: MockPlatformUtilsService }, ProductSwitcherService, { provide: I18nPipe, @@ -157,7 +182,6 @@ export const SMAvailable: Story = { canManageUsers: false, canAccessSecretsManager: true, enabled: true, - canAccessExport: (_) => false, }, ] as Organization[], mockProviders: [], @@ -173,7 +197,6 @@ export const SMAndACAvailable: Story = { canManageUsers: true, canAccessSecretsManager: true, enabled: true, - canAccessExport: (_) => false, }, ] as Organization[], mockProviders: [], @@ -189,7 +212,6 @@ export const WithAllOptions: Story = { canManageUsers: true, canAccessSecretsManager: true, enabled: true, - canAccessExport: (_) => false, }, ] as Organization[], mockProviders: [{ id: "provider-a" }] as Provider[], diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher.component.ts b/apps/web/src/app/layouts/product-switcher/product-switcher.component.ts index 80155166394..834571e2cb4 100644 --- a/apps/web/src/app/layouts/product-switcher/product-switcher.component.ts +++ b/apps/web/src/app/layouts/product-switcher/product-switcher.component.ts @@ -1,5 +1,7 @@ import { AfterViewInit, ChangeDetectorRef, Component, Input } from "@angular/core"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { IconButtonType } from "@bitwarden/components/src/icon-button/icon-button.component"; import { ProductSwitcherService } from "./shared/product-switcher.service"; diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts b/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts index b53d0243f64..ca27aae4581 100644 --- a/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts +++ b/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts @@ -1,16 +1,21 @@ import { Component, Directive, importProvidersFrom, Input } from "@angular/core"; import { RouterModule } from "@angular/router"; import { applicationConfig, Meta, moduleMetadata, StoryObj } from "@storybook/angular"; -import { BehaviorSubject, firstValueFrom } from "rxjs"; +import { BehaviorSubject, firstValueFrom, Observable, of } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; +import { AccountService, Account } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SyncService } from "@bitwarden/common/platform/sync"; +import { UserId } from "@bitwarden/common/types/guid"; import { IconButtonModule, LinkModule, MenuModule } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { I18nMockService } from "@bitwarden/components/src/utils/i18n-mock.service"; import { ProductSwitcherContentComponent } from "./product-switcher-content.component"; @@ -22,11 +27,14 @@ import { ProductSwitcherService } from "./shared/product-switcher.service"; }) class MockOrganizationService implements Partial { private static _orgs = new BehaviorSubject([]); - organizations$ = MockOrganizationService._orgs; // eslint-disable-line rxjs/no-exposed-subjects + + organizations$(): Observable { + return MockOrganizationService._orgs.asObservable(); + } @Input() set mockOrgs(orgs: Organization[]) { - this.organizations$.next(orgs); + MockOrganizationService._orgs.next(orgs); } } @@ -52,6 +60,21 @@ class MockSyncService implements Partial { } } +class MockAccountService implements Partial { + activeAccount$?: Observable = of({ + id: "test-user-id" as UserId, + name: "Test User 1", + email: "test@email.com", + emailVerified: true, + }); +} + +class MockPlatformUtilsService implements Partial { + isSelfHost() { + return false; + } +} + @Component({ selector: "story-layout", template: ``, @@ -78,11 +101,15 @@ export default { ], imports: [JslibModule, MenuModule, IconButtonModule, LinkModule, RouterModule], providers: [ + { provide: AccountService, useClass: MockAccountService }, + MockAccountService, { provide: OrganizationService, useClass: MockOrganizationService }, MockOrganizationService, { provide: ProviderService, useClass: MockProviderService }, MockProviderService, { provide: SyncService, useClass: MockSyncService }, + { provide: PlatformUtilsService, useClass: MockPlatformUtilsService }, + MockPlatformUtilsService, ProductSwitcherService, { provide: I18nService, @@ -134,7 +161,9 @@ export default { ], } as Meta; -type Story = StoryObj; +type Story = StoryObj< + ProductSwitcherComponent & MockProviderService & MockOrganizationService & MockAccountService +>; const Template: Story = { render: (args) => ({ @@ -176,7 +205,6 @@ export const WithSM: Story = { canManageUsers: false, canAccessSecretsManager: true, enabled: true, - canAccessExport: (_) => false, }, ] as Organization[], mockProviders: [], @@ -192,7 +220,6 @@ export const WithSMAndAC: Story = { canManageUsers: true, canAccessSecretsManager: true, enabled: true, - canAccessExport: (_) => false, }, ] as Organization[], mockProviders: [], @@ -208,7 +235,6 @@ export const WithAllOptions: Story = { canManageUsers: true, canAccessSecretsManager: true, enabled: true, - canAccessExport: (_) => false, }, ] as Organization[], mockProviders: [{ id: "provider-a" }] as Provider[], diff --git a/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.spec.ts b/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.spec.ts index a071d0f8852..a1ac434d590 100644 --- a/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.spec.ts +++ b/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.spec.ts @@ -10,7 +10,12 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SyncService } from "@bitwarden/common/platform/sync"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { ProductSwitcherService } from "./product-switcher.service"; @@ -19,8 +24,11 @@ describe("ProductSwitcherService", () => { let router: { url: string; events: Observable }; let organizationService: MockProxy; let providerService: MockProxy; + let accountService: FakeAccountService; + let platformUtilsService: MockProxy; let activeRouteParams = convertToParamMap({ organizationId: "1234" }); const getLastSync = jest.fn().mockResolvedValue(new Date("2024-05-14")); + const userId = Utils.newGuid() as UserId; // The service is dependent on the SyncService, which is behind a `setTimeout` // Most of the tests don't need to test this aspect so `advanceTimersByTime` @@ -36,17 +44,22 @@ describe("ProductSwitcherService", () => { router = mock(); organizationService = mock(); providerService = mock(); + accountService = mockAccountServiceWith(userId); + platformUtilsService = mock(); router.url = "/"; router.events = of({}); - organizationService.organizations$ = of([{}] as Organization[]); + organizationService.organizations$.mockReturnValue(of([{}] as Organization[])); providerService.getAll.mockResolvedValue([] as Provider[]); + platformUtilsService.isSelfHost.mockReturnValue(false); TestBed.configureTestingModule({ providers: [ { provide: Router, useValue: router }, { provide: OrganizationService, useValue: organizationService }, { provide: ProviderService, useValue: providerService }, + { provide: AccountService, useValue: accountService }, + { provide: PlatformUtilsService, useValue: platformUtilsService }, { provide: ActivatedRoute, useValue: { @@ -111,14 +124,15 @@ describe("ProductSwitcherService", () => { }); it("is included in bento when there is an organization with SM", async () => { - organizationService.organizations$ = of([ - { - id: "1234", - canAccessSecretsManager: true, - enabled: true, - canAccessExport: (_) => true, - }, - ] as Organization[]); + organizationService.organizations$.mockReturnValue( + of([ + { + id: "1234", + canAccessSecretsManager: true, + enabled: true, + }, + ] as Organization[]), + ); initiateService(); @@ -129,17 +143,39 @@ describe("ProductSwitcherService", () => { }); describe("Admin/Organizations", () => { - it("includes Organizations in other when there are organizations", async () => { + it("includes Organizations with the internal route in other when there are organizations on cloud", async () => { initiateService(); const products = await firstValueFrom(service.products$); + const organizations = products.other.find((p) => p.name === "Organizations"); + expect(organizations).toBeDefined(); + expect(organizations.marketingRoute.route).toBe("/create-organization"); + expect(organizations.marketingRoute.external).toBe(false); + + expect(products.other.find((p) => p.name === "Organizations")).toBeDefined(); + expect(products.bento.find((p) => p.name === "Admin Console")).toBeUndefined(); + }); + + it("includes Organizations with the external route in other when there are organizations on Self-Host", async () => { + platformUtilsService.isSelfHost.mockReturnValue(true); + initiateService(); + + const products = await firstValueFrom(service.products$); + + const organizations = products.other.find((p) => p.name === "Organizations"); + expect(organizations).toBeDefined(); + expect(organizations.marketingRoute.route).toBe("https://bitwarden.com/products/business/"); + expect(organizations.marketingRoute.external).toBe(true); + expect(products.other.find((p) => p.name === "Organizations")).toBeDefined(); expect(products.bento.find((p) => p.name === "Admin Console")).toBeUndefined(); }); it("includes Admin Console in bento when a user has access to it", async () => { - organizationService.organizations$ = of([{ id: "1234", isOwner: true }] as Organization[]); + organizationService.organizations$.mockReturnValue( + of([{ id: "1234", isOwner: true }] as Organization[]), + ); initiateService(); @@ -195,7 +231,9 @@ describe("ProductSwitcherService", () => { }); it("marks Admin Console as active", async () => { - organizationService.organizations$ = of([{ id: "1234", isOwner: true }] as Organization[]); + organizationService.organizations$.mockReturnValue( + of([{ id: "1234", isOwner: true }] as Organization[]), + ); activeRouteParams = convertToParamMap({ organizationId: "1" }); router.url = "/organizations/"; @@ -226,22 +264,22 @@ describe("ProductSwitcherService", () => { it("updates secrets manager path when the org id is found in the path", async () => { router.url = "/sm/4243"; - organizationService.organizations$ = of([ - { - id: "23443234", - canAccessSecretsManager: true, - enabled: true, - name: "Org 2", - canAccessExport: (_) => true, - }, - { - id: "4243", - canAccessSecretsManager: true, - enabled: true, - name: "Org 32", - canAccessExport: (_) => true, - }, - ] as Organization[]); + organizationService.organizations$.mockReturnValue( + of([ + { + id: "23443234", + canAccessSecretsManager: true, + enabled: true, + name: "Org 2", + }, + { + id: "4243", + canAccessSecretsManager: true, + enabled: true, + name: "Org 32", + }, + ] as Organization[]), + ); initiateService(); @@ -256,10 +294,12 @@ describe("ProductSwitcherService", () => { it("updates admin console path when the org id is found in the path", async () => { router.url = "/organizations/111-22-33"; - organizationService.organizations$ = of([ - { id: "111-22-33", isOwner: true, name: "Test Org" }, - { id: "4243", isOwner: true, name: "My Org" }, - ] as Organization[]); + organizationService.organizations$.mockReturnValue( + of([ + { id: "111-22-33", isOwner: true, name: "Test Org" }, + { id: "4243", isOwner: true, name: "My Org" }, + ] as Organization[]), + ); initiateService(); diff --git a/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.ts b/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.ts index 2c16886a2d4..1cc5c92c120 100644 --- a/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.ts +++ b/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.ts @@ -2,7 +2,16 @@ // @ts-strict-ignore import { Injectable } from "@angular/core"; import { ActivatedRoute, NavigationEnd, NavigationStart, ParamMap, Router } from "@angular/router"; -import { combineLatest, concatMap, filter, map, Observable, ReplaySubject, startWith } from "rxjs"; +import { + combineLatest, + concatMap, + filter, + map, + Observable, + ReplaySubject, + startWith, + switchMap, +} from "rxjs"; import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { @@ -11,6 +20,8 @@ import { } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SyncService } from "@bitwarden/common/platform/sync"; export type ProductSwitcherItem = { @@ -90,18 +101,21 @@ export class ProductSwitcherService { private router: Router, private i18n: I18nPipe, private syncService: SyncService, + private accountService: AccountService, + private platformUtilsService: PlatformUtilsService, ) { this.pollUntilSynced(); } + organizations$ = this.accountService.activeAccount$.pipe( + map((a) => a?.id), + switchMap((id) => this.organizationService.organizations$(id)), + ); + products$: Observable<{ bento: ProductSwitcherItem[]; other: ProductSwitcherItem[]; - }> = combineLatest([ - this.organizationService.organizations$, - this.route.paramMap, - this.triggerProductUpdate$, - ]).pipe( + }> = combineLatest([this.organizations$, this.route.paramMap, this.triggerProductUpdate$]).pipe( map(([orgs, ...rest]): [Organization[], ParamMap, void] => { return [ // Sort orgs by name to match the order within the sidebar @@ -139,6 +153,16 @@ export class ProductSwitcherService { // TODO: This should be migrated to an Observable provided by the provider service and moved to the combineLatest above. See AC-2092. const providers = await this.providerService.getAll(); + const orgsMarketingRoute = this.platformUtilsService.isSelfHost() + ? { + route: "https://bitwarden.com/products/business/", + external: true, + } + : { + route: "/create-organization", + external: false, + }; + const products = { pm: { name: "Password Manager", @@ -185,10 +209,7 @@ export class ProductSwitcherService { orgs: { name: "Organizations", icon: "bwi-business", - marketingRoute: { - route: "https://bitwarden.com/products/business/", - external: true, - }, + marketingRoute: orgsMarketingRoute, otherProductOverrides: { name: "Share your passwords", supportingText: this.i18n.transform("protectYourFamilyOrBusiness"), diff --git a/apps/web/src/app/layouts/user-layout.component.ts b/apps/web/src/app/layouts/user-layout.component.ts index f0ac3ef9b48..e859993af32 100644 --- a/apps/web/src/app/layouts/user-layout.component.ts +++ b/apps/web/src/app/layouts/user-layout.component.ts @@ -31,7 +31,6 @@ import { WebLayoutModule } from "./web-layout.module"; }) export class UserLayoutComponent implements OnInit { protected readonly logo = PasswordManagerLogo; - isFreeFamilyFlagEnabled: boolean; protected hasFamilySponsorshipAvailable$: Observable; protected showSponsoredFamilies$: Observable; protected showSubscription$: Observable; diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index fadcc28f832..c531f358b34 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -1,7 +1,7 @@ import { NgModule } from "@angular/core"; import { Route, RouterModule, Routes } from "@angular/router"; -import { TwoFactorTimeoutComponent } from "@bitwarden/angular/auth/components/two-factor-auth/two-factor-auth-expired.component"; +import { AuthenticationTimeoutComponent } from "@bitwarden/angular/auth/components/authentication-timeout.component"; import { unauthUiRefreshSwap } from "@bitwarden/angular/auth/functions/unauth-ui-refresh-route-swap"; import { authGuard, @@ -9,9 +9,8 @@ import { redirectGuard, tdeDecryptionRequiredGuard, unauthGuardFn, + activeAuthGuard, } from "@bitwarden/angular/auth/guards"; -import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard"; -import { generatorSwap } from "@bitwarden/angular/tools/generator/generator-swap"; import { NewDeviceVerificationNoticeGuard } from "@bitwarden/angular/vault/guards"; import { AnonLayoutWrapperComponent, @@ -37,16 +36,18 @@ import { SsoComponent, VaultIcon, LoginDecryptionOptionsComponent, + TwoFactorAuthComponent, + TwoFactorAuthGuard, + NewDeviceVerificationComponent, + DeviceVerificationIcon, } from "@bitwarden/auth/angular"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { LockComponent } from "@bitwarden/key-management/angular"; +import { LockComponent } from "@bitwarden/key-management-ui"; import { NewDeviceVerificationNoticePageOneComponent, NewDeviceVerificationNoticePageTwoComponent, VaultIcons, } from "@bitwarden/vault"; -import { twofactorRefactorSwap } from "../../../../libs/angular/src/utils/two-factor-component-refactor-route-swap"; import { flagEnabled, Flags } from "../utils/flags"; import { VerifyRecoverDeleteOrgComponent } from "./admin-console/organizations/manage/verify-recover-delete-org.component"; @@ -54,10 +55,6 @@ import { AcceptFamilySponsorshipComponent } from "./admin-console/organizations/ import { FamiliesForEnterpriseSetupComponent } from "./admin-console/organizations/sponsorships/families-for-enterprise-setup.component"; import { CreateOrganizationComponent } from "./admin-console/settings/create-organization.component"; import { deepLinkGuard } from "./auth/guards/deep-link.guard"; -import { HintComponent } from "./auth/hint.component"; -import { LoginDecryptionOptionsComponentV1 } from "./auth/login/login-decryption-options/login-decryption-options-v1.component"; -import { LoginComponentV1 } from "./auth/login/login-v1.component"; -import { LoginViaAuthRequestComponentV1 } from "./auth/login/login-via-auth-request-v1.component"; import { LoginViaWebAuthnComponent } from "./auth/login/login-via-webauthn/login-via-webauthn.component"; import { AcceptOrganizationComponent } from "./auth/organization-invite/accept-organization.component"; import { RecoverDeleteComponent } from "./auth/recover-delete.component"; @@ -68,17 +65,14 @@ import { AccountComponent } from "./auth/settings/account/account.component"; import { EmergencyAccessComponent } from "./auth/settings/emergency-access/emergency-access.component"; import { EmergencyAccessViewComponent } from "./auth/settings/emergency-access/view/emergency-access-view.component"; import { SecurityRoutingModule } from "./auth/settings/security/security-routing.module"; -import { SsoComponentV1 } from "./auth/sso-v1.component"; -import { CompleteTrialInitiationComponent } from "./auth/trial-initiation/complete-trial-initiation/complete-trial-initiation.component"; -import { freeTrialTextResolver } from "./auth/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver"; -import { TrialInitiationComponent } from "./auth/trial-initiation/trial-initiation.component"; -import { TwoFactorAuthComponent } from "./auth/two-factor-auth.component"; -import { TwoFactorComponent } from "./auth/two-factor.component"; +import { TwoFactorComponentV1 } from "./auth/two-factor-v1.component"; import { UpdatePasswordComponent } from "./auth/update-password.component"; import { UpdateTempPasswordComponent } from "./auth/update-temp-password.component"; import { VerifyEmailTokenComponent } from "./auth/verify-email-token.component"; import { VerifyRecoverDeleteComponent } from "./auth/verify-recover-delete.component"; import { SponsoredFamiliesComponent } from "./billing/settings/sponsored-families.component"; +import { CompleteTrialInitiationComponent } from "./billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component"; +import { freeTrialTextResolver } from "./billing/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver"; import { EnvironmentSelectorComponent } from "./components/environment-selector/environment-selector.component"; import { RouteDataProperties } from "./core"; import { FrontendLayoutComponent } from "./layouts/frontend-layout.component"; @@ -88,14 +82,26 @@ import { SMLandingComponent } from "./secrets-manager/secrets-manager-landing/sm import { DomainRulesComponent } from "./settings/domain-rules.component"; import { PreferencesComponent } from "./settings/preferences.component"; import { CredentialGeneratorComponent } from "./tools/credential-generator/credential-generator.component"; -import { GeneratorComponent } from "./tools/generator.component"; import { ReportsModule } from "./tools/reports"; -import { AccessComponent } from "./tools/send/access.component"; -import { SendAccessExplainerComponent } from "./tools/send/send-access-explainer.component"; +import { AccessComponent, SendAccessExplainerComponent } from "./tools/send/send-access"; import { SendComponent } from "./tools/send/send.component"; +import { BrowserExtensionPromptInstallComponent } from "./vault/components/browser-extension-prompt/browser-extension-prompt-install.component"; +import { BrowserExtensionPromptComponent } from "./vault/components/browser-extension-prompt/browser-extension-prompt.component"; import { VaultModule } from "./vault/individual-vault/vault.module"; const routes: Routes = [ + // These need to be placed at the top of the list prior to the root + // so that the redirectGuard does not interrupt the navigation. + { + path: "register", + redirectTo: "signup", + pathMatch: "full", + }, + { + path: "trial", + redirectTo: "signup", + pathMatch: "full", + }, { path: "", component: FrontendLayoutComponent, @@ -112,20 +118,6 @@ const routes: Routes = [ component: LoginViaWebAuthnComponent, data: { titleId: "logInWithPasskey" } satisfies RouteDataProperties, }, - { - path: "register", - component: TrialInitiationComponent, - canActivate: [ - canAccessFeature(FeatureFlag.EmailVerification, false, "/signup", false), - unauthGuardFn(), - ], - data: { titleId: "createAccount" } satisfies RouteDataProperties, - }, - { - path: "trial", - redirectTo: "register", - pathMatch: "full", - }, { path: "set-password", component: SetPasswordComponent, @@ -175,179 +167,13 @@ const routes: Routes = [ }, ], }, - ...unauthUiRefreshSwap( - LoginViaAuthRequestComponentV1, - AnonLayoutWrapperComponent, - { - path: "login-with-device", - data: { titleId: "loginWithDevice" } satisfies RouteDataProperties, - }, - { - path: "login-with-device", - data: { - pageIcon: DevicesIcon, - pageTitle: { - key: "loginInitiated", - }, - pageSubtitle: { - key: "aNotificationWasSentToYourDevice", - }, - titleId: "loginInitiated", - } satisfies RouteDataProperties & AnonLayoutWrapperData, - children: [ - { path: "", component: LoginViaAuthRequestComponent }, - { - path: "", - component: EnvironmentSelectorComponent, - outlet: "environment-selector", - }, - ], - }, - ), - ...unauthUiRefreshSwap( - LoginViaAuthRequestComponentV1, - AnonLayoutWrapperComponent, - { - path: "admin-approval-requested", - data: { titleId: "adminApprovalRequested" } satisfies RouteDataProperties, - }, - { - path: "admin-approval-requested", - data: { - pageIcon: DevicesIcon, - pageTitle: { - key: "adminApprovalRequested", - }, - pageSubtitle: { - key: "adminApprovalRequestSentToAdmins", - }, - titleId: "adminApprovalRequested", - } satisfies RouteDataProperties & AnonLayoutWrapperData, - children: [{ path: "", component: LoginViaAuthRequestComponent }], - }, - ), - ...unauthUiRefreshSwap( - AnonLayoutWrapperComponent, - AnonLayoutWrapperComponent, - { - path: "login", - canActivate: [unauthGuardFn()], - children: [ - { - path: "", - component: LoginComponentV1, - }, - { - path: "", - component: EnvironmentSelectorComponent, - outlet: "environment-selector", - }, - ], - data: { - pageTitle: { - key: "logIn", - }, - }, - }, - { - path: "login", - canActivate: [unauthGuardFn()], - data: { - pageTitle: { - key: "logInToBitwarden", - }, - pageIcon: VaultIcon, - } satisfies RouteDataProperties & AnonLayoutWrapperData, - children: [ - { - path: "", - component: LoginComponent, - }, - { - path: "", - component: LoginSecondaryContentComponent, - outlet: "secondary", - }, - { - path: "", - component: EnvironmentSelectorComponent, - outlet: "environment-selector", - }, - ], - }, - ), - ...unauthUiRefreshSwap( - LoginDecryptionOptionsComponentV1, - AnonLayoutWrapperComponent, - { - path: "login-initiated", - canActivate: [tdeDecryptionRequiredGuard()], - }, - { - path: "login-initiated", - canActivate: [tdeDecryptionRequiredGuard()], - data: { - pageIcon: DevicesIcon, - }, - children: [{ path: "", component: LoginDecryptionOptionsComponent }], - }, - ), - ...unauthUiRefreshSwap( - AnonLayoutWrapperComponent, - AnonLayoutWrapperComponent, - { - path: "hint", - canActivate: [unauthGuardFn()], - data: { - pageTitle: { - key: "passwordHint", - }, - titleId: "passwordHint", - }, - children: [ - { path: "", component: HintComponent }, - { - path: "", - component: EnvironmentSelectorComponent, - outlet: "environment-selector", - }, - ], - }, - { - path: "", - children: [ - { - path: "hint", - canActivate: [unauthGuardFn()], - data: { - pageTitle: { - key: "requestPasswordHint", - }, - pageSubtitle: { - key: "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou", - }, - pageIcon: UserLockIcon, - state: "hint", - }, - children: [ - { path: "", component: PasswordHintComponent }, - { - path: "", - component: EnvironmentSelectorComponent, - outlet: "environment-selector", - }, - ], - }, - ], - }, - ), { path: "", component: AnonLayoutWrapperComponent, children: [ { path: "signup", - canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()], + canActivate: [unauthGuardFn()], data: { pageIcon: RegistrationUserAddIcon, pageTitle: { @@ -372,7 +198,7 @@ const routes: Routes = [ }, { path: "finish-signup", - canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()], + canActivate: [unauthGuardFn()], data: { pageIcon: RegistrationLockAltIcon, titleId: "setAStrongPassword", @@ -384,6 +210,97 @@ const routes: Routes = [ }, ], }, + { + path: "login", + canActivate: [unauthGuardFn()], + data: { + pageTitle: { + key: "logInToBitwarden", + }, + pageIcon: VaultIcon, + } satisfies RouteDataProperties & AnonLayoutWrapperData, + children: [ + { + path: "", + component: LoginComponent, + }, + { + path: "", + component: LoginSecondaryContentComponent, + outlet: "secondary", + }, + { + path: "", + component: EnvironmentSelectorComponent, + outlet: "environment-selector", + }, + ], + }, + { + path: "login-with-device", + data: { + pageIcon: DevicesIcon, + pageTitle: { + key: "logInRequestSent", + }, + pageSubtitle: { + key: "aNotificationWasSentToYourDevice", + }, + titleId: "loginInitiated", + } satisfies RouteDataProperties & AnonLayoutWrapperData, + children: [ + { path: "", component: LoginViaAuthRequestComponent }, + { + path: "", + component: EnvironmentSelectorComponent, + outlet: "environment-selector", + }, + ], + }, + { + path: "admin-approval-requested", + data: { + pageIcon: DevicesIcon, + pageTitle: { + key: "adminApprovalRequested", + }, + pageSubtitle: { + key: "adminApprovalRequestSentToAdmins", + }, + titleId: "adminApprovalRequested", + } satisfies RouteDataProperties & AnonLayoutWrapperData, + children: [{ path: "", component: LoginViaAuthRequestComponent }], + }, + { + path: "hint", + canActivate: [unauthGuardFn()], + data: { + pageTitle: { + key: "requestPasswordHint", + }, + pageSubtitle: { + key: "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou", + }, + pageIcon: UserLockIcon, + state: "hint", + }, + children: [ + { path: "", component: PasswordHintComponent }, + { + path: "", + component: EnvironmentSelectorComponent, + outlet: "environment-selector", + }, + ], + }, + { + path: "login-initiated", + canActivate: [tdeDecryptionRequiredGuard()], + data: { + pageIcon: DevicesIcon, + }, + children: [{ path: "", component: LoginDecryptionOptionsComponent }], + }, { path: "send/:sendId/:key", data: { @@ -406,7 +323,6 @@ const routes: Routes = [ }, { path: "set-password-jit", - canActivate: [canAccessFeature(FeatureFlag.EmailVerification)], component: SetPasswordJitComponent, data: { pageTitle: { @@ -419,7 +335,7 @@ const routes: Routes = [ }, { path: "signup-link-expired", - canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()], + canActivate: [unauthGuardFn()], data: { pageIcon: RegistrationExpiredLinkIcon, pageTitle: { @@ -436,64 +352,24 @@ const routes: Routes = [ }, ], }, - ...unauthUiRefreshSwap( - SsoComponentV1, - SsoComponent, - { - path: "sso", - canActivate: [unauthGuardFn()], - data: { - pageTitle: { - key: "enterpriseSingleSignOn", - }, - titleId: "enterpriseSingleSignOn", - } satisfies RouteDataProperties & AnonLayoutWrapperData, - children: [ - { - path: "", - component: SsoComponentV1, - }, - { - path: "", - component: EnvironmentSelectorComponent, - outlet: "environment-selector", - }, - ], - }, - { - path: "sso", - canActivate: [unauthGuardFn()], - data: { - pageTitle: { - key: "singleSignOn", - }, - titleId: "enterpriseSingleSignOn", - pageSubtitle: { - key: "singleSignOnEnterOrgIdentifierText", - }, - titleAreaMaxWidth: "md", - pageIcon: SsoKeyIcon, - } satisfies RouteDataProperties & AnonLayoutWrapperData, - children: [ - { - path: "", - component: SsoComponent, - }, - { - path: "", - component: EnvironmentSelectorComponent, - outlet: "environment-selector", - }, - ], - }, - ), { - path: "login", + path: "sso", canActivate: [unauthGuardFn()], + data: { + pageTitle: { + key: "singleSignOn", + }, + titleId: "enterpriseSingleSignOn", + pageSubtitle: { + key: "singleSignOnEnterOrgIdentifierText", + }, + titleAreaMaxWidth: "md", + pageIcon: SsoKeyIcon, + } satisfies RouteDataProperties & AnonLayoutWrapperData, children: [ { path: "", - component: LoginComponent, + component: SsoComponent, }, { path: "", @@ -501,12 +377,52 @@ const routes: Routes = [ outlet: "environment-selector", }, ], - data: { - pageTitle: { - key: "logIn", - }, - }, }, + ...unauthUiRefreshSwap( + TwoFactorComponentV1, + TwoFactorAuthComponent, + { + path: "2fa", + canActivate: [unauthGuardFn()], + children: [ + { + path: "", + component: TwoFactorComponentV1, + }, + { + path: "", + component: EnvironmentSelectorComponent, + outlet: "environment-selector", + }, + ], + data: { + pageTitle: { + key: "verifyYourIdentity", + }, + } satisfies RouteDataProperties & AnonLayoutWrapperData, + }, + { + path: "2fa", + canActivate: [unauthGuardFn(), TwoFactorAuthGuard], + children: [ + { + path: "", + component: TwoFactorAuthComponent, + }, + { + path: "", + component: EnvironmentSelectorComponent, + outlet: "environment-selector", + }, + ], + data: { + pageTitle: { + key: "verifyYourIdentity", + }, + titleAreaMaxWidth: "md", + } satisfies RouteDataProperties & AnonLayoutWrapperData, + }, + ), { path: "lock", canActivate: [deepLinkGuard(), lockGuard()], @@ -525,31 +441,12 @@ const routes: Routes = [ } satisfies AnonLayoutWrapperData, }, { - path: "2fa", - canActivate: [unauthGuardFn()], - children: [ - ...twofactorRefactorSwap(TwoFactorComponent, TwoFactorAuthComponent, { - path: "", - }), - { - path: "", - component: EnvironmentSelectorComponent, - outlet: "environment-selector", - }, - ], - data: { - pageTitle: { - key: "verifyIdentity", - }, - } satisfies RouteDataProperties & AnonLayoutWrapperData, - }, - { - path: "2fa-timeout", + path: "authentication-timeout", canActivate: [unauthGuardFn()], children: [ { path: "", - component: TwoFactorTimeoutComponent, + component: AuthenticationTimeoutComponent, }, { path: "", @@ -586,6 +483,25 @@ const routes: Routes = [ titleId: "recoverAccountTwoStep", } satisfies RouteDataProperties & AnonLayoutWrapperData, }, + { + path: "device-verification", + canActivate: [unauthGuardFn(), activeAuthGuard()], + children: [ + { + path: "", + component: NewDeviceVerificationComponent, + }, + ], + data: { + pageIcon: DeviceVerificationIcon, + pageTitle: { + key: "verifyYourIdentity", + }, + pageSubtitle: { + key: "weDontRecognizeThisDevice", + }, + } satisfies RouteDataProperties & AnonLayoutWrapperData, + }, { path: "accept-emergency", canActivate: [deepLinkGuard()], @@ -656,7 +572,7 @@ const routes: Routes = [ }, { path: "trial-initiation", - canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()], + canActivate: [unauthGuardFn()], component: CompleteTrialInitiationComponent, resolve: { pageTitle: freeTrialTextResolver, @@ -667,7 +583,7 @@ const routes: Routes = [ }, { path: "secrets-manager-trial-initiation", - canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()], + canActivate: [unauthGuardFn()], component: CompleteTrialInitiationComponent, resolve: { pageTitle: freeTrialTextResolver, @@ -676,6 +592,23 @@ const routes: Routes = [ maxWidth: "3xl", } satisfies AnonLayoutWrapperData, }, + { + path: "browser-extension-prompt", + data: { + pageIcon: VaultIcons.BrowserExtensionIcon, + } satisfies AnonLayoutWrapperData, + children: [ + { + path: "", + component: BrowserExtensionPromptComponent, + }, + { + path: "", + component: BrowserExtensionPromptInstallComponent, + outlet: "secondary", + }, + ], + }, ], }, { @@ -809,10 +742,11 @@ const routes: Routes = [ titleId: "exportVault", } satisfies RouteDataProperties, }, - ...generatorSwap(GeneratorComponent, CredentialGeneratorComponent, { + { path: "generator", + component: CredentialGeneratorComponent, data: { titleId: "generator" } satisfies RouteDataProperties, - }), + }, ], }, { diff --git a/apps/web/src/app/oss.module.ts b/apps/web/src/app/oss.module.ts index 3f18440d231..39d0a9ae202 100644 --- a/apps/web/src/app/oss.module.ts +++ b/apps/web/src/app/oss.module.ts @@ -2,9 +2,9 @@ import { NgModule } from "@angular/core"; import { AuthModule } from "./auth"; import { LoginModule } from "./auth/login/login.module"; -import { TrialInitiationModule } from "./auth/trial-initiation/trial-initiation.module"; +import { TrialInitiationModule } from "./billing/trial-initiation/trial-initiation.module"; import { LooseComponentsModule, SharedModule } from "./shared"; -import { AccessComponent } from "./tools/send/access.component"; +import { AccessComponent } from "./tools/send/send-access/access.component"; import { OrganizationBadgeModule } from "./vault/individual-vault/organization-badge/organization-badge.module"; import { VaultFilterModule } from "./vault/individual-vault/vault-filter/vault-filter.module"; diff --git a/apps/web/src/app/platform/notifications/permissions-webpush-connection.service.ts b/apps/web/src/app/platform/notifications/permissions-webpush-connection.service.ts new file mode 100644 index 00000000000..44866285251 --- /dev/null +++ b/apps/web/src/app/platform/notifications/permissions-webpush-connection.service.ts @@ -0,0 +1,54 @@ +import { concat, defer, fromEvent, map, Observable, of, switchMap } from "rxjs"; + +import { SupportStatus } from "@bitwarden/common/platform/misc/support-status"; +// eslint-disable-next-line no-restricted-imports -- In platform owned code. +import { + WebPushConnector, + WorkerWebPushConnectionService, +} from "@bitwarden/common/platform/notifications/internal"; +import { UserId } from "@bitwarden/common/types/guid"; + +export class PermissionsWebPushConnectionService extends WorkerWebPushConnectionService { + override supportStatus$(userId: UserId): Observable> { + return this.notificationPermission$().pipe( + switchMap((notificationPermission) => { + if (notificationPermission === "denied") { + return of>({ + type: "not-supported", + reason: "permission-denied", + }); + } + + if (notificationPermission === "default") { + return of>({ + type: "needs-configuration", + reason: "permission-not-requested", + }); + } + + if (notificationPermission === "prompt") { + return of>({ + type: "needs-configuration", + reason: "prompt-must-be-granted", + }); + } + + // Delegate to default worker checks + return super.supportStatus$(userId); + }), + ); + } + + private notificationPermission$() { + return concat( + of(Notification.permission), + defer(async () => { + return await window.navigator.permissions.query({ name: "notifications" }); + }).pipe( + switchMap((permissionStatus) => { + return fromEvent(permissionStatus, "change").pipe(map(() => permissionStatus.state)); + }), + ), + ); + } +} diff --git a/apps/web/src/app/platform/web-sdk-client-factory.ts b/apps/web/src/app/platform/web-sdk-client-factory.ts deleted file mode 100644 index 2ebb2bcc10f..00000000000 --- a/apps/web/src/app/platform/web-sdk-client-factory.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { SdkClientFactory } from "@bitwarden/common/platform/abstractions/sdk/sdk-client-factory"; -import * as sdk from "@bitwarden/sdk-internal"; - -/** - * SDK client factory with a js fallback for when WASM is not supported. - */ -export class WebSdkClientFactory implements SdkClientFactory { - async createSdkClient( - ...args: ConstructorParameters - ): Promise { - const module = await load(); - - (sdk as any).init(module); - - return Promise.resolve(new sdk.BitwardenClient(...args)); - } -} - -// https://stackoverflow.com/a/47880734 -const supported = (() => { - try { - if (typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function") { - const module = new WebAssembly.Module( - Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00), - ); - if (module instanceof WebAssembly.Module) { - return new WebAssembly.Instance(module) instanceof WebAssembly.Instance; - } - } - } catch (e) { - // ignore - } - return false; -})(); - -async function load() { - if (supported) { - return await import("@bitwarden/sdk-internal/bitwarden_wasm_internal_bg.wasm"); - } else { - return await import("@bitwarden/sdk-internal/bitwarden_wasm_internal_bg.wasm.js"); - } -} diff --git a/apps/web/src/app/platform/web-sdk-load.service.ts b/apps/web/src/app/platform/web-sdk-load.service.ts new file mode 100644 index 00000000000..8be3d20b0a7 --- /dev/null +++ b/apps/web/src/app/platform/web-sdk-load.service.ts @@ -0,0 +1,31 @@ +import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service"; +import * as sdk from "@bitwarden/sdk-internal"; + +// https://stackoverflow.com/a/47880734 +const supported = (() => { + try { + if (typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function") { + const module = new WebAssembly.Module( + Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00), + ); + if (module instanceof WebAssembly.Module) { + return new WebAssembly.Instance(module) instanceof WebAssembly.Instance; + } + } + } catch { + // ignore + } + return false; +})(); + +export class WebSdkLoadService extends SdkLoadService { + async load(): Promise { + let module: any; + if (supported) { + module = await import("@bitwarden/sdk-internal/bitwarden_wasm_internal_bg.wasm"); + } else { + module = await import("@bitwarden/sdk-internal/bitwarden_wasm_internal_bg.wasm.js"); + } + (sdk as any).init(module); + } +} diff --git a/apps/web/src/app/secrets-manager/models/requests/request-sm-access.request.ts b/apps/web/src/app/secrets-manager/models/requests/request-sm-access.request.ts index 805745c369d..5edd8bc046e 100644 --- a/apps/web/src/app/secrets-manager/models/requests/request-sm-access.request.ts +++ b/apps/web/src/app/secrets-manager/models/requests/request-sm-access.request.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Guid } from "@bitwarden/common/src/types/guid"; +import { Guid } from "@bitwarden/common/types/guid"; export class RequestSMAccessRequest { OrganizationId: Guid; diff --git a/apps/web/src/app/secrets-manager/secrets-manager-landing/request-sm-access.component.ts b/apps/web/src/app/secrets-manager/secrets-manager-landing/request-sm-access.component.ts index 8909df6cf8a..d1ab7689cfe 100644 --- a/apps/web/src/app/secrets-manager/secrets-manager-landing/request-sm-access.component.ts +++ b/apps/web/src/app/secrets-manager/secrets-manager-landing/request-sm-access.component.ts @@ -3,9 +3,12 @@ import { Component, OnInit } from "@angular/core"; import { FormControl, FormGroup, Validators } from "@angular/forms"; import { Router } from "@angular/router"; +import { firstValueFrom } from "rxjs"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Guid } from "@bitwarden/common/types/guid"; import { NoItemsModule, SearchModule, ToastService } from "@bitwarden/components"; @@ -39,10 +42,12 @@ export class RequestSMAccessComponent implements OnInit { private organizationService: OrganizationService, private smLandingApiService: SmLandingApiService, private toastService: ToastService, + private accountService: AccountService, ) {} async ngOnInit() { - this.organizations = (await this.organizationService.getAll()) + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + this.organizations = (await firstValueFrom(this.organizationService.organizations$(userId))) .filter((e) => e.enabled) .sort((a, b) => a.name.localeCompare(b.name)); diff --git a/apps/web/src/app/secrets-manager/secrets-manager-landing/sm-landing.component.ts b/apps/web/src/app/secrets-manager/secrets-manager-landing/sm-landing.component.ts index 3698031a5b6..4d9dceab34a 100644 --- a/apps/web/src/app/secrets-manager/secrets-manager-landing/sm-landing.component.ts +++ b/apps/web/src/app/secrets-manager/secrets-manager-landing/sm-landing.component.ts @@ -1,9 +1,12 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Component, OnInit } from "@angular/core"; +import { firstValueFrom } from "rxjs"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { NoItemsModule, SearchModule } from "@bitwarden/components"; import { HeaderModule } from "../../layouts/header/header.module"; @@ -22,10 +25,16 @@ export class SMLandingComponent implements OnInit { showSecretsManagerInformation: boolean = true; showGiveMembersAccessInstructions: boolean = false; - constructor(private organizationService: OrganizationService) {} + constructor( + private organizationService: OrganizationService, + private accountService: AccountService, + ) {} async ngOnInit() { - const enabledOrganizations = (await this.organizationService.getAll()).filter((e) => e.enabled); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + const enabledOrganizations = ( + await firstValueFrom(this.organizationService.organizations$(userId)) + ).filter((e) => e.enabled); if (enabledOrganizations.length > 0) { this.handleEnabledOrganizations(enabledOrganizations); diff --git a/apps/web/src/app/settings/domain-rules.component.html b/apps/web/src/app/settings/domain-rules.component.html index a3bea63fb86..7e9e7d6ed64 100644 --- a/apps/web/src/app/settings/domain-rules.component.html +++ b/apps/web/src/app/settings/domain-rules.component.html @@ -43,7 +43,7 @@ -

    {{ "globalEqDomains" | i18n }}

    +

    {{ "globalEqDomains" | i18n }}

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

    -
    - -
    -
    -
    - -
    - - -
    -
    - -
    - -
    - - -
    -
    - -
    -
    - - -
    -
    - - -
    -
    - -
    -
    - - -
    -
    - - -
    -
    -
    - -
    -
    - - -
    -
    - - - - {{ passwordOptionsMinLengthForReader$ | async }} - -
    -
    - - -
    -
    - - -
    -
    - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    -
    -
    - - -
    -
    - -
    -
    -
    - -
    -
    - - - - -
    -
    - - -
    -
    - -
    - - -
    -
    -
    - - -
    -
    - - -
    -
    -
    -
    - - -
    -
    -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    -
    - - -
    -
    -
    -
    - - -
    -
    -
    -
    - - -
    -
    - - -
    -
    -
    -
    -
    - - -
    -
    -
    -
    - - -
    -
    - - -
    -
    -
    - - -
    -
    - - -
    -
    -
    -
    -
    - - -
    -
    - - diff --git a/apps/web/src/app/tools/generator.component.ts b/apps/web/src/app/tools/generator.component.ts deleted file mode 100644 index a11c0c4a97b..00000000000 --- a/apps/web/src/app/tools/generator.component.ts +++ /dev/null @@ -1,73 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, NgZone } from "@angular/core"; -import { ActivatedRoute } from "@angular/router"; - -import { GeneratorComponent as BaseGeneratorComponent } from "@bitwarden/angular/tools/generator/components/generator.component"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { - PasswordGenerationServiceAbstraction, - UsernameGenerationServiceAbstraction, -} from "@bitwarden/generator-legacy"; - -import { PasswordGeneratorHistoryComponent } from "./password-generator-history.component"; - -@Component({ - selector: "app-generator", - templateUrl: "generator.component.html", -}) -export class GeneratorComponent extends BaseGeneratorComponent { - constructor( - passwordGenerationService: PasswordGenerationServiceAbstraction, - usernameGenerationService: UsernameGenerationServiceAbstraction, - accountService: AccountService, - platformUtilsService: PlatformUtilsService, - i18nService: I18nService, - logService: LogService, - route: ActivatedRoute, - ngZone: NgZone, - private dialogService: DialogService, - toastService: ToastService, - ) { - super( - passwordGenerationService, - usernameGenerationService, - platformUtilsService, - accountService, - i18nService, - logService, - route, - ngZone, - window, - toastService, - ); - if (platformUtilsService.isSelfHost()) { - // Allow only valid email forwarders for self host - this.forwardOptions = this.forwardOptions.filter((forwarder) => forwarder.validForSelfHosted); - } - } - - get isSelfHosted(): boolean { - return this.platformUtilsService.isSelfHost(); - } - - async history() { - this.dialogService.open(PasswordGeneratorHistoryComponent); - } - - lengthChanged() { - document.getElementById("length").focus(); - } - - minNumberChanged() { - document.getElementById("min-number").focus(); - } - - minSpecialChanged() { - document.getElementById("min-special").focus(); - } -} diff --git a/apps/web/src/app/tools/import/import-collection-admin.service.ts b/apps/web/src/app/tools/import/import-collection-admin.service.ts index 093bfed7024..64050eb9c06 100644 --- a/apps/web/src/app/tools/import/import-collection-admin.service.ts +++ b/apps/web/src/app/tools/import/import-collection-admin.service.ts @@ -1,8 +1,7 @@ import { Injectable } from "@angular/core"; import { CollectionAdminService, CollectionAdminView } from "@bitwarden/admin-console/common"; - -import { ImportCollectionServiceAbstraction } from "../../../../../../libs/importer/src/services/import-collection.service.abstraction"; +import { ImportCollectionServiceAbstraction } from "@bitwarden/importer-core"; @Injectable() export class ImportCollectionAdminService implements ImportCollectionServiceAbstraction { diff --git a/apps/web/src/app/tools/import/import-web.component.ts b/apps/web/src/app/tools/import/import-web.component.ts index 3f1d5155039..a527b9e71f4 100644 --- a/apps/web/src/app/tools/import/import-web.component.ts +++ b/apps/web/src/app/tools/import/import-web.component.ts @@ -1,7 +1,7 @@ import { Component } from "@angular/core"; import { Router } from "@angular/router"; -import { ImportComponent } from "@bitwarden/importer/ui"; +import { ImportComponent } from "@bitwarden/importer-ui"; import { HeaderModule } from "../../layouts/header/header.module"; import { SharedModule } from "../../shared"; diff --git a/apps/web/src/app/admin-console/organizations/settings/org-import.component.html b/apps/web/src/app/tools/import/org-import.component.html similarity index 100% rename from apps/web/src/app/admin-console/organizations/settings/org-import.component.html rename to apps/web/src/app/tools/import/org-import.component.html diff --git a/apps/web/src/app/admin-console/organizations/settings/org-import.component.ts b/apps/web/src/app/tools/import/org-import.component.ts similarity index 66% rename from apps/web/src/app/admin-console/organizations/settings/org-import.component.ts rename to apps/web/src/app/tools/import/org-import.component.ts index 49ec7465da5..90c13833ffc 100644 --- a/apps/web/src/app/admin-console/organizations/settings/org-import.component.ts +++ b/apps/web/src/app/tools/import/org-import.component.ts @@ -2,18 +2,21 @@ // @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { CollectionAdminService } from "@bitwarden/admin-console/common"; import { canAccessVaultTab, OrganizationService, } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { ImportCollectionServiceAbstraction } from "@bitwarden/importer/core"; -import { ImportComponent } from "@bitwarden/importer/ui"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { ImportCollectionServiceAbstraction } from "@bitwarden/importer-core"; +import { ImportComponent } from "@bitwarden/importer-ui"; -import { LooseComponentsModule, SharedModule } from "../../../shared"; -import { ImportCollectionAdminService } from "../../../tools/import/import-collection-admin.service"; +import { LooseComponentsModule, SharedModule } from "../../shared"; + +import { ImportCollectionAdminService } from "./import-collection-admin.service"; @Component({ templateUrl: "org-import.component.html", @@ -36,6 +39,7 @@ export class OrgImportComponent implements OnInit { private route: ActivatedRoute, private organizationService: OrganizationService, private router: Router, + private accountService: AccountService, ) {} ngOnInit(): void { @@ -46,7 +50,12 @@ export class OrgImportComponent implements OnInit { * Callback that is called after a successful import. */ protected async onSuccessfulImport(organizationId: string): Promise { - const organization = await firstValueFrom(this.organizationService.get$(organizationId)); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + const organization = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(map((organizations) => organizations.find((o) => o.id === organizationId))), + ); if (organization == null) { return; } diff --git a/apps/web/src/app/tools/password-generator-history.component.html b/apps/web/src/app/tools/password-generator-history.component.html deleted file mode 100644 index eabb45ece2d..00000000000 --- a/apps/web/src/app/tools/password-generator-history.component.html +++ /dev/null @@ -1,47 +0,0 @@ - - - {{ "passwordHistory" | i18n }} - - - - - - - - - {{ h.date | date: "medium" }} - - - - - - - - -
    - {{ "noPasswordsInList" | i18n }} -
    -
    - - - - -
    diff --git a/apps/web/src/app/tools/password-generator-history.component.ts b/apps/web/src/app/tools/password-generator-history.component.ts deleted file mode 100644 index 0c7c9c4e221..00000000000 --- a/apps/web/src/app/tools/password-generator-history.component.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Component } from "@angular/core"; - -import { PasswordGeneratorHistoryComponent as BasePasswordGeneratorHistoryComponent } from "@bitwarden/angular/tools/generator/components/password-generator-history.component"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { ToastService } from "@bitwarden/components"; -import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; - -@Component({ - selector: "app-password-generator-history", - templateUrl: "password-generator-history.component.html", -}) -export class PasswordGeneratorHistoryComponent extends BasePasswordGeneratorHistoryComponent { - constructor( - passwordGenerationService: PasswordGenerationServiceAbstraction, - platformUtilsService: PlatformUtilsService, - i18nService: I18nService, - toastService: ToastService, - ) { - super(passwordGenerationService, platformUtilsService, i18nService, window, toastService); - } -} diff --git a/apps/web/src/app/tools/reports/pages/breach-report.component.spec.ts b/apps/web/src/app/tools/reports/pages/breach-report.component.spec.ts index 1574389a1a0..9af15749a91 100644 --- a/apps/web/src/app/tools/reports/pages/breach-report.component.spec.ts +++ b/apps/web/src/app/tools/reports/pages/breach-report.component.spec.ts @@ -1,6 +1,5 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -// eslint-disable-next-line no-restricted-imports import { ComponentFixture, TestBed } from "@angular/core/testing"; import { ReactiveFormsModule } from "@angular/forms"; import { mock, MockProxy } from "jest-mock-extended"; @@ -66,6 +65,9 @@ describe("BreachReportComponent", () => { useValue: mock(), }, ], + // FIXME(PM-18598): Replace unknownElements and unknownProperties with actual imports + errorOnUnknownElements: false, + errorOnUnknownProperties: false, }).compileComponents(); }); diff --git a/apps/web/src/app/tools/reports/pages/cipher-report.component.ts b/apps/web/src/app/tools/reports/pages/cipher-report.component.ts index b1a46bd13a8..8f6abec46bb 100644 --- a/apps/web/src/app/tools/reports/pages/cipher-report.component.ts +++ b/apps/web/src/app/tools/reports/pages/cipher-report.component.ts @@ -1,22 +1,40 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { DialogRef } from "@angular/cdk/dialog"; import { Directive, ViewChild, ViewContainerRef, OnDestroy } from "@angular/core"; -import { BehaviorSubject, Observable, Subject, takeUntil } from "rxjs"; +import { + BehaviorSubject, + lastValueFrom, + Observable, + Subject, + firstValueFrom, + switchMap, + takeUntil, +} from "rxjs"; -import { ModalService } from "@bitwarden/angular/services/modal.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { OrganizationId } from "@bitwarden/common/types/guid"; +import { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { TableDataSource } from "@bitwarden/components"; -import { PasswordRepromptService } from "@bitwarden/vault"; +import { TableDataSource, DialogService } from "@bitwarden/components"; +import { + CipherFormConfig, + CipherFormConfigService, + PasswordRepromptService, +} from "@bitwarden/vault"; -import { AddEditComponent } from "../../../vault/individual-vault/add-edit.component"; -import { AddEditComponent as OrgAddEditComponent } from "../../../vault/org-vault/add-edit.component"; +import { + VaultItemDialogComponent, + VaultItemDialogMode, + VaultItemDialogResult, +} from "../../../vault/components/vault-item-dialog/vault-item-dialog.component"; +import { AdminConsoleCipherFormConfigService } from "../../../vault/org-vault/services/admin-console-cipher-form-config.service"; @Directive() export class CipherReportComponent implements OnDestroy { @@ -39,16 +57,24 @@ export class CipherReportComponent implements OnDestroy { currentFilterStatus: number | string; protected filterOrgStatus$ = new BehaviorSubject(0); private destroyed$: Subject = new Subject(); + private vaultItemDialogRef?: DialogRef | undefined; constructor( protected cipherService: CipherService, - private modalService: ModalService, + private dialogService: DialogService, protected passwordRepromptService: PasswordRepromptService, protected organizationService: OrganizationService, + protected accountService: AccountService, protected i18nService: I18nService, private syncService: SyncService, + private cipherFormConfigService: CipherFormConfigService, + private adminConsoleCipherFormConfigService: AdminConsoleCipherFormConfigService, ) { - this.organizations$ = this.organizationService.organizations$; + this.organizations$ = this.accountService.activeAccount$.pipe( + getUserId, + switchMap((userId) => this.organizationService.organizations$(userId)), + ); + this.organizations$.pipe(takeUntil(this.destroyed$)).subscribe((orgs) => { this.organizations = orgs; }); @@ -127,43 +153,63 @@ export class CipherReportComponent implements OnDestroy { this.loading = false; this.hasLoaded = true; } - async selectCipher(cipher: CipherView) { if (!(await this.repromptCipher(cipher))) { return; } - const type = this.organization != null ? OrgAddEditComponent : AddEditComponent; + if (this.organization) { + const adminCipherFormConfig = await this.adminConsoleCipherFormConfigService.buildConfig( + "edit", + cipher.id as CipherId, + cipher.type, + ); - const [modal, childComponent] = await this.modalService.openViewRef( - type, - this.cipherAddEditModalRef, - (comp: OrgAddEditComponent | AddEditComponent) => { - if (this.organization != null) { - (comp as OrgAddEditComponent).organization = this.organization; - comp.organizationId = this.organization.id; - } + await this.openVaultItemDialog("view", adminCipherFormConfig, cipher); + } else { + const cipherFormConfig = await this.cipherFormConfigService.buildConfig( + "edit", + cipher.id as CipherId, + cipher.type, + ); + await this.openVaultItemDialog("view", cipherFormConfig, cipher); + } + } - comp.cipherId = cipher == null ? null : cipher.id; - // eslint-disable-next-line rxjs/no-async-subscribe - comp.onSavedCipher.subscribe(async () => { - modal.close(); - await this.load(); - }); - // eslint-disable-next-line rxjs/no-async-subscribe - comp.onDeletedCipher.subscribe(async () => { - modal.close(); - await this.load(); - }); - // eslint-disable-next-line rxjs/no-async-subscribe - comp.onRestoredCipher.subscribe(async () => { - modal.close(); - await this.load(); - }); - }, - ); + /** + * Open the combined view / edit dialog for a cipher. + * @param mode - Starting mode of the dialog. + * @param formConfig - Configuration for the form when editing/adding a cipher. + * @param activeCollectionId - The active collection ID. + */ + async openVaultItemDialog( + mode: VaultItemDialogMode, + formConfig: CipherFormConfig, + cipher: CipherView, + activeCollectionId?: CollectionId, + ) { + const disableForm = cipher ? !cipher.edit && !this.organization.canEditAllCiphers : false; - return childComponent; + this.vaultItemDialogRef = VaultItemDialogComponent.open(this.dialogService, { + mode, + formConfig, + activeCollectionId, + disableForm, + }); + + const result = await lastValueFrom(this.vaultItemDialogRef.closed); + this.vaultItemDialogRef = undefined; + + // When the dialog is closed for a premium upgrade, return early as the user + // should be navigated to the subscription settings elsewhere + if (result === VaultItemDialogResult.PremiumUpgrade) { + return; + } + + // If the dialog was closed by deleting the cipher, refresh the report. + if (result === VaultItemDialogResult.Deleted || result === VaultItemDialogResult.Saved) { + await this.load(); + } } protected async setCiphers() { @@ -178,7 +224,8 @@ export class CipherReportComponent implements OnDestroy { } protected async getAllCiphers(): Promise { - return await this.cipherService.getAllDecrypted(); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + return await this.cipherService.getAllDecrypted(activeUserId); } protected filterCiphersByOrg(ciphersList: CipherView[]) { diff --git a/apps/web/src/app/tools/reports/pages/exposed-passwords-report.component.html b/apps/web/src/app/tools/reports/pages/exposed-passwords-report.component.html index b3f2ae581b3..eb8e2c56527 100644 --- a/apps/web/src/app/tools/reports/pages/exposed-passwords-report.component.html +++ b/apps/web/src/app/tools/reports/pages/exposed-passwords-report.component.html @@ -26,77 +26,74 @@ - + - - - {{ "name" | i18n }} - - {{ "owner" | i18n }} - - - {{ "timesExposed" | i18n }} - - + + {{ "name" | i18n }} + + {{ "owner" | i18n }} + + + {{ "timesExposed" | i18n }} + - - - - - - - - {{ r.name }} - - - {{ r.name }} - - - - {{ "shared" | i18n }} - - - - {{ "attachments" | i18n }} - -
    - {{ r.subTitle }} - - - + + + + + + - - - - - {{ "exposedXTimes" | i18n: (r.exposedXTimes | number) }} - - - + {{ row.name }} + + + + {{ row.name }} + + + + {{ "shared" | i18n }} + + + + {{ "attachments" | i18n }} + +
    + {{ row.subTitle }} + + + + + + + + {{ "exposedXTimes" | i18n: (row.exposedXTimes | number) }} + +
    -
    +
    diff --git a/apps/web/src/app/tools/reports/pages/exposed-passwords-report.component.spec.ts b/apps/web/src/app/tools/reports/pages/exposed-passwords-report.component.spec.ts index 07dc218bd64..052e3bc7cfe 100644 --- a/apps/web/src/app/tools/reports/pages/exposed-passwords-report.component.spec.ts +++ b/apps/web/src/app/tools/reports/pages/exposed-passwords-report.component.spec.ts @@ -1,16 +1,21 @@ -// eslint-disable-next-line no-restricted-imports import { ComponentFixture, TestBed } from "@angular/core/testing"; import { mock, MockProxy } from "jest-mock-extended"; import { of } from "rxjs"; import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; -import { ModalService } from "@bitwarden/angular/services/modal.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { PasswordRepromptService } from "@bitwarden/vault"; +import { DialogService } from "@bitwarden/components"; +import { CipherFormConfigService, PasswordRepromptService } from "@bitwarden/vault"; + +import { AdminConsoleCipherFormConfigService } from "../../../vault/org-vault/services/admin-console-cipher-form-config.service"; import { ExposedPasswordsReportComponent } from "./exposed-passwords-report.component"; import { cipherData } from "./reports-ciphers.mock"; @@ -21,12 +26,16 @@ describe("ExposedPasswordsReportComponent", () => { let auditService: MockProxy; let organizationService: MockProxy; let syncServiceMock: MockProxy; + let adminConsoleCipherFormConfigServiceMock: MockProxy; + const userId = Utils.newGuid() as UserId; + const accountService: FakeAccountService = mockAccountServiceWith(userId); beforeEach(() => { + let cipherFormConfigServiceMock: MockProxy; syncServiceMock = mock(); auditService = mock(); organizationService = mock(); - organizationService.organizations$ = of([]); + organizationService.organizations$.mockReturnValue(of([])); // 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 TestBed.configureTestingModule({ @@ -45,8 +54,12 @@ describe("ExposedPasswordsReportComponent", () => { useValue: organizationService, }, { - provide: ModalService, - useValue: mock(), + provide: AccountService, + useValue: accountService, + }, + { + provide: DialogService, + useValue: mock(), }, { provide: PasswordRepromptService, @@ -60,8 +73,19 @@ describe("ExposedPasswordsReportComponent", () => { provide: I18nService, useValue: mock(), }, + { + provide: CipherFormConfigService, + useValue: cipherFormConfigServiceMock, + }, + { + provide: AdminConsoleCipherFormConfigService, + useValue: adminConsoleCipherFormConfigServiceMock, + }, ], schemas: [], + // FIXME(PM-18598): Replace unknownElements and unknownProperties with actual imports + errorOnUnknownElements: false, + errorOnUnknownProperties: false, }).compileComponents(); }); diff --git a/apps/web/src/app/tools/reports/pages/exposed-passwords-report.component.ts b/apps/web/src/app/tools/reports/pages/exposed-passwords-report.component.ts index 13d2804c5e5..900ff3703f0 100644 --- a/apps/web/src/app/tools/reports/pages/exposed-passwords-report.component.ts +++ b/apps/web/src/app/tools/reports/pages/exposed-passwords-report.component.ts @@ -1,14 +1,17 @@ import { Component, OnInit } from "@angular/core"; -import { ModalService } from "@bitwarden/angular/services/modal.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { PasswordRepromptService } from "@bitwarden/vault"; +import { DialogService } from "@bitwarden/components"; +import { CipherFormConfigService, PasswordRepromptService } from "@bitwarden/vault"; + +import { AdminConsoleCipherFormConfigService } from "../../../vault/org-vault/services/admin-console-cipher-form-config.service"; import { CipherReportComponent } from "./cipher-report.component"; @@ -25,18 +28,24 @@ export class ExposedPasswordsReportComponent extends CipherReportComponent imple protected cipherService: CipherService, protected auditService: AuditService, protected organizationService: OrganizationService, - modalService: ModalService, + dialogService: DialogService, + accountService: AccountService, passwordRepromptService: PasswordRepromptService, i18nService: I18nService, syncService: SyncService, + cipherFormConfigService: CipherFormConfigService, + adminConsoleCipherFormConfigService: AdminConsoleCipherFormConfigService, ) { super( cipherService, - modalService, + dialogService, passwordRepromptService, organizationService, + accountService, i18nService, syncService, + cipherFormConfigService, + adminConsoleCipherFormConfigService, ); } diff --git a/apps/web/src/app/tools/reports/pages/inactive-two-factor-report.component.html b/apps/web/src/app/tools/reports/pages/inactive-two-factor-report.component.html index 52bbb1c5e6d..e667a65235b 100644 --- a/apps/web/src/app/tools/reports/pages/inactive-two-factor-report.component.html +++ b/apps/web/src/app/tools/reports/pages/inactive-two-factor-report.component.html @@ -47,14 +47,19 @@ - {{ r.name }} + + {{ r.name }} + + + {{ r.name }} + { let fixture: ComponentFixture; let organizationService: MockProxy; let syncServiceMock: MockProxy; + let adminConsoleCipherFormConfigServiceMock: MockProxy; + const userId = Utils.newGuid() as UserId; + const accountService: FakeAccountService = mockAccountServiceWith(userId); beforeEach(() => { + let cipherFormConfigServiceMock: MockProxy; organizationService = mock(); - organizationService.organizations$ = of([]); + organizationService.organizations$.mockReturnValue(of([])); syncServiceMock = mock(); // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises @@ -39,8 +48,12 @@ describe("InactiveTwoFactorReportComponent", () => { useValue: organizationService, }, { - provide: ModalService, - useValue: mock(), + provide: AccountService, + useValue: accountService, + }, + { + provide: DialogService, + useValue: mock(), }, { provide: LogService, @@ -58,8 +71,18 @@ describe("InactiveTwoFactorReportComponent", () => { provide: I18nService, useValue: mock(), }, + { + provide: CipherFormConfigService, + useValue: cipherFormConfigServiceMock, + }, + { + provide: AdminConsoleCipherFormConfigService, + useValue: adminConsoleCipherFormConfigServiceMock, + }, ], schemas: [], + // FIXME(PM-18598): Replace unknownElements and unknownProperties with actual imports + errorOnUnknownElements: false, }).compileComponents(); }); diff --git a/apps/web/src/app/tools/reports/pages/inactive-two-factor-report.component.ts b/apps/web/src/app/tools/reports/pages/inactive-two-factor-report.component.ts index 792ad0616f2..5265326128e 100644 --- a/apps/web/src/app/tools/reports/pages/inactive-two-factor-report.component.ts +++ b/apps/web/src/app/tools/reports/pages/inactive-two-factor-report.component.ts @@ -2,8 +2,8 @@ // @ts-strict-ignore import { Component, OnInit } from "@angular/core"; -import { ModalService } from "@bitwarden/angular/services/modal.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -11,7 +11,10 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { PasswordRepromptService } from "@bitwarden/vault"; +import { DialogService } from "@bitwarden/components"; +import { CipherFormConfigService, PasswordRepromptService } from "@bitwarden/vault"; + +import { AdminConsoleCipherFormConfigService } from "../../../vault/org-vault/services/admin-console-cipher-form-config.service"; import { CipherReportComponent } from "./cipher-report.component"; @@ -27,19 +30,25 @@ export class InactiveTwoFactorReportComponent extends CipherReportComponent impl constructor( protected cipherService: CipherService, protected organizationService: OrganizationService, - modalService: ModalService, + dialogService: DialogService, + accountService: AccountService, private logService: LogService, passwordRepromptService: PasswordRepromptService, i18nService: I18nService, syncService: SyncService, + cipherFormConfigService: CipherFormConfigService, + adminConsoleCipherFormConfigService: AdminConsoleCipherFormConfigService, ) { super( cipherService, - modalService, + dialogService, passwordRepromptService, organizationService, + accountService, i18nService, syncService, + cipherFormConfigService, + adminConsoleCipherFormConfigService, ); } @@ -121,4 +130,15 @@ export class InactiveTwoFactorReportComponent extends CipherReportComponent impl this.services.set(serviceData.domain, serviceData.documentation); } } + + /** + * Provides a way to determine if someone with permissions to run an organizational report is also able to view/edit ciphers within the results + * Default to true for indivduals running reports on their own vault. + * @param c CipherView + * @returns boolean + */ + protected canManageCipher(c: CipherView): boolean { + // this will only ever be false from the org view; + return true; + } } diff --git a/apps/web/src/app/tools/reports/pages/organizations/exposed-passwords-report.component.ts b/apps/web/src/app/tools/reports/pages/organizations/exposed-passwords-report.component.ts new file mode 100644 index 00000000000..4f0988082b4 --- /dev/null +++ b/apps/web/src/app/tools/reports/pages/organizations/exposed-passwords-report.component.ts @@ -0,0 +1,97 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { Component, OnInit } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { firstValueFrom } from "rxjs"; + +import { AuditService } from "@bitwarden/common/abstractions/audit.service"; +import { + getOrganizationById, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; +import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { DialogService } from "@bitwarden/components"; +import { PasswordRepromptService, CipherFormConfigService } from "@bitwarden/vault"; + +import { RoutedVaultFilterBridgeService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter-bridge.service"; +import { RoutedVaultFilterService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter.service"; +import { AdminConsoleCipherFormConfigService } from "../../../../vault/org-vault/services/admin-console-cipher-form-config.service"; +import { ExposedPasswordsReportComponent as BaseExposedPasswordsReportComponent } from "../exposed-passwords-report.component"; + +@Component({ + selector: "app-org-exposed-passwords-report", + templateUrl: "../exposed-passwords-report.component.html", + providers: [ + { + provide: CipherFormConfigService, + useClass: AdminConsoleCipherFormConfigService, + }, + AdminConsoleCipherFormConfigService, + RoutedVaultFilterService, + RoutedVaultFilterBridgeService, + ], +}) +export class ExposedPasswordsReportComponent + extends BaseExposedPasswordsReportComponent + implements OnInit +{ + manageableCiphers: Cipher[]; + + constructor( + cipherService: CipherService, + auditService: AuditService, + dialogService: DialogService, + organizationService: OrganizationService, + protected accountService: AccountService, + private route: ActivatedRoute, + passwordRepromptService: PasswordRepromptService, + i18nService: I18nService, + syncService: SyncService, + cipherFormService: CipherFormConfigService, + adminConsoleCipherFormConfigService: AdminConsoleCipherFormConfigService, + ) { + super( + cipherService, + auditService, + organizationService, + dialogService, + accountService, + passwordRepromptService, + i18nService, + syncService, + cipherFormService, + adminConsoleCipherFormConfigService, + ); + } + + async ngOnInit() { + this.isAdminConsoleActive = true; + // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe + this.route.parent.parent.params.subscribe(async (params) => { + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + this.organization = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(params.organizationId)), + ); + this.manageableCiphers = await this.cipherService.getAll(userId); + }); + } + + getAllCiphers(): Promise { + return this.cipherService.getAllFromApiForOrganization(this.organization.id); + } + + canManageCipher(c: CipherView): boolean { + if (c.collectionIds.length === 0) { + return true; + } + return this.manageableCiphers.some((x) => x.id === c.id); + } +} diff --git a/apps/web/src/app/tools/reports/pages/organizations/inactive-two-factor-report.component.ts b/apps/web/src/app/tools/reports/pages/organizations/inactive-two-factor-report.component.ts new file mode 100644 index 00000000000..6dc202de0b3 --- /dev/null +++ b/apps/web/src/app/tools/reports/pages/organizations/inactive-two-factor-report.component.ts @@ -0,0 +1,100 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { Component, OnInit } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { firstValueFrom, map } from "rxjs"; + +import { + getOrganizationById, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; +import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { DialogService } from "@bitwarden/components"; +import { CipherFormConfigService, PasswordRepromptService } from "@bitwarden/vault"; + +import { RoutedVaultFilterBridgeService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter-bridge.service"; +import { RoutedVaultFilterService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter.service"; +import { AdminConsoleCipherFormConfigService } from "../../../../vault/org-vault/services/admin-console-cipher-form-config.service"; +import { InactiveTwoFactorReportComponent as BaseInactiveTwoFactorReportComponent } from "../inactive-two-factor-report.component"; + +@Component({ + selector: "app-inactive-two-factor-report", + templateUrl: "../inactive-two-factor-report.component.html", + providers: [ + { + provide: CipherFormConfigService, + useClass: AdminConsoleCipherFormConfigService, + }, + AdminConsoleCipherFormConfigService, + RoutedVaultFilterService, + RoutedVaultFilterBridgeService, + ], +}) +export class InactiveTwoFactorReportComponent + extends BaseInactiveTwoFactorReportComponent + implements OnInit +{ + // Contains a list of ciphers, the user running the report, can manage + private manageableCiphers: Cipher[]; + + constructor( + cipherService: CipherService, + dialogService: DialogService, + private route: ActivatedRoute, + logService: LogService, + passwordRepromptService: PasswordRepromptService, + organizationService: OrganizationService, + accountService: AccountService, + i18nService: I18nService, + syncService: SyncService, + cipherFormConfigService: CipherFormConfigService, + adminConsoleCipherFormConfigService: AdminConsoleCipherFormConfigService, + ) { + super( + cipherService, + organizationService, + dialogService, + accountService, + logService, + passwordRepromptService, + i18nService, + syncService, + cipherFormConfigService, + adminConsoleCipherFormConfigService, + ); + } + + async ngOnInit() { + this.isAdminConsoleActive = true; + // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe + this.route.parent.parent.params.subscribe(async (params) => { + const userId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + this.organization = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(params.organizationId)), + ); + this.manageableCiphers = await this.cipherService.getAll(userId); + await super.ngOnInit(); + }); + } + + getAllCiphers(): Promise { + return this.cipherService.getAllFromApiForOrganization(this.organization.id); + } + + protected canManageCipher(c: CipherView): boolean { + if (c.collectionIds.length === 0) { + return true; + } + return this.manageableCiphers.some((x) => x.id === c.id); + } +} diff --git a/apps/web/src/app/tools/reports/pages/organizations/reused-passwords-report.component.ts b/apps/web/src/app/tools/reports/pages/organizations/reused-passwords-report.component.ts new file mode 100644 index 00000000000..4e37f53ba61 --- /dev/null +++ b/apps/web/src/app/tools/reports/pages/organizations/reused-passwords-report.component.ts @@ -0,0 +1,95 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { Component, OnInit } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { firstValueFrom } from "rxjs"; + +import { + getOrganizationById, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; +import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { DialogService } from "@bitwarden/components"; +import { CipherFormConfigService, PasswordRepromptService } from "@bitwarden/vault"; + +import { RoutedVaultFilterBridgeService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter-bridge.service"; +import { RoutedVaultFilterService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter.service"; +import { AdminConsoleCipherFormConfigService } from "../../../../vault/org-vault/services/admin-console-cipher-form-config.service"; +import { ReusedPasswordsReportComponent as BaseReusedPasswordsReportComponent } from "../reused-passwords-report.component"; + +@Component({ + selector: "app-reused-passwords-report", + templateUrl: "../reused-passwords-report.component.html", + providers: [ + { + provide: CipherFormConfigService, + useClass: AdminConsoleCipherFormConfigService, + }, + AdminConsoleCipherFormConfigService, + RoutedVaultFilterService, + RoutedVaultFilterBridgeService, + ], +}) +export class ReusedPasswordsReportComponent + extends BaseReusedPasswordsReportComponent + implements OnInit +{ + manageableCiphers: Cipher[]; + + constructor( + cipherService: CipherService, + dialogService: DialogService, + private route: ActivatedRoute, + organizationService: OrganizationService, + protected accountService: AccountService, + passwordRepromptService: PasswordRepromptService, + i18nService: I18nService, + syncService: SyncService, + cipherFormConfigService: CipherFormConfigService, + adminConsoleCipherFormConfigService: AdminConsoleCipherFormConfigService, + ) { + super( + cipherService, + organizationService, + dialogService, + accountService, + passwordRepromptService, + i18nService, + syncService, + cipherFormConfigService, + adminConsoleCipherFormConfigService, + ); + } + + async ngOnInit() { + this.isAdminConsoleActive = true; + // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe + this.route.parent.parent.params.subscribe(async (params) => { + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + this.organization = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(params.organizationId)), + ); + this.manageableCiphers = await this.cipherService.getAll(userId); + await super.ngOnInit(); + }); + } + + getAllCiphers(): Promise { + return this.cipherService.getAllFromApiForOrganization(this.organization.id); + } + + canManageCipher(c: CipherView): boolean { + if (c.collectionIds.length === 0) { + return true; + } + return this.manageableCiphers.some((x) => x.id === c.id); + } +} diff --git a/apps/web/src/app/tools/reports/pages/organizations/unsecured-websites-report.component.ts b/apps/web/src/app/tools/reports/pages/organizations/unsecured-websites-report.component.ts new file mode 100644 index 00000000000..25e1314fceb --- /dev/null +++ b/apps/web/src/app/tools/reports/pages/organizations/unsecured-websites-report.component.ts @@ -0,0 +1,100 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { Component, OnInit } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { firstValueFrom, map } from "rxjs"; + +import { CollectionService } from "@bitwarden/admin-console/common"; +import { + getOrganizationById, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; +import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { DialogService } from "@bitwarden/components"; +import { CipherFormConfigService, PasswordRepromptService } from "@bitwarden/vault"; + +import { RoutedVaultFilterBridgeService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter-bridge.service"; +import { RoutedVaultFilterService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter.service"; +import { AdminConsoleCipherFormConfigService } from "../../../../vault/org-vault/services/admin-console-cipher-form-config.service"; +import { UnsecuredWebsitesReportComponent as BaseUnsecuredWebsitesReportComponent } from "../unsecured-websites-report.component"; + +@Component({ + selector: "app-unsecured-websites-report", + templateUrl: "../unsecured-websites-report.component.html", + providers: [ + { + provide: CipherFormConfigService, + useClass: AdminConsoleCipherFormConfigService, + }, + AdminConsoleCipherFormConfigService, + RoutedVaultFilterService, + RoutedVaultFilterBridgeService, + ], +}) +export class UnsecuredWebsitesReportComponent + extends BaseUnsecuredWebsitesReportComponent + implements OnInit +{ + // Contains a list of ciphers, the user running the report, can manage + private manageableCiphers: Cipher[]; + + constructor( + cipherService: CipherService, + dialogService: DialogService, + private route: ActivatedRoute, + organizationService: OrganizationService, + protected accountService: AccountService, + passwordRepromptService: PasswordRepromptService, + i18nService: I18nService, + syncService: SyncService, + collectionService: CollectionService, + cipherFormConfigService: CipherFormConfigService, + adminConsoleCipherFormConfigService: AdminConsoleCipherFormConfigService, + ) { + super( + cipherService, + organizationService, + dialogService, + accountService, + passwordRepromptService, + i18nService, + syncService, + collectionService, + cipherFormConfigService, + adminConsoleCipherFormConfigService, + ); + } + + async ngOnInit() { + this.isAdminConsoleActive = true; + // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe + this.route.parent.parent.params.subscribe(async (params) => { + const userId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + this.organization = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(params.organizationId)), + ); + this.manageableCiphers = await this.cipherService.getAll(userId); + await super.ngOnInit(); + }); + } + + getAllCiphers(): Promise { + return this.cipherService.getAllFromApiForOrganization(this.organization.id); + } + + protected canManageCipher(c: CipherView): boolean { + if (c.collectionIds.length === 0) { + return true; + } + return this.manageableCiphers.some((x) => x.id === c.id); + } +} diff --git a/apps/web/src/app/tools/reports/pages/organizations/weak-passwords-report.component.ts b/apps/web/src/app/tools/reports/pages/organizations/weak-passwords-report.component.ts new file mode 100644 index 00000000000..ef9bd97008e --- /dev/null +++ b/apps/web/src/app/tools/reports/pages/organizations/weak-passwords-report.component.ts @@ -0,0 +1,99 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { Component, OnInit } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { firstValueFrom } from "rxjs"; + +import { + getOrganizationById, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; +import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { DialogService } from "@bitwarden/components"; +import { CipherFormConfigService, PasswordRepromptService } from "@bitwarden/vault"; + +import { RoutedVaultFilterBridgeService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter-bridge.service"; +import { RoutedVaultFilterService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter.service"; +import { AdminConsoleCipherFormConfigService } from "../../../../vault/org-vault/services/admin-console-cipher-form-config.service"; +import { WeakPasswordsReportComponent as BaseWeakPasswordsReportComponent } from "../weak-passwords-report.component"; + +@Component({ + selector: "app-weak-passwords-report", + templateUrl: "../weak-passwords-report.component.html", + providers: [ + { + provide: CipherFormConfigService, + useClass: AdminConsoleCipherFormConfigService, + }, + AdminConsoleCipherFormConfigService, + RoutedVaultFilterService, + RoutedVaultFilterBridgeService, + ], +}) +export class WeakPasswordsReportComponent + extends BaseWeakPasswordsReportComponent + implements OnInit +{ + manageableCiphers: Cipher[]; + + constructor( + cipherService: CipherService, + passwordStrengthService: PasswordStrengthServiceAbstraction, + dialogService: DialogService, + private route: ActivatedRoute, + organizationService: OrganizationService, + passwordRepromptService: PasswordRepromptService, + i18nService: I18nService, + syncService: SyncService, + cipherFormConfigService: CipherFormConfigService, + protected accountService: AccountService, + adminConsoleCipherFormConfigService: AdminConsoleCipherFormConfigService, + ) { + super( + cipherService, + passwordStrengthService, + organizationService, + dialogService, + accountService, + passwordRepromptService, + i18nService, + syncService, + cipherFormConfigService, + adminConsoleCipherFormConfigService, + ); + } + + async ngOnInit() { + this.isAdminConsoleActive = true; + // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe + this.route.parent.parent.params.subscribe(async (params) => { + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + + this.organization = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(params.organizationId)), + ); + this.manageableCiphers = await this.cipherService.getAll(userId); + await super.ngOnInit(); + }); + } + + getAllCiphers(): Promise { + return this.cipherService.getAllFromApiForOrganization(this.organization.id); + } + + canManageCipher(c: CipherView): boolean { + if (c.collectionIds.length === 0) { + return true; + } + return this.manageableCiphers.some((x) => x.id === c.id); + } +} diff --git a/apps/web/src/app/tools/reports/pages/reused-passwords-report.component.html b/apps/web/src/app/tools/reports/pages/reused-passwords-report.component.html index af336c94854..ded456ff963 100644 --- a/apps/web/src/app/tools/reports/pages/reused-passwords-report.component.html +++ b/apps/web/src/app/tools/reports/pages/reused-passwords-report.component.html @@ -33,73 +33,69 @@ - + - - - {{ "name" | i18n }} - {{ "owner" | i18n }} - {{ "timesReused" | i18n }} - + + {{ "name" | i18n }} + {{ "owner" | i18n }} + {{ "timesReused" | i18n }} - - - - - - - - {{ r.name }} - - - {{ r.name }} - - - - {{ "shared" | i18n }} - - - - {{ "attachments" | i18n }} - -
    - {{ r.subTitle }} - - - + + + + + + {{ row.name }} - - - - - {{ "reusedXTimes" | i18n: passwordUseMap.get(r.login.password) }} - - - + + + {{ row.name }} + + + + {{ "shared" | i18n }} + + + + {{ "attachments" | i18n }} + +
    + {{ row.subTitle }} + + + + + + + + {{ "reusedXTimes" | i18n: passwordUseMap.get(row.login.password) }} + +
    -
    +
    diff --git a/apps/web/src/app/tools/reports/pages/reused-passwords-report.component.spec.ts b/apps/web/src/app/tools/reports/pages/reused-passwords-report.component.spec.ts index 9d16bbb1c62..5933d2ce293 100644 --- a/apps/web/src/app/tools/reports/pages/reused-passwords-report.component.spec.ts +++ b/apps/web/src/app/tools/reports/pages/reused-passwords-report.component.spec.ts @@ -1,15 +1,20 @@ -// eslint-disable-next-line no-restricted-imports import { ComponentFixture, TestBed } from "@angular/core/testing"; import { MockProxy, mock } from "jest-mock-extended"; import { of } from "rxjs"; import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; -import { ModalService } from "@bitwarden/angular/services/modal.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { PasswordRepromptService } from "@bitwarden/vault"; +import { DialogService } from "@bitwarden/components"; +import { CipherFormConfigService, PasswordRepromptService } from "@bitwarden/vault"; + +import { AdminConsoleCipherFormConfigService } from "../../../vault/org-vault/services/admin-console-cipher-form-config.service"; import { cipherData } from "./reports-ciphers.mock"; import { ReusedPasswordsReportComponent } from "./reused-passwords-report.component"; @@ -19,10 +24,14 @@ describe("ReusedPasswordsReportComponent", () => { let fixture: ComponentFixture; let organizationService: MockProxy; let syncServiceMock: MockProxy; + let adminConsoleCipherFormConfigServiceMock: MockProxy; + const userId = Utils.newGuid() as UserId; + const accountService: FakeAccountService = mockAccountServiceWith(userId); beforeEach(() => { + let cipherFormConfigServiceMock: MockProxy; organizationService = mock(); - organizationService.organizations$ = of([]); + organizationService.organizations$.mockReturnValue(of([])); syncServiceMock = mock(); // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises @@ -38,8 +47,12 @@ describe("ReusedPasswordsReportComponent", () => { useValue: organizationService, }, { - provide: ModalService, - useValue: mock(), + provide: AccountService, + useValue: accountService, + }, + { + provide: DialogService, + useValue: mock(), }, { provide: PasswordRepromptService, @@ -53,8 +66,18 @@ describe("ReusedPasswordsReportComponent", () => { provide: I18nService, useValue: mock(), }, + { + provide: CipherFormConfigService, + useValue: cipherFormConfigServiceMock, + }, + { + provide: AdminConsoleCipherFormConfigService, + useValue: adminConsoleCipherFormConfigServiceMock, + }, ], schemas: [], + // FIXME(PM-18598): Replace unknownElements and unknownProperties with actual imports + errorOnUnknownElements: false, }).compileComponents(); }); diff --git a/apps/web/src/app/tools/reports/pages/reused-passwords-report.component.ts b/apps/web/src/app/tools/reports/pages/reused-passwords-report.component.ts index a8806acea13..6d70cc23875 100644 --- a/apps/web/src/app/tools/reports/pages/reused-passwords-report.component.ts +++ b/apps/web/src/app/tools/reports/pages/reused-passwords-report.component.ts @@ -2,14 +2,17 @@ // @ts-strict-ignore import { Component, OnInit } from "@angular/core"; -import { ModalService } from "@bitwarden/angular/services/modal.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { PasswordRepromptService } from "@bitwarden/vault"; +import { DialogService } from "@bitwarden/components"; +import { CipherFormConfigService, PasswordRepromptService } from "@bitwarden/vault"; + +import { AdminConsoleCipherFormConfigService } from "../../../vault/org-vault/services/admin-console-cipher-form-config.service"; import { CipherReportComponent } from "./cipher-report.component"; @@ -24,18 +27,24 @@ export class ReusedPasswordsReportComponent extends CipherReportComponent implem constructor( protected cipherService: CipherService, protected organizationService: OrganizationService, - modalService: ModalService, + dialogService: DialogService, + accountService: AccountService, passwordRepromptService: PasswordRepromptService, i18nService: I18nService, syncService: SyncService, + cipherFormConfigService: CipherFormConfigService, + adminConsoleCipherFormConfigService: AdminConsoleCipherFormConfigService, ) { super( cipherService, - modalService, + dialogService, passwordRepromptService, organizationService, + accountService, i18nService, syncService, + cipherFormConfigService, + adminConsoleCipherFormConfigService, ); } diff --git a/apps/web/src/app/tools/reports/pages/unsecured-websites-report.component.html b/apps/web/src/app/tools/reports/pages/unsecured-websites-report.component.html index 9293915363e..6632413a79e 100644 --- a/apps/web/src/app/tools/reports/pages/unsecured-websites-report.component.html +++ b/apps/web/src/app/tools/reports/pages/unsecured-websites-report.component.html @@ -47,15 +47,19 @@ - - {{ r.name }} - + + {{ r.name }} + + + {{ r.name }} + { let organizationService: MockProxy; let syncServiceMock: MockProxy; let collectionService: MockProxy; + let adminConsoleCipherFormConfigService: MockProxy; + const userId = Utils.newGuid() as UserId; + const accountService: FakeAccountService = mockAccountServiceWith(userId); beforeEach(() => { + let cipherFormConfigServiceMock: MockProxy; organizationService = mock(); - organizationService.organizations$ = of([]); + organizationService.organizations$.mockReturnValue(of([])); syncServiceMock = mock(); collectionService = mock(); + adminConsoleCipherFormConfigService = mock(); + // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises TestBed.configureTestingModule({ @@ -41,8 +52,12 @@ describe("UnsecuredWebsitesReportComponent", () => { useValue: organizationService, }, { - provide: ModalService, - useValue: mock(), + provide: AccountService, + useValue: accountService, + }, + { + provide: DialogService, + useValue: mock(), }, { provide: PasswordRepromptService, @@ -60,8 +75,18 @@ describe("UnsecuredWebsitesReportComponent", () => { provide: CollectionService, useValue: collectionService, }, + { + provide: CipherFormConfigService, + useValue: cipherFormConfigServiceMock, + }, + { + provide: AdminConsoleCipherFormConfigService, + useValue: adminConsoleCipherFormConfigService, + }, ], schemas: [], + // FIXME(PM-18598): Replace unknownElements and unknownProperties with actual imports + errorOnUnknownElements: false, }).compileComponents(); }); diff --git a/apps/web/src/app/tools/reports/pages/unsecured-websites-report.component.ts b/apps/web/src/app/tools/reports/pages/unsecured-websites-report.component.ts index 6a1ba1f6333..02d4117c684 100644 --- a/apps/web/src/app/tools/reports/pages/unsecured-websites-report.component.ts +++ b/apps/web/src/app/tools/reports/pages/unsecured-websites-report.component.ts @@ -1,14 +1,17 @@ import { Component, OnInit } from "@angular/core"; -import { CollectionService, Collection } from "@bitwarden/admin-console/common"; -import { ModalService } from "@bitwarden/angular/services/modal.service"; +import { CollectionService } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { PasswordRepromptService } from "@bitwarden/vault"; +import { DialogService } from "@bitwarden/components"; +import { CipherFormConfigService, PasswordRepromptService } from "@bitwarden/vault"; + +import { AdminConsoleCipherFormConfigService } from "../../../vault/org-vault/services/admin-console-cipher-form-config.service"; import { CipherReportComponent } from "./cipher-report.component"; @@ -22,19 +25,25 @@ export class UnsecuredWebsitesReportComponent extends CipherReportComponent impl constructor( protected cipherService: CipherService, protected organizationService: OrganizationService, - modalService: ModalService, + dialogService: DialogService, + accountService: AccountService, passwordRepromptService: PasswordRepromptService, i18nService: I18nService, syncService: SyncService, private collectionService: CollectionService, + cipherFormConfigService: CipherFormConfigService, + adminConsoleCipherFormConfigService: AdminConsoleCipherFormConfigService, ) { super( cipherService, - modalService, + dialogService, passwordRepromptService, organizationService, + accountService, i18nService, syncService, + cipherFormConfigService, + adminConsoleCipherFormConfigService, ); } @@ -44,17 +53,10 @@ export class UnsecuredWebsitesReportComponent extends CipherReportComponent impl async setCiphers() { const allCiphers = await this.getAllCiphers(); - const allCollections = await this.collectionService.getAll(); this.filterStatus = [0]; const unsecuredCiphers = allCiphers.filter((c) => { - const containsUnsecured = this.cipherContainsUnsecured(c); - if (containsUnsecured === false) { - return false; - } - - const canView = this.canView(c, allCollections); - return canView; + return this.cipherContainsUnsecured(c); }); this.filterCiphersByOrg(unsecuredCiphers); @@ -65,7 +67,12 @@ export class UnsecuredWebsitesReportComponent extends CipherReportComponent impl * @param cipher Current cipher with unsecured uri */ private cipherContainsUnsecured(cipher: CipherView): boolean { - if (cipher.type !== CipherType.Login || !cipher.login.hasUris || cipher.isDeleted) { + if ( + cipher.type !== CipherType.Login || + !cipher.login.hasUris || + cipher.isDeleted || + (!this.organization && !cipher.edit) + ) { return false; } @@ -76,19 +83,13 @@ export class UnsecuredWebsitesReportComponent extends CipherReportComponent impl } /** - * If the user does not have readonly set or it's false they have the ability to edit - * @param cipher Current cipher with unsecured uri - * @param allCollections The collections for the user + * Provides a way to determine if someone with permissions to run an organizational report is also able to view/edit ciphers within the results + * Default to true for indivduals running reports on their own vault. + * @param c CipherView + * @returns boolean */ - private canView(cipher: CipherView, allCollections: Collection[]): boolean { - if (!cipher.organizationId) { - return true; - } - - return ( - allCollections.filter( - (item) => cipher.collectionIds.indexOf(item.id) > -1 && !(item.readOnly ?? false), - ).length > 0 - ); + protected canManageCipher(c: CipherView): boolean { + // this will only ever be false from the org view; + return true; } } diff --git a/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.html b/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.html index 9c5b587e60a..3ef1d11f9b2 100644 --- a/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.html +++ b/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.html @@ -31,77 +31,73 @@ - + - - - {{ "name" | i18n }} - - {{ "owner" | i18n }} - - - {{ "weakness" | i18n }} - - + + {{ "name" | i18n }} + + {{ "owner" | i18n }} + + + {{ "weakness" | i18n }} + - - - - - - - - {{ r.name }} - - - {{ r.name }} - - - - {{ "shared" | i18n }} - - - - {{ "attachments" | i18n }} - -
    - {{ r.subTitle }} - - - + + + + + + {{ row.name }} - - - - - {{ r.reportValue.label | i18n }} - - - + + + {{ row.name }} + + + + {{ "shared" | i18n }} + + + + {{ "attachments" | i18n }} + +
    + {{ row.subTitle }} + + + + + + + + {{ row.reportValue.label | i18n }} + +
    -
    +
    diff --git a/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.spec.ts b/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.spec.ts index bcace60ac0c..d78dc7e3ceb 100644 --- a/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.spec.ts +++ b/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.spec.ts @@ -1,16 +1,21 @@ -// eslint-disable-next-line no-restricted-imports import { ComponentFixture, TestBed } from "@angular/core/testing"; import { mock, MockProxy } from "jest-mock-extended"; import { of } from "rxjs"; import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; -import { ModalService } from "@bitwarden/angular/services/modal.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { PasswordRepromptService } from "@bitwarden/vault"; +import { DialogService } from "@bitwarden/components"; +import { CipherFormConfigService, PasswordRepromptService } from "@bitwarden/vault"; + +import { AdminConsoleCipherFormConfigService } from "../../../vault/org-vault/services/admin-console-cipher-form-config.service"; import { cipherData } from "./reports-ciphers.mock"; import { WeakPasswordsReportComponent } from "./weak-passwords-report.component"; @@ -21,12 +26,16 @@ describe("WeakPasswordsReportComponent", () => { let passwordStrengthService: MockProxy; let organizationService: MockProxy; let syncServiceMock: MockProxy; + let adminConsoleCipherFormConfigServiceMock: MockProxy; + const userId = Utils.newGuid() as UserId; + const accountService: FakeAccountService = mockAccountServiceWith(userId); beforeEach(() => { + let cipherFormConfigServiceMock: MockProxy; syncServiceMock = mock(); passwordStrengthService = mock(); organizationService = mock(); - organizationService.organizations$ = of([]); + organizationService.organizations$.mockReturnValue(of([])); // 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 TestBed.configureTestingModule({ @@ -45,8 +54,12 @@ describe("WeakPasswordsReportComponent", () => { useValue: organizationService, }, { - provide: ModalService, - useValue: mock(), + provide: AccountService, + useValue: accountService, + }, + { + provide: DialogService, + useValue: mock(), }, { provide: PasswordRepromptService, @@ -60,8 +73,19 @@ describe("WeakPasswordsReportComponent", () => { provide: I18nService, useValue: mock(), }, + { + provide: CipherFormConfigService, + useValue: cipherFormConfigServiceMock, + }, + + { + provide: AdminConsoleCipherFormConfigService, + useValue: adminConsoleCipherFormConfigServiceMock, + }, ], schemas: [], + // FIXME(PM-18598): Replace unknownElements and unknownProperties with actual imports + errorOnUnknownElements: false, }).compileComponents(); }); diff --git a/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.ts b/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.ts index f3ad6840c8b..3c9186267ca 100644 --- a/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.ts +++ b/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.ts @@ -2,8 +2,8 @@ // @ts-strict-ignore import { Component, OnInit } from "@angular/core"; -import { ModalService } from "@bitwarden/angular/services/modal.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; @@ -11,8 +11,10 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { BadgeVariant } from "@bitwarden/components"; -import { PasswordRepromptService } from "@bitwarden/vault"; +import { BadgeVariant, DialogService } from "@bitwarden/components"; +import { CipherFormConfigService, PasswordRepromptService } from "@bitwarden/vault"; + +import { AdminConsoleCipherFormConfigService } from "../../../vault/org-vault/services/admin-console-cipher-form-config.service"; import { CipherReportComponent } from "./cipher-report.component"; @@ -32,18 +34,24 @@ export class WeakPasswordsReportComponent extends CipherReportComponent implemen protected cipherService: CipherService, protected passwordStrengthService: PasswordStrengthServiceAbstraction, protected organizationService: OrganizationService, - modalService: ModalService, + dialogService: DialogService, + protected accountService: AccountService, passwordRepromptService: PasswordRepromptService, i18nService: I18nService, syncService: SyncService, + cipherFormConfigService: CipherFormConfigService, + adminConsoleCipherFormConfigService: AdminConsoleCipherFormConfigService, ) { super( cipherService, - modalService, + dialogService, passwordRepromptService, organizationService, + accountService, i18nService, syncService, + cipherFormConfigService, + adminConsoleCipherFormConfigService, ); } diff --git a/apps/web/src/app/tools/reports/reports-routing.module.ts b/apps/web/src/app/tools/reports/reports-routing.module.ts index cad6586bb82..941e6eb7d3d 100644 --- a/apps/web/src/app/tools/reports/reports-routing.module.ts +++ b/apps/web/src/app/tools/reports/reports-routing.module.ts @@ -3,7 +3,7 @@ import { RouterModule, Routes } from "@angular/router"; import { authGuard } from "@bitwarden/angular/auth/guards"; -import { hasPremiumGuard } from "../../core/guards/has-premium.guard"; +import { hasPremiumGuard } from "../../billing/guards/has-premium.guard"; import { BreachReportComponent } from "./pages/breach-report.component"; import { ExposedPasswordsReportComponent } from "./pages/exposed-passwords-report.component"; diff --git a/apps/web/src/app/tools/reports/reports.module.ts b/apps/web/src/app/tools/reports/reports.module.ts index 10849d56d9c..358768e71ee 100644 --- a/apps/web/src/app/tools/reports/reports.module.ts +++ b/apps/web/src/app/tools/reports/reports.module.ts @@ -1,10 +1,15 @@ import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; +import { CipherFormConfigService, DefaultCipherFormConfigService } from "@bitwarden/vault"; + import { HeaderModule } from "../../layouts/header/header.module"; import { SharedModule } from "../../shared"; import { OrganizationBadgeModule } from "../../vault/individual-vault/organization-badge/organization-badge.module"; import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module"; +import { RoutedVaultFilterBridgeService } from "../../vault/individual-vault/vault-filter/services/routed-vault-filter-bridge.service"; +import { RoutedVaultFilterService } from "../../vault/individual-vault/vault-filter/services/routed-vault-filter.service"; +import { AdminConsoleCipherFormConfigService } from "../../vault/org-vault/services/admin-console-cipher-form-config.service"; import { BreachReportComponent } from "./pages/breach-report.component"; import { ExposedPasswordsReportComponent } from "./pages/exposed-passwords-report.component"; @@ -37,5 +42,14 @@ import { ReportsSharedModule } from "./shared"; UnsecuredWebsitesReportComponent, WeakPasswordsReportComponent, ], + providers: [ + { + provide: CipherFormConfigService, + useClass: DefaultCipherFormConfigService, + }, + RoutedVaultFilterService, + AdminConsoleCipherFormConfigService, + RoutedVaultFilterBridgeService, + ], }) export class ReportsModule {} diff --git a/apps/web/src/app/tools/send/add-edit.component.html b/apps/web/src/app/tools/send/add-edit.component.html deleted file mode 100644 index 7eade18a7c6..00000000000 --- a/apps/web/src/app/tools/send/add-edit.component.html +++ /dev/null @@ -1,286 +0,0 @@ -
    - - - {{ title }} - - - - {{ "sendDisabledWarning" | i18n }} - - - {{ "sendOptionsPolicyInEffect" | i18n }} -
      -
    • {{ "sendDisableHideEmailInEffect" | i18n }}
    • -
    -
    - - {{ "name" | i18n }} - - {{ "sendNameDesc" | i18n }} - -
    - - {{ "whatTypeOfSend" | i18n }} - - - - {{ o.name }} - - - - -
    - - - - {{ "sendTypeText" | i18n }} - - {{ "sendTextDesc" | i18n }} - - - - {{ "textHiddenByDefault" | i18n }} - - - - -
    -
    - {{ "file" | i18n }} -

    - {{ send.file.fileName }} ({{ send.file.sizeName }}) -

    -
    - - {{ "file" | i18n }} -
    - - {{ selectedFile?.name ?? ("noFileChosen" | i18n) }} -
    - - {{ "sendFileDesc" | i18n }} {{ "maxFileSize" | i18n }} -
    -
    -
    -

    {{ "share" | i18n }}

    - - - {{ "sendLinkLabel" | i18n }} - - - - - - {{ "copySendLinkOnSave" | i18n }} - -
    -

    - -

    -
    -
    -
    -
    - - {{ "deletionDate" | i18n }} - - - - - - - {{ "deletionDateDesc" | i18n }} - -
    -
    - - {{ "deletionDate" | i18n }} - - {{ "deletionDateDesc" | i18n }} - -
    -
    - - - {{ "expirationDate" | i18n }} - - - - - - - - {{ "expirationDateDesc" | i18n }} - -
    -
    - - - {{ "expirationDate" | i18n }} - - - - - {{ "expirationDateDesc" | i18n }} - -
    -
    -
    - - {{ "maxAccessCount" | i18n }} - - {{ "maxAccessCountDesc" | i18n }} - - - {{ "currentAccessCount" | i18n }} - - -
    -
    - - {{ "password" | i18n }} - {{ "newPassword" | i18n }} - - - - {{ "sendPasswordDesc" | i18n }} - -
    - - {{ "notes" | i18n }} - - {{ "sendNotesDesc" | i18n }} - - - - - {{ "hideEmail" | i18n }} - - - - - {{ "disableThisSend" | i18n }} - -
    -
    - - - - - - -
    -
    diff --git a/apps/web/src/app/tools/send/add-edit.component.ts b/apps/web/src/app/tools/send/add-edit.component.ts deleted file mode 100644 index 4ce126a33bc..00000000000 --- a/apps/web/src/app/tools/send/add-edit.component.ts +++ /dev/null @@ -1,102 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog"; -import { DatePipe } from "@angular/common"; -import { Component, Inject } from "@angular/core"; -import { FormBuilder } from "@angular/forms"; - -import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/tools/send/add-edit.component"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; -import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; -import { DialogService, ToastService } from "@bitwarden/components"; - -@Component({ - selector: "app-send-add-edit", - templateUrl: "add-edit.component.html", -}) -export class AddEditComponent extends BaseAddEditComponent { - override componentName = "app-send-add-edit"; - protected selectedFile: File; - - constructor( - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - environmentService: EnvironmentService, - datePipe: DatePipe, - sendService: SendService, - stateService: StateService, - messagingService: MessagingService, - policyService: PolicyService, - logService: LogService, - sendApiService: SendApiService, - dialogService: DialogService, - formBuilder: FormBuilder, - billingAccountProfileStateService: BillingAccountProfileStateService, - protected dialogRef: DialogRef, - @Inject(DIALOG_DATA) params: { sendId: string }, - accountService: AccountService, - toastService: ToastService, - ) { - super( - i18nService, - platformUtilsService, - environmentService, - datePipe, - sendService, - messagingService, - policyService, - logService, - stateService, - sendApiService, - dialogService, - formBuilder, - billingAccountProfileStateService, - accountService, - toastService, - ); - - this.sendId = params.sendId; - } - - async copyLinkToClipboard(link: string): Promise { - // Copy function on web depends on the modal being open or not. Since this event occurs during a transition - // of the modal closing we need to add a small delay to make sure state of the DOM is consistent. - return new Promise((resolve) => { - window.setTimeout(() => resolve(super.copyLinkToClipboard(link)), 500); - }); - } - - protected setSelectedFile(event: Event) { - const fileInputEl = event.target; - const file = fileInputEl.files.length > 0 ? fileInputEl.files[0] : null; - this.selectedFile = file; - } - - submitAndClose = async () => { - this.formGroup.markAllAsTouched(); - if (this.formGroup.invalid) { - return; - } - - const success = await this.submit(); - if (success) { - this.dialogRef.close(); - } - }; - - deleteAndClose = async () => { - const success = await this.delete(); - if (success) { - this.dialogRef.close(); - } - }; -} diff --git a/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.html b/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.html new file mode 100644 index 00000000000..34e28be1084 --- /dev/null +++ b/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.html @@ -0,0 +1,23 @@ + + + + + {{ "sendTypeText" | i18n }} + + + + {{ "sendTypeFile" | i18n }} + + + diff --git a/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.ts b/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.ts new file mode 100644 index 00000000000..8cd052aa016 --- /dev/null +++ b/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.ts @@ -0,0 +1,63 @@ +import { CommonModule } from "@angular/common"; +import { Component, Input } from "@angular/core"; +import { Router } from "@angular/router"; +import { firstValueFrom, Observable, of, switchMap } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; +import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; +import { BadgeModule, ButtonModule, DialogService, MenuModule } from "@bitwarden/components"; +import { DefaultSendFormConfigService, SendAddEditDialogComponent } from "@bitwarden/send-ui"; + +@Component({ + selector: "tools-new-send-dropdown", + templateUrl: "new-send-dropdown.component.html", + standalone: true, + imports: [JslibModule, CommonModule, ButtonModule, MenuModule, BadgeModule], + providers: [DefaultSendFormConfigService], +}) +/** + * A dropdown component that allows the user to create a new Send of a specific type. + */ +export class NewSendDropdownComponent { + /** If true, the plus icon will be hidden */ + @Input() hideIcon: boolean = false; + + /** SendType provided for the markup to pass back the selected type of Send */ + protected sendType = SendType; + + /** Indicates whether the user can access premium features. */ + protected canAccessPremium$: Observable; + + constructor( + private router: Router, + private billingAccountProfileStateService: BillingAccountProfileStateService, + private accountService: AccountService, + private dialogService: DialogService, + private addEditFormConfigService: DefaultSendFormConfigService, + ) { + this.canAccessPremium$ = this.accountService.activeAccount$.pipe( + switchMap((account) => + account + ? this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id) + : of(false), + ), + ); + } + + /** + * Opens the SendAddEditComponent for a new Send with the provided type. + * If has user does not have premium access and the type is File, the user will be redirected to the premium settings page. + * @param type The type of Send to create. + */ + async createSend(type: SendType) { + if (!(await firstValueFrom(this.canAccessPremium$)) && type === SendType.File) { + return await this.router.navigate(["settings/subscription/premium"]); + } + + const formConfig = await this.addEditFormConfigService.buildConfig("add", undefined, type); + + await SendAddEditDialogComponent.open(this.dialogService, { formConfig }); + } +} diff --git a/apps/web/src/app/tools/send/send-access-explainer.component.ts b/apps/web/src/app/tools/send/send-access-explainer.component.ts deleted file mode 100644 index 756a1068985..00000000000 --- a/apps/web/src/app/tools/send/send-access-explainer.component.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Component } from "@angular/core"; - -import { RegisterRouteService } from "@bitwarden/auth/common"; - -import { SharedModule } from "../../shared"; - -@Component({ - selector: "app-send-access-explainer", - templateUrl: "send-access-explainer.component.html", - standalone: true, - imports: [SharedModule], -}) -export class SendAccessExplainerComponent { - // TODO: remove when email verification flag is removed - registerRoute$ = this.registerRouteService.registerRoute$(); - constructor(private registerRouteService: RegisterRouteService) {} -} diff --git a/apps/web/src/app/tools/send/access.component.html b/apps/web/src/app/tools/send/send-access/access.component.html similarity index 100% rename from apps/web/src/app/tools/send/access.component.html rename to apps/web/src/app/tools/send/send-access/access.component.html diff --git a/apps/web/src/app/tools/send/access.component.ts b/apps/web/src/app/tools/send/send-access/access.component.ts similarity index 92% rename from apps/web/src/app/tools/send/access.component.ts rename to apps/web/src/app/tools/send/send-access/access.component.ts index 80439acd510..6bed32e97d5 100644 --- a/apps/web/src/app/tools/send/access.component.ts +++ b/apps/web/src/app/tools/send/send-access/access.component.ts @@ -5,9 +5,7 @@ import { FormBuilder } from "@angular/forms"; import { ActivatedRoute } from "@angular/router"; import { AnonLayoutWrapperDataService } from "@bitwarden/auth/angular"; -import { RegisterRouteService } from "@bitwarden/auth/common"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -23,7 +21,7 @@ import { NoItemsModule, ToastService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; import { ExpiredSendIcon } from "@bitwarden/send-ui"; -import { SharedModule } from "../../shared"; +import { SharedModule } from "../../../shared"; import { SendAccessFileComponent } from "./send-access-file.component"; import { SendAccessPasswordComponent } from "./send-access-password.component"; @@ -41,7 +39,6 @@ import { SendAccessTextComponent } from "./send-access-text.component"; NoItemsModule, ], }) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil export class AccessComponent implements OnInit { protected send: SendAccessView; protected sendType = SendType; @@ -58,9 +55,6 @@ export class AccessComponent implements OnInit { protected formGroup = this.formBuilder.group({}); - // TODO: remove when email verification flag is removed - registerRoute$ = this.registerRouteService.registerRoute$(); - private id: string; private key: string; @@ -71,8 +65,6 @@ export class AccessComponent implements OnInit { private sendApiService: SendApiService, private toastService: ToastService, private i18nService: I18nService, - private configService: ConfigService, - private registerRouteService: RegisterRouteService, private layoutWrapperDataService: AnonLayoutWrapperDataService, protected formBuilder: FormBuilder, ) {} diff --git a/apps/web/src/app/tools/send/send-access/index.ts b/apps/web/src/app/tools/send/send-access/index.ts new file mode 100644 index 00000000000..c9df5ce5193 --- /dev/null +++ b/apps/web/src/app/tools/send/send-access/index.ts @@ -0,0 +1,2 @@ +export { AccessComponent } from "./access.component"; +export { SendAccessExplainerComponent } from "./send-access-explainer.component"; diff --git a/apps/web/src/app/tools/send/send-access-explainer.component.html b/apps/web/src/app/tools/send/send-access/send-access-explainer.component.html similarity index 84% rename from apps/web/src/app/tools/send/send-access-explainer.component.html rename to apps/web/src/app/tools/send/send-access/send-access-explainer.component.html index e8090cb850c..f56b68cc299 100644 --- a/apps/web/src/app/tools/send/send-access-explainer.component.html +++ b/apps/web/src/app/tools/send/send-access/send-access-explainer.component.html @@ -10,7 +10,7 @@ >Bitwarden Send {{ "sendAccessTaglineOr" | i18n }} - {{ + {{ "sendAccessTaglineSignUp" | i18n }} {{ "sendAccessTaglineTryToday" | i18n }} diff --git a/apps/web/src/app/tools/send/send-access/send-access-explainer.component.ts b/apps/web/src/app/tools/send/send-access/send-access-explainer.component.ts new file mode 100644 index 00000000000..ec39d970444 --- /dev/null +++ b/apps/web/src/app/tools/send/send-access/send-access-explainer.component.ts @@ -0,0 +1,13 @@ +import { Component } from "@angular/core"; + +import { SharedModule } from "../../../shared"; + +@Component({ + selector: "app-send-access-explainer", + templateUrl: "send-access-explainer.component.html", + standalone: true, + imports: [SharedModule], +}) +export class SendAccessExplainerComponent { + constructor() {} +} diff --git a/apps/web/src/app/tools/send/send-access-file.component.html b/apps/web/src/app/tools/send/send-access/send-access-file.component.html similarity index 100% rename from apps/web/src/app/tools/send/send-access-file.component.html rename to apps/web/src/app/tools/send/send-access/send-access-file.component.html diff --git a/apps/web/src/app/tools/send/send-access-file.component.ts b/apps/web/src/app/tools/send/send-access/send-access-file.component.ts similarity index 90% rename from apps/web/src/app/tools/send/send-access-file.component.ts rename to apps/web/src/app/tools/send/send-access/send-access-file.component.ts index 0b2a971bbe8..eec0bfd787b 100644 --- a/apps/web/src/app/tools/send/send-access-file.component.ts +++ b/apps/web/src/app/tools/send/send-access/send-access-file.component.ts @@ -2,7 +2,7 @@ // @ts-strict-ignore import { Component, Input } from "@angular/core"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -13,7 +13,7 @@ import { SendAccessView } from "@bitwarden/common/tools/send/models/view/send-ac import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; import { ToastService } from "@bitwarden/components"; -import { SharedModule } from "../../shared"; +import { SharedModule } from "../../../shared"; @Component({ selector: "app-send-access-file", @@ -70,6 +70,8 @@ export class SendAccessFileComponent { blobData: decBuf, downloadMethod: "save", }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.toastService.showToast({ variant: "error", diff --git a/apps/web/src/app/tools/send/send-access-password.component.html b/apps/web/src/app/tools/send/send-access/send-access-password.component.html similarity index 100% rename from apps/web/src/app/tools/send/send-access-password.component.html rename to apps/web/src/app/tools/send/send-access/send-access-password.component.html diff --git a/apps/web/src/app/tools/send/send-access-password.component.ts b/apps/web/src/app/tools/send/send-access/send-access-password.component.ts similarity index 95% rename from apps/web/src/app/tools/send/send-access-password.component.ts rename to apps/web/src/app/tools/send/send-access/send-access-password.component.ts index bd98e9d18c8..0cfd93fcea0 100644 --- a/apps/web/src/app/tools/send/send-access-password.component.ts +++ b/apps/web/src/app/tools/send/send-access/send-access-password.component.ts @@ -4,7 +4,7 @@ import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angu import { FormBuilder, Validators } from "@angular/forms"; import { Subject, takeUntil } from "rxjs"; -import { SharedModule } from "../../shared"; +import { SharedModule } from "../../../shared"; @Component({ selector: "app-send-access-password", diff --git a/apps/web/src/app/tools/send/send-access-text.component.html b/apps/web/src/app/tools/send/send-access/send-access-text.component.html similarity index 100% rename from apps/web/src/app/tools/send/send-access-text.component.html rename to apps/web/src/app/tools/send/send-access/send-access-text.component.html diff --git a/apps/web/src/app/tools/send/send-access-text.component.ts b/apps/web/src/app/tools/send/send-access/send-access-text.component.ts similarity index 97% rename from apps/web/src/app/tools/send/send-access-text.component.ts rename to apps/web/src/app/tools/send/send-access/send-access-text.component.ts index 6568fe482ad..6f9bc798d4b 100644 --- a/apps/web/src/app/tools/send/send-access-text.component.ts +++ b/apps/web/src/app/tools/send/send-access/send-access-text.component.ts @@ -8,7 +8,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { SendAccessView } from "@bitwarden/common/tools/send/models/view/send-access.view"; import { ToastService } from "@bitwarden/components"; -import { SharedModule } from "../../shared"; +import { SharedModule } from "../../../shared"; @Component({ selector: "app-send-access-text", diff --git a/apps/web/src/app/tools/send/send.component.html b/apps/web/src/app/tools/send/send.component.html index 5a88f45cdc6..6f690459bb0 100644 --- a/apps/web/src/app/tools/send/send.component.html +++ b/apps/web/src/app/tools/send/send.component.html @@ -11,23 +11,22 @@
    - - + {{ "sendDisabledWarning" | i18n }}
    -
    -
    -
    +
    +
    +
    {{ "filters" | i18n }}
    -
    +
    {{ "sendsNoItemsTitle" | i18n }} {{ "sendsNoItemsMessage" | i18n }} - +
    diff --git a/apps/web/src/app/tools/send/send.component.ts b/apps/web/src/app/tools/send/send.component.ts index 1268e4bfb50..d88517f53e5 100644 --- a/apps/web/src/app/tools/send/send.component.ts +++ b/apps/web/src/app/tools/send/send.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, NgZone, ViewChild, OnInit, OnDestroy, ViewContainerRef } from "@angular/core"; +import { DialogRef } from "@angular/cdk/dialog"; +import { Component, NgZone, OnInit, OnDestroy } from "@angular/core"; import { lastValueFrom } from "rxjs"; import { SendComponent as BaseSendComponent } from "@bitwarden/angular/tools/send/send.component"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -14,6 +16,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; +import { SendId } from "@bitwarden/common/types/guid"; import { DialogService, NoItemsModule, @@ -21,24 +24,30 @@ import { TableDataSource, ToastService, } from "@bitwarden/components"; -import { NoSendsIcon } from "@bitwarden/send-ui"; +import { + DefaultSendFormConfigService, + NoSendsIcon, + SendFormConfig, + SendAddEditDialogComponent, + SendItemDialogResult, +} from "@bitwarden/send-ui"; import { HeaderModule } from "../../layouts/header/header.module"; import { SharedModule } from "../../shared"; -import { AddEditComponent } from "./add-edit.component"; +import { NewSendDropdownComponent } from "./new-send/new-send-dropdown.component"; const BroadcasterSubscriptionId = "SendComponent"; @Component({ selector: "app-send", standalone: true, - imports: [SharedModule, SearchModule, NoItemsModule, HeaderModule], + imports: [SharedModule, SearchModule, NoItemsModule, HeaderModule, NewSendDropdownComponent], templateUrl: "send.component.html", + providers: [DefaultSendFormConfigService], }) export class SendComponent extends BaseSendComponent implements OnInit, OnDestroy { - @ViewChild("sendAddEdit", { read: ViewContainerRef, static: true }) - sendAddEditModalRef: ViewContainerRef; + private sendItemDialogRef?: DialogRef | undefined; noItemIcon = NoSendsIcon; override set filteredSends(filteredSends: SendView[]) { @@ -65,6 +74,8 @@ export class SendComponent extends BaseSendComponent implements OnInit, OnDestro sendApiService: SendApiService, dialogService: DialogService, toastService: ToastService, + private addEditFormConfigService: DefaultSendFormConfigService, + accountService: AccountService, ) { super( sendService, @@ -78,6 +89,7 @@ export class SendComponent extends BaseSendComponent implements OnInit, OnDestro sendApiService, dialogService, toastService, + accountService, ); } @@ -111,17 +123,41 @@ export class SendComponent extends BaseSendComponent implements OnInit, OnDestro return; } - await this.editSend(null); + const config = await this.addEditFormConfigService.buildConfig("add", null, 0); + + await this.openSendItemDialog(config); } async editSend(send: SendView) { - const dialog = this.dialogService.open(AddEditComponent, { - data: { - sendId: send == null ? null : send.id, - }, + const config = await this.addEditFormConfigService.buildConfig( + send == null ? "add" : "edit", + send == null ? null : (send.id as SendId), + send.type, + ); + + await this.openSendItemDialog(config); + } + + /** + * Opens the send item dialog. + * @param formConfig The form configuration. + * */ + async openSendItemDialog(formConfig: SendFormConfig) { + // Prevent multiple dialogs from being opened. + if (this.sendItemDialogRef) { + return; + } + + this.sendItemDialogRef = SendAddEditDialogComponent.open(this.dialogService, { + formConfig, }); - await lastValueFrom(dialog.closed); - await this.load(); + const result = await lastValueFrom(this.sendItemDialogRef.closed); + this.sendItemDialogRef = undefined; + + // If the dialog was closed by deleting the cipher, refresh the vault. + if (result === SendItemDialogResult.Deleted || result === SendItemDialogResult.Saved) { + await this.load(); + } } } diff --git a/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export.component.html b/apps/web/src/app/tools/vault-export/org-vault-export.component.html similarity index 100% rename from apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export.component.html rename to apps/web/src/app/tools/vault-export/org-vault-export.component.html diff --git a/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export.component.ts b/apps/web/src/app/tools/vault-export/org-vault-export.component.ts similarity index 92% rename from apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export.component.ts rename to apps/web/src/app/tools/vault-export/org-vault-export.component.ts index 93b8ebd1f23..d84d2b26a90 100644 --- a/apps/web/src/app/admin-console/organizations/tools/vault-export/org-vault-export.component.ts +++ b/apps/web/src/app/tools/vault-export/org-vault-export.component.ts @@ -5,7 +5,7 @@ import { ActivatedRoute } from "@angular/router"; import { ExportComponent } from "@bitwarden/vault-export-ui"; -import { LooseComponentsModule, SharedModule } from "../../../../shared"; +import { LooseComponentsModule, SharedModule } from "../../shared"; @Component({ templateUrl: "org-vault-export.component.html", diff --git a/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt-install.component.html b/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt-install.component.html new file mode 100644 index 00000000000..eb8e3d16422 --- /dev/null +++ b/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt-install.component.html @@ -0,0 +1,6 @@ +
    +

    {{ "doNotHaveExtension" | i18n }}

    + + {{ "installExtension" | i18n }} + +
    diff --git a/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt-install.component.spec.ts b/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt-install.component.spec.ts new file mode 100644 index 00000000000..e3729130a01 --- /dev/null +++ b/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt-install.component.spec.ts @@ -0,0 +1,145 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { By } from "@angular/platform-browser"; +import { BehaviorSubject } from "rxjs"; + +import { DeviceType } from "@bitwarden/common/enums"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; + +import { + BrowserExtensionPromptService, + BrowserPromptState, +} from "../../services/browser-extension-prompt.service"; + +import { BrowserExtensionPromptInstallComponent } from "./browser-extension-prompt-install.component"; + +describe("BrowserExtensionInstallComponent", () => { + let fixture: ComponentFixture; + let component: BrowserExtensionPromptInstallComponent; + const pageState$ = new BehaviorSubject(BrowserPromptState.Loading); + + const getDevice = jest.fn(); + + beforeEach(async () => { + getDevice.mockClear(); + await TestBed.configureTestingModule({ + providers: [ + { + provide: BrowserExtensionPromptService, + useValue: { pageState$ }, + }, + { + provide: I18nService, + useValue: { t: (key: string) => key }, + }, + { + provide: PlatformUtilsService, + useValue: { getDevice }, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(BrowserExtensionPromptInstallComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("only shows during error state", () => { + expect(fixture.nativeElement.textContent).toBe(""); + + pageState$.next(BrowserPromptState.Success); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent).toBe(""); + + pageState$.next(BrowserPromptState.Error); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent).not.toBe(""); + + pageState$.next(BrowserPromptState.ManualOpen); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent).not.toBe(""); + }); + + describe("error state", () => { + beforeEach(() => { + pageState$.next(BrowserPromptState.Error); + fixture.detectChanges(); + }); + + it("shows error text", () => { + const errorText = fixture.debugElement.query(By.css("p")).nativeElement; + expect(errorText.textContent).toBe("doNotHaveExtension"); + }); + + it("links to bitwarden installation page by default", () => { + const link = fixture.debugElement.query(By.css("a")).nativeElement; + + expect(link.getAttribute("href")).toBe( + "https://bitwarden.com/download/#downloads-web-browser", + ); + }); + + it("links to bitwarden installation page for Chrome", () => { + getDevice.mockReturnValue(DeviceType.ChromeBrowser); + component.ngOnInit(); + fixture.detectChanges(); + + const link = fixture.debugElement.query(By.css("a")).nativeElement; + + expect(link.getAttribute("href")).toBe( + "https://chrome.google.com/webstore/detail/bitwarden-password-manage/nngceckbapebfimnlniiiahkandclblb", + ); + }); + + it("links to bitwarden installation page for Firefox", () => { + getDevice.mockReturnValue(DeviceType.FirefoxBrowser); + component.ngOnInit(); + fixture.detectChanges(); + + const link = fixture.debugElement.query(By.css("a")).nativeElement; + + expect(link.getAttribute("href")).toBe( + "https://addons.mozilla.org/en-US/firefox/addon/bitwarden-password-manager/", + ); + }); + + it("links to bitwarden installation page for Safari", () => { + getDevice.mockReturnValue(DeviceType.SafariBrowser); + component.ngOnInit(); + fixture.detectChanges(); + + const link = fixture.debugElement.query(By.css("a")).nativeElement; + + expect(link.getAttribute("href")).toBe( + "https://apps.apple.com/us/app/bitwarden/id1352778147?mt=12", + ); + }); + + it("links to bitwarden installation page for Opera", () => { + getDevice.mockReturnValue(DeviceType.OperaBrowser); + component.ngOnInit(); + fixture.detectChanges(); + + const link = fixture.debugElement.query(By.css("a")).nativeElement; + + expect(link.getAttribute("href")).toBe( + "https://addons.opera.com/extensions/details/bitwarden-free-password-manager/", + ); + }); + + it("links to bitwarden installation page for Edge", () => { + getDevice.mockReturnValue(DeviceType.EdgeBrowser); + component.ngOnInit(); + fixture.detectChanges(); + + const link = fixture.debugElement.query(By.css("a")).nativeElement; + + expect(link.getAttribute("href")).toBe( + "https://microsoftedge.microsoft.com/addons/detail/jbkfoedolllekgbhcbcoahefnbanhhlh", + ); + }); + }); +}); diff --git a/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt-install.component.ts b/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt-install.component.ts new file mode 100644 index 00000000000..73f4307d9cc --- /dev/null +++ b/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt-install.component.ts @@ -0,0 +1,66 @@ +import { CommonModule } from "@angular/common"; +import { Component, OnInit } from "@angular/core"; +import { map } from "rxjs"; + +import { DeviceType } from "@bitwarden/common/enums"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { LinkModule } from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; + +import { + BrowserExtensionPromptService, + BrowserPromptState, +} from "../../services/browser-extension-prompt.service"; + +/** Device specific Urls for the extension */ +const WebStoreUrls: Partial> = { + [DeviceType.ChromeBrowser]: + "https://chrome.google.com/webstore/detail/bitwarden-password-manage/nngceckbapebfimnlniiiahkandclblb", + [DeviceType.FirefoxBrowser]: + "https://addons.mozilla.org/en-US/firefox/addon/bitwarden-password-manager/", + [DeviceType.SafariBrowser]: "https://apps.apple.com/us/app/bitwarden/id1352778147?mt=12", + [DeviceType.OperaBrowser]: + "https://addons.opera.com/extensions/details/bitwarden-free-password-manager/", + [DeviceType.EdgeBrowser]: + "https://microsoftedge.microsoft.com/addons/detail/jbkfoedolllekgbhcbcoahefnbanhhlh", +}; + +@Component({ + selector: "vault-browser-extension-prompt-install", + templateUrl: "./browser-extension-prompt-install.component.html", + standalone: true, + imports: [CommonModule, I18nPipe, LinkModule], +}) +export class BrowserExtensionPromptInstallComponent implements OnInit { + /** The install link should only show for the error states */ + protected shouldShow$ = this.browserExtensionPromptService.pageState$.pipe( + map((state) => state === BrowserPromptState.Error || state === BrowserPromptState.ManualOpen), + ); + + /** All available page states */ + protected BrowserPromptState = BrowserPromptState; + + /** + * Installation link for the extension + */ + protected webStoreUrl: string = "https://bitwarden.com/download/#downloads-web-browser"; + + constructor( + private browserExtensionPromptService: BrowserExtensionPromptService, + private platformService: PlatformUtilsService, + ) {} + + ngOnInit(): void { + this.setBrowserStoreLink(); + } + + /** If available, set web store specific URL for the extension */ + private setBrowserStoreLink(): void { + const deviceType = this.platformService.getDevice(); + const platformSpecificUrl = WebStoreUrls[deviceType]; + + if (platformSpecificUrl) { + this.webStoreUrl = platformSpecificUrl; + } + } +} diff --git a/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt.component.html b/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt.component.html new file mode 100644 index 00000000000..1c643fcc3e4 --- /dev/null +++ b/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt.component.html @@ -0,0 +1,44 @@ +
    + + +

    {{ "openingExtension" | i18n }}

    +
    + + +

    {{ "openingExtensionError" | i18n }}

    + +
    + + + +

    + {{ "openedExtensionViewAtRiskPasswords" | i18n }} +

    +
    + + +

    + {{ "openExtensionManuallyPart1" | i18n }} + + {{ "openExtensionManuallyPart2" | i18n }} +

    +
    + + +

    + {{ "reopenLinkOnDesktop" | i18n }} +

    +
    +
    diff --git a/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt.component.spec.ts b/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt.component.spec.ts new file mode 100644 index 00000000000..0bea6c186eb --- /dev/null +++ b/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt.component.spec.ts @@ -0,0 +1,151 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { By } from "@angular/platform-browser"; +import { BehaviorSubject } from "rxjs"; + +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; + +import { + BrowserExtensionPromptService, + BrowserPromptState, +} from "../../services/browser-extension-prompt.service"; + +import { BrowserExtensionPromptComponent } from "./browser-extension-prompt.component"; + +describe("BrowserExtensionPromptComponent", () => { + let fixture: ComponentFixture; + let component: BrowserExtensionPromptComponent; + const start = jest.fn(); + const pageState$ = new BehaviorSubject(BrowserPromptState.Loading); + const setAttribute = jest.fn(); + const getAttribute = jest.fn().mockReturnValue("width=1010"); + + beforeEach(async () => { + start.mockClear(); + setAttribute.mockClear(); + getAttribute.mockClear(); + + // Store original querySelector + const originalQuerySelector = document.querySelector.bind(document); + + // Mock querySelector while preserving the document context + jest.spyOn(document, "querySelector").mockImplementation(function (selector) { + if (selector === 'meta[name="viewport"]') { + return { setAttribute, getAttribute } as unknown as HTMLMetaElement; + } + return originalQuerySelector.call(document, selector); + }); + + await TestBed.configureTestingModule({ + providers: [ + { + provide: BrowserExtensionPromptService, + useValue: { start, pageState$ }, + }, + { + provide: I18nService, + useValue: { t: (key: string) => key }, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(BrowserExtensionPromptComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it("calls start on initialization", () => { + expect(start).toHaveBeenCalledTimes(1); + }); + + describe("loading state", () => { + beforeEach(() => { + pageState$.next(BrowserPromptState.Loading); + fixture.detectChanges(); + }); + + it("shows loading text", () => { + const element = fixture.nativeElement; + expect(element.textContent.trim()).toBe("openingExtension"); + }); + }); + + describe("error state", () => { + beforeEach(() => { + pageState$.next(BrowserPromptState.Error); + fixture.detectChanges(); + }); + + it("shows error text", () => { + const errorText = fixture.debugElement.query(By.css("p")).nativeElement; + expect(errorText.textContent.trim()).toBe("openingExtensionError"); + }); + }); + + describe("success state", () => { + beforeEach(() => { + pageState$.next(BrowserPromptState.Success); + fixture.detectChanges(); + }); + + it("shows success message", () => { + const successText = fixture.debugElement.query(By.css("p")).nativeElement; + expect(successText.textContent.trim()).toBe("openedExtensionViewAtRiskPasswords"); + }); + }); + + describe("mobile state", () => { + beforeEach(() => { + pageState$.next(BrowserPromptState.MobileBrowser); + fixture.detectChanges(); + }); + + it("shows mobile message", () => { + const mobileText = fixture.debugElement.query(By.css("p")).nativeElement; + expect(mobileText.textContent.trim()).toBe("reopenLinkOnDesktop"); + }); + + it("sets min-width on the body", () => { + expect(document.body.style.minWidth).toBe("auto"); + }); + + it("stores viewport content", () => { + expect(getAttribute).toHaveBeenCalledWith("content"); + expect(component["viewportContent"]).toBe("width=1010"); + }); + + it("sets viewport meta tag to be mobile friendly", () => { + expect(setAttribute).toHaveBeenCalledWith("content", "width=device-width, initial-scale=1.0"); + }); + + describe("on destroy", () => { + beforeEach(() => { + fixture.destroy(); + }); + + it("resets body min-width", () => { + expect(document.body.style.minWidth).toBe(""); + }); + + it("resets viewport meta tag", () => { + expect(setAttribute).toHaveBeenCalledWith("content", "width=1010"); + }); + }); + }); + + describe("manual error state", () => { + beforeEach(() => { + pageState$.next(BrowserPromptState.ManualOpen); + fixture.detectChanges(); + }); + + it("shows manual open error message", () => { + const manualText = fixture.debugElement.query(By.css("p")).nativeElement; + expect(manualText.textContent.trim()).toContain("openExtensionManuallyPart1"); + expect(manualText.textContent.trim()).toContain("openExtensionManuallyPart2"); + }); + }); +}); diff --git a/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt.component.ts b/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt.component.ts new file mode 100644 index 00000000000..4d3a5fa07dd --- /dev/null +++ b/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt.component.ts @@ -0,0 +1,66 @@ +import { CommonModule, DOCUMENT } from "@angular/common"; +import { Component, Inject, OnDestroy, OnInit } from "@angular/core"; + +import { ButtonComponent, IconModule } from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; +import { VaultIcons } from "@bitwarden/vault"; + +import { + BrowserExtensionPromptService, + BrowserPromptState, +} from "../../services/browser-extension-prompt.service"; + +@Component({ + selector: "vault-browser-extension-prompt", + templateUrl: "./browser-extension-prompt.component.html", + standalone: true, + imports: [CommonModule, I18nPipe, ButtonComponent, IconModule], +}) +export class BrowserExtensionPromptComponent implements OnInit, OnDestroy { + /** Current state of the prompt page */ + protected pageState$ = this.browserExtensionPromptService.pageState$; + + /** All available page states */ + protected BrowserPromptState = BrowserPromptState; + + protected BitwardenIcon = VaultIcons.BitwardenIcon; + + /** Content of the meta[name="viewport"] element */ + private viewportContent: string | null = null; + + constructor( + private browserExtensionPromptService: BrowserExtensionPromptService, + @Inject(DOCUMENT) private document: Document, + ) {} + + ngOnInit(): void { + this.browserExtensionPromptService.start(); + + // It is not be uncommon for users to hit this page from a mobile device. + // There are global styles and the viewport meta tag that set a min-width + // for the page which cause it to render poorly. Remove them here. + // https://github.com/bitwarden/clients/blob/main/apps/web/src/scss/base.scss#L6 + this.document.body.style.minWidth = "auto"; + + const viewportMeta = this.document.querySelector('meta[name="viewport"]'); + + // Save the current viewport content to reset it when the component is destroyed + this.viewportContent = viewportMeta?.getAttribute("content") ?? null; + viewportMeta?.setAttribute("content", "width=device-width, initial-scale=1.0"); + } + + ngOnDestroy(): void { + // Reset the body min-width when the component is destroyed + this.document.body.style.minWidth = ""; + + if (this.viewportContent !== null) { + this.document + .querySelector('meta[name="viewport"]') + ?.setAttribute("content", this.viewportContent); + } + } + + openExtension(): void { + this.browserExtensionPromptService.openExtension(); + } +} diff --git a/apps/web/src/app/vault/components/collection-dialog/collection-dialog.module.ts b/apps/web/src/app/vault/components/collection-dialog/collection-dialog.module.ts deleted file mode 100644 index a8f284fb485..00000000000 --- a/apps/web/src/app/vault/components/collection-dialog/collection-dialog.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { NgModule } from "@angular/core"; - -import { SelectModule } from "@bitwarden/components"; - -import { AccessSelectorModule } from "../../../admin-console/organizations/shared/components/access-selector/access-selector.module"; -import { SharedModule } from "../../../shared"; - -import { CollectionDialogComponent } from "./collection-dialog.component"; -@NgModule({ - imports: [SharedModule, AccessSelectorModule, SelectModule], - declarations: [CollectionDialogComponent], - exports: [CollectionDialogComponent], -}) -export class CollectionDialogModule {} diff --git a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.html b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.html index 56acc421de4..45df670570d 100644 --- a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.html +++ b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.html @@ -36,7 +36,10 @@
    - + + - +
    diff --git a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts index 59638aad653..b2adeecbf69 100644 --- a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts +++ b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts @@ -4,7 +4,7 @@ import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; import { Component, ElementRef, Inject, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { Router } from "@angular/router"; -import { firstValueFrom, Observable, Subject, switchMap } from "rxjs"; +import { firstValueFrom, Subject, switchMap } from "rxjs"; import { map } from "rxjs/operators"; import { CollectionView } from "@bitwarden/admin-console/common"; @@ -12,8 +12,11 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { EventType } from "@bitwarden/common/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 { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -35,12 +38,17 @@ import { ToastService, } from "@bitwarden/components"; import { + ChangeLoginPasswordService, CipherAttachmentsComponent, + CipherFormComponent, CipherFormConfig, CipherFormGenerationService, CipherFormModule, CipherViewComponent, DecryptionFailureDialogComponent, + DefaultChangeLoginPasswordService, + DefaultTaskService, + TaskService, } from "@bitwarden/vault"; import { SharedModule } from "../../../shared/shared.module"; @@ -49,6 +57,8 @@ import { AttachmentDialogResult, AttachmentsV2Component, } from "../../individual-vault/attachments-v2.component"; +import { RoutedVaultFilterService } from "../../individual-vault/vault-filter/services/routed-vault-filter.service"; +import { RoutedVaultFilterModel } from "../../individual-vault/vault-filter/shared/models/routed-vault-filter.model"; import { WebCipherFormGenerationService } from "../../services/web-cipher-form-generation.service"; import { WebVaultPremiumUpgradePromptService } from "../../services/web-premium-upgrade-prompt.service"; import { WebViewPasswordHistoryService } from "../../services/web-view-password-history.service"; @@ -82,6 +92,11 @@ export interface VaultItemDialogParams { * If true, the dialog is being opened from the admin console. */ isAdminConsoleAction?: boolean; + + /** + * Function to restore a cipher from the trash. + */ + restore?: (c: CipherView) => Promise; } export enum VaultItemDialogResult { @@ -99,6 +114,11 @@ export enum VaultItemDialogResult { * The dialog was closed to navigate the user the premium upgrade page. */ PremiumUpgrade = "premiumUpgrade", + + /** + * A cipher was restored + */ + Restored = "restored", } @Component({ @@ -121,6 +141,9 @@ export enum VaultItemDialogResult { { provide: PremiumUpgradePromptService, useClass: WebVaultPremiumUpgradePromptService }, { provide: ViewPasswordHistoryService, useClass: WebViewPasswordHistoryService }, { provide: CipherFormGenerationService, useClass: WebCipherFormGenerationService }, + RoutedVaultFilterService, + { provide: TaskService, useClass: DefaultTaskService }, + { provide: ChangeLoginPasswordService, useClass: DefaultChangeLoginPasswordService }, ], }) export class VaultItemDialogComponent implements OnInit, OnDestroy { @@ -131,6 +154,8 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { @ViewChild("dialogContent") protected dialogContent: ElementRef; + @ViewChild(CipherFormComponent) cipherFormComponent!: CipherFormComponent; + /** * Tracks if the cipher was ever modified while the dialog was open. Used to ensure the dialog emits the correct result * in case of closing with the X button or ESC key. @@ -191,6 +216,31 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { ), ); + protected get isTrashFilter() { + return this.filter?.type === "trash"; + } + + protected get showCancel() { + return !this.isTrashFilter && !this.showCipherView; + } + + protected get showClose() { + return this.isTrashFilter && !this.showRestore; + } + + /** + * Determines if the user may restore the item. + * A user may restore items if they have delete permissions and the item is in the trash. + */ + protected async canUserRestore() { + if (await firstValueFrom(this.limitItemDeletion$)) { + return this.isTrashFilter && this.cipher?.isDeleted && this.cipher?.permissions.restore; + } + return this.isTrashFilter && this.cipher?.isDeleted && this.canDelete; + } + + protected showRestore: boolean; + protected get loadingForm() { return this.loadForm && !this.formReady; } @@ -199,8 +249,8 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { return this.params.disableForm; } - protected get canDelete() { - return this.cipher?.edit ?? false; + protected get showEdit() { + return this.showCipherView && !this.isTrashFilter && !this.showRestore; } protected get showDelete() { @@ -228,7 +278,11 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { protected formConfig: CipherFormConfig = this.params.formConfig; - protected canDeleteCipher$: Observable; + protected filter: RoutedVaultFilterModel; + + protected canDelete = false; + + protected limitItemDeletion$ = this.configService.getFeatureFlag$(FeatureFlag.LimitItemDeletion); constructor( @Inject(DIALOG_DATA) protected params: VaultItemDialogParams, @@ -246,6 +300,8 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { private cipherAuthorizationService: CipherAuthorizationService, private apiService: ApiService, private eventCollectionService: EventCollectionService, + private routedVaultFilterService: RoutedVaultFilterService, + private configService: ConfigService, ) { this.updateTitle(); } @@ -269,10 +325,12 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { (o) => o.id === this.cipher.organizationId, ); - this.canDeleteCipher$ = this.cipherAuthorizationService.canDeleteCipher$( - this.cipher, - [this.params.activeCollectionId], - this.params.isAdminConsoleAction, + this.canDelete = await firstValueFrom( + this.cipherAuthorizationService.canDeleteCipher$( + this.cipher, + [this.params.activeCollectionId], + this.params.isAdminConsoleAction, + ), ); await this.eventCollectionService.collect( @@ -283,6 +341,9 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { ); } + this.filter = await firstValueFrom(this.routedVaultFilterService.filter$); + + this.showRestore = await this.canUserRestore(); this.performingInitialLoad = false; } @@ -309,8 +370,8 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { this.formConfig.mode = "edit"; this.formConfig.initialValues = null; } - - let cipher = await this.cipherService.get(cipherView.id); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + let cipher = await this.cipherService.get(cipherView.id, activeUserId); // When the form config is used within the Admin Console, retrieve the cipher from the admin endpoint (if not found in local state) if (this.formConfig.isAdminConsole && (cipher == null || this.formConfig.admin)) { @@ -320,6 +381,9 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { const cipherData = new CipherData(cipherResponse); cipher = new Cipher(cipherData); + + // Update organizationUseTotp from server response + this.cipher.organizationUseTotp = cipher.organizationUseTotp; } // Store the updated cipher so any following edits use the most up to date cipher @@ -336,6 +400,11 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { this._formReadySubject.next(); } + restore = async () => { + await this.params.restore?.(this.cipher); + this.dialogRef.close(VaultItemDialogResult.Restored); + }; + delete = async () => { if (!this.cipher) { return; @@ -394,6 +463,25 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { result.action === AttachmentDialogResult.Removed || result.action === AttachmentDialogResult.Uploaded ) { + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + const updatedCipher = await this.cipherService.get( + this.formConfig.originalCipher?.id, + activeUserId, + ); + + const updatedCipherView = await updatedCipher.decrypt( + await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), + ); + + this.cipherFormComponent.patchCipher((currentCipher) => { + currentCipher.attachments = updatedCipherView.attachments; + currentCipher.revisionDate = updatedCipherView.revisionDate; + + return currentCipher; + }); + this._cipherModified = true; } }; @@ -420,9 +508,7 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { if (config.originalCipher == null) { return; } - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); return await config.originalCipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(config.originalCipher, activeUserId), ); @@ -504,10 +590,14 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { // - The cipher is unassigned const asAdmin = this.organization?.canEditAllCiphers || cipherIsUnassigned; + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + if (this.cipher.isDeleted) { - await this.cipherService.deleteWithServer(this.cipher.id, asAdmin); + await this.cipherService.deleteWithServer(this.cipher.id, activeUserId, asAdmin); } else { - await this.cipherService.softDeleteWithServer(this.cipher.id, asAdmin); + await this.cipherService.softDeleteWithServer(this.cipher.id, activeUserId, asAdmin); } } diff --git a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html index 7e59853851c..ef6a347f7d3 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html +++ b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html @@ -22,7 +22,7 @@ [routerLink]="[]" [queryParams]="{ itemId: cipher.id, action: clickAction }" queryParamsHandling="merge" - [replaceUrl]="extensionRefreshEnabled" + [replaceUrl]="true" title="{{ 'editItemWithName' | i18n: cipher.name }}" type="button" appStopProp @@ -86,7 +86,12 @@ appStopProp > - - - - -
    diff --git a/apps/web/src/app/vault/components/web-generator-dialog/web-generator-dialog.component.spec.ts b/apps/web/src/app/vault/components/web-generator-dialog/web-generator-dialog.component.spec.ts index 26c2d9e3890..11a97a1f343 100644 --- a/apps/web/src/app/vault/components/web-generator-dialog/web-generator-dialog.component.spec.ts +++ b/apps/web/src/app/vault/components/web-generator-dialog/web-generator-dialog.component.spec.ts @@ -1,19 +1,19 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; -import { Component, EventEmitter, Input, Output } from "@angular/core"; +import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog"; +import { Component, Input, Output, EventEmitter } from "@angular/core"; import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { By } from "@angular/platform-browser"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; import { mock, MockProxy } from "jest-mock-extended"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { AlgorithmInfo } from "@bitwarden/generator-core"; import { CipherFormGeneratorComponent } from "@bitwarden/vault"; import { WebVaultGeneratorDialogAction, WebVaultGeneratorDialogComponent, - WebVaultGeneratorDialogParams, + WebVaultGeneratorDialogResult, } from "./web-generator-dialog.component"; @Component({ @@ -22,42 +22,29 @@ import { standalone: true, }) class MockCipherFormGenerator { - @Input() type: "password" | "username"; + @Input() type: "password" | "username" = "password"; + @Output() algorithmSelected: EventEmitter = new EventEmitter(); + @Input() uri?: string; @Output() valueGenerated = new EventEmitter(); } describe("WebVaultGeneratorDialogComponent", () => { let component: WebVaultGeneratorDialogComponent; let fixture: ComponentFixture; - - let dialogRef: MockProxy>; + let dialogRef: MockProxy>; let mockI18nService: MockProxy; beforeEach(async () => { - dialogRef = mock>(); + dialogRef = mock>(); mockI18nService = mock(); - const mockDialogData: WebVaultGeneratorDialogParams = { type: "password" }; - await TestBed.configureTestingModule({ imports: [NoopAnimationsModule, WebVaultGeneratorDialogComponent], providers: [ - { - provide: DialogRef, - useValue: dialogRef, - }, - { - provide: DIALOG_DATA, - useValue: mockDialogData, - }, - { - provide: I18nService, - useValue: mockI18nService, - }, - { - provide: PlatformUtilsService, - useValue: mock(), - }, + { provide: DialogRef, useValue: dialogRef }, + { provide: DIALOG_DATA, useValue: { type: "password" } }, + { provide: I18nService, useValue: mockI18nService }, + { provide: PlatformUtilsService, useValue: mock() }, ], }) .overrideComponent(WebVaultGeneratorDialogComponent, { @@ -71,38 +58,73 @@ describe("WebVaultGeneratorDialogComponent", () => { fixture.detectChanges(); }); - it("initializes without errors", () => { - fixture.detectChanges(); + it("should create", () => { expect(component).toBeTruthy(); }); - it("closes the dialog with 'canceled' result when close is called", () => { - const closeSpy = jest.spyOn(dialogRef, "close"); + it("should enable button when value and algorithm are selected", () => { + const generator = fixture.debugElement.query( + By.css("vault-cipher-form-generator"), + ).componentInstance; - (component as any).close(); + generator.algorithmSelected.emit({ useGeneratedValue: "Use Password" } as any); + generator.valueGenerated.emit("test-password"); + fixture.detectChanges(); - expect(closeSpy).toHaveBeenCalledWith({ + const button = fixture.debugElement.query( + By.css("[data-testid='select-button']"), + ).nativeElement; + expect(button.disabled).toBe(false); + }); + + it("should disable the button if no value has been generated", () => { + const generator = fixture.debugElement.query( + By.css("vault-cipher-form-generator"), + ).componentInstance; + + generator.algorithmSelected.emit({ useGeneratedValue: "Use Password" } as any); + fixture.detectChanges(); + + const button = fixture.debugElement.query( + By.css("[data-testid='select-button']"), + ).nativeElement; + expect(button.disabled).toBe(true); + }); + + it("should disable the button if no algorithm is selected", () => { + const generator = fixture.debugElement.query( + By.css("vault-cipher-form-generator"), + ).componentInstance; + + generator.valueGenerated.emit("test-password"); + fixture.detectChanges(); + + const button = fixture.debugElement.query( + By.css("[data-testid='select-button']"), + ).nativeElement; + expect(button.disabled).toBe(true); + }); + + it("should close with selected value when confirmed", () => { + const generator = fixture.debugElement.query( + By.css("vault-cipher-form-generator"), + ).componentInstance; + generator.algorithmSelected.emit({ useGeneratedValue: "Use Password" } as any); + generator.valueGenerated.emit("test-password"); + fixture.detectChanges(); + + fixture.debugElement.query(By.css("[data-testid='select-button']")).nativeElement.click(); + + expect(dialogRef.close).toHaveBeenCalledWith({ + action: WebVaultGeneratorDialogAction.Selected, + generatedValue: "test-password", + }); + }); + + it("should close with canceled action when dismissed", () => { + component["close"](); + expect(dialogRef.close).toHaveBeenCalledWith({ action: WebVaultGeneratorDialogAction.Canceled, }); }); - - it("closes the dialog with 'selected' result when selectValue is called", () => { - const closeSpy = jest.spyOn(dialogRef, "close"); - const generatedValue = "generated-value"; - component.onValueGenerated(generatedValue); - - (component as any).selectValue(); - - expect(closeSpy).toHaveBeenCalledWith({ - action: WebVaultGeneratorDialogAction.Selected, - generatedValue: generatedValue, - }); - }); - - it("updates generatedValue when onValueGenerated is called", () => { - const generatedValue = "new-generated-value"; - component.onValueGenerated(generatedValue); - - expect((component as any).generatedValue).toBe(generatedValue); - }); }); diff --git a/apps/web/src/app/vault/components/web-generator-dialog/web-generator-dialog.component.ts b/apps/web/src/app/vault/components/web-generator-dialog/web-generator-dialog.component.ts index d21403dd4e5..b0e5514ce21 100644 --- a/apps/web/src/app/vault/components/web-generator-dialog/web-generator-dialog.component.ts +++ b/apps/web/src/app/vault/components/web-generator-dialog/web-generator-dialog.component.ts @@ -6,10 +6,13 @@ import { Component, Inject } from "@angular/core"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ButtonModule, DialogModule, DialogService } from "@bitwarden/components"; +import { AlgorithmInfo } from "@bitwarden/generator-core"; +import { I18nPipe } from "@bitwarden/ui-common"; import { CipherFormGeneratorComponent } from "@bitwarden/vault"; export interface WebVaultGeneratorDialogParams { type: "password" | "username"; + uri?: string; } export interface WebVaultGeneratorDialogResult { @@ -26,13 +29,11 @@ export enum WebVaultGeneratorDialogAction { selector: "web-vault-generator-dialog", templateUrl: "./web-generator-dialog.component.html", standalone: true, - imports: [CommonModule, CipherFormGeneratorComponent, ButtonModule, DialogModule], + imports: [CommonModule, CipherFormGeneratorComponent, ButtonModule, DialogModule, I18nPipe], }) export class WebVaultGeneratorDialogComponent { - protected title = this.i18nService.t(this.isPassword ? "passwordGenerator" : "usernameGenerator"); - protected selectButtonText = this.i18nService.t( - this.isPassword ? "useThisPassword" : "useThisUsername", - ); + protected titleKey = this.isPassword ? "passwordGenerator" : "usernameGenerator"; + protected buttonLabel: string | undefined; /** * Whether the dialog is generating a password/passphrase. If false, it is generating a username. @@ -48,11 +49,15 @@ export class WebVaultGeneratorDialogComponent { */ protected generatedValue: string = ""; + protected uri: string; + constructor( @Inject(DIALOG_DATA) protected params: WebVaultGeneratorDialogParams, private dialogRef: DialogRef, private i18nService: I18nService, - ) {} + ) { + this.uri = params.uri; + } /** * Close the dialog without selecting a value. @@ -75,6 +80,16 @@ export class WebVaultGeneratorDialogComponent { this.generatedValue = value; } + onAlgorithmSelected = (selected?: AlgorithmInfo) => { + if (selected) { + this.buttonLabel = selected.useGeneratedValue; + } else { + // default to email + this.buttonLabel = this.i18nService.t("useThisEmail"); + } + this.generatedValue = undefined; + }; + /** * Opens the vault generator dialog. */ diff --git a/apps/web/src/app/vault/individual-vault/add-edit-v2.component.spec.ts b/apps/web/src/app/vault/individual-vault/add-edit-v2.component.spec.ts index 1ca9b0de47c..9127a213a45 100644 --- a/apps/web/src/app/vault/individual-vault/add-edit-v2.component.spec.ts +++ b/apps/web/src/app/vault/individual-vault/add-edit-v2.component.spec.ts @@ -49,7 +49,7 @@ describe("AddEditComponentV2", () => { } as Organization; organizationService = mock(); - organizationService.organizations$ = of([mockOrganization]); + organizationService.organizations$.mockReturnValue(of([mockOrganization])); policyService = mock(); policyService.policyAppliesToActiveUser$.mockImplementation((policyType: PolicyType) => diff --git a/apps/web/src/app/vault/individual-vault/add-edit.component.ts b/apps/web/src/app/vault/individual-vault/add-edit.component.ts index 56db7dc88da..d8739465938 100644 --- a/apps/web/src/app/vault/individual-vault/add-edit.component.ts +++ b/apps/web/src/app/vault/individual-vault/add-edit.component.ts @@ -15,7 +15,6 @@ import { isCardExpired } from "@bitwarden/common/autofill/utils"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { ProductTierType } from "@bitwarden/common/billing/enums"; import { EventType } from "@bitwarden/common/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 { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -30,7 +29,7 @@ import { Launchable } from "@bitwarden/common/vault/interfaces/launchable"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { DialogService, ToastService } from "@bitwarden/components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; -import { PasswordRepromptService } from "@bitwarden/vault"; +import { PasswordRepromptService, SshImportPromptService } from "@bitwarden/vault"; @Component({ selector: "app-vault-add-edit", @@ -76,6 +75,7 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On cipherAuthorizationService: CipherAuthorizationService, toastService: ToastService, sdkService: SdkService, + sshImportPromptService: SshImportPromptService, ) { super( cipherService, @@ -98,6 +98,7 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On cipherAuthorizationService, toastService, sdkService, + sshImportPromptService, ); } @@ -105,17 +106,6 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On await super.ngOnInit(); await this.load(); - // https://bitwarden.atlassian.net/browse/PM-10413 - // cannot generate ssh keys so block creation - if ( - this.type === CipherType.SshKey && - this.cipherId == null && - !(await this.configService.getFeatureFlag(FeatureFlag.SSHKeyVaultItem)) - ) { - this.type = CipherType.Login; - this.cipher.type = CipherType.Login; - } - this.viewOnly = !this.cipher.edit && this.editMode; // remove when all the title for all clients are updated to New Item if (this.cloneMode || !this.editMode) { @@ -135,19 +125,18 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On if (this.showTotp()) { await this.totpUpdateCode(); - const interval = this.totpService.getTimeInterval(this.cipher.login.totp); - await this.totpTick(interval); - - this.totpInterval = window.setInterval(async () => { + const totpResponse = await firstValueFrom(this.totpService.getCode$(this.cipher.login.totp)); + if (totpResponse) { + const interval = totpResponse.period; await this.totpTick(interval); - }, 1000); + + this.totpInterval = window.setInterval(async () => { + await this.totpTick(interval); + }, 1000); + } } - const extensionRefreshEnabled = await firstValueFrom( - this.configService.getFeatureFlag$(FeatureFlag.ExtensionRefresh), - ); - - this.cardIsExpired = extensionRefreshEnabled && isCardExpired(this.cipher.card); + this.cardIsExpired = isCardExpired(this.cipher.card); } ngOnDestroy() { @@ -194,11 +183,11 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On } this.platformUtilsService.copyToClipboard(value, { window: window }); - this.platformUtilsService.showToast( - "info", - null, - this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)), - ); + this.toastService.showToast({ + variant: "info", + title: null, + message: this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)), + }); if (this.editMode) { if (typeI18nKey === "password") { @@ -277,7 +266,8 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On return; } - this.totpCode = await this.totpService.getCode(this.cipher.login.totp); + const totpResponse = await firstValueFrom(this.totpService.getCode$(this.cipher.login.totp)); + this.totpCode = totpResponse?.code; if (this.totpCode != null) { if (this.totpCode.length > 4) { const half = Math.floor(this.totpCode.length / 2); diff --git a/apps/web/src/app/vault/individual-vault/attachments-v2.component.ts b/apps/web/src/app/vault/individual-vault/attachments-v2.component.ts index 68bceb55985..81f14d5aa25 100644 --- a/apps/web/src/app/vault/individual-vault/attachments-v2.component.ts +++ b/apps/web/src/app/vault/individual-vault/attachments-v2.component.ts @@ -8,7 +8,7 @@ import { CipherId } from "@bitwarden/common/types/guid"; import { DialogService } from "@bitwarden/components"; import { CipherAttachmentsComponent } from "@bitwarden/vault"; -import { SharedModule } from "../../shared"; +import { SharedModule } from "../../shared/shared.module"; export interface AttachmentsDialogParams { cipherId: CipherId; diff --git a/apps/web/src/app/vault/individual-vault/attachments.component.ts b/apps/web/src/app/vault/individual-vault/attachments.component.ts index a6c25b71fd4..c6079dbe78f 100644 --- a/apps/web/src/app/vault/individual-vault/attachments.component.ts +++ b/apps/web/src/app/vault/individual-vault/attachments.component.ts @@ -4,7 +4,7 @@ import { AttachmentsComponent as BaseAttachmentsComponent } from "@bitwarden/ang import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; diff --git a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts index 913f106004d..86019fe745e 100644 --- a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts +++ b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts @@ -2,15 +2,18 @@ // @ts-strict-ignore import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; import { Component, Inject } from "@angular/core"; +import { firstValueFrom } from "rxjs"; import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherBulkDeleteRequest } from "@bitwarden/common/vault/models/request/cipher-bulk-delete.request"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; export interface BulkDeleteDialogParams { cipherIds?: string[]; @@ -60,6 +63,8 @@ export class BulkDeleteDialogComponent { private i18nService: I18nService, private apiService: ApiService, private collectionService: CollectionService, + private toastService: ToastService, + private accountService: AccountService, ) { this.cipherIds = params.cipherIds ?? []; this.permanent = params.permanent; @@ -95,29 +100,31 @@ export class BulkDeleteDialogComponent { await Promise.all(deletePromises); if (this.cipherIds.length || this.unassignedCiphers.length) { - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t(this.permanent ? "permanentlyDeletedItems" : "deletedItems"), - ); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t(this.permanent ? "permanentlyDeletedItems" : "deletedItems"), + }); } if (this.collections.length) { await this.collectionService.delete(this.collections.map((c) => c.id)); - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("deletedCollections"), - ); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("deletedCollections"), + }); } this.close(BulkDeleteDialogResult.Deleted); }; private async deleteCiphers(): Promise { const asAdmin = this.organization?.canEditAllCiphers; + + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); if (this.permanent) { - await this.cipherService.deleteManyWithServer(this.cipherIds, asAdmin); + await this.cipherService.deleteManyWithServer(this.cipherIds, activeUserId, asAdmin); } else { - await this.cipherService.softDeleteManyWithServer(this.cipherIds, asAdmin); + await this.cipherService.softDeleteManyWithServer(this.cipherIds, activeUserId, asAdmin); } } @@ -134,11 +141,11 @@ export class BulkDeleteDialogComponent { // From org vault if (this.organization) { if (this.collections.some((c) => !c.canDelete(this.organization))) { - this.platformUtilsService.showToast( - "error", - this.i18nService.t("errorOccurred"), - this.i18nService.t("missingPermissions"), - ); + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: this.i18nService.t("missingPermissions"), + }); return; } return await this.apiService.deleteManyCollections( @@ -151,11 +158,11 @@ export class BulkDeleteDialogComponent { for (const organization of this.organizations) { const orgCollections = this.collections.filter((o) => o.organizationId === organization.id); if (orgCollections.some((c) => !c.canDelete(organization))) { - this.platformUtilsService.showToast( - "error", - this.i18nService.t("errorOccurred"), - this.i18nService.t("missingPermissions"), - ); + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: this.i18nService.t("missingPermissions"), + }); return; } const orgCollectionIds = orgCollections.map((c) => c.id); diff --git a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-dialogs.module.ts b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-dialogs.module.ts index 848f5629dae..47378eeac63 100644 --- a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-dialogs.module.ts +++ b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-dialogs.module.ts @@ -4,11 +4,10 @@ import { SharedModule } from "../../../shared"; import { BulkDeleteDialogComponent } from "./bulk-delete-dialog/bulk-delete-dialog.component"; import { BulkMoveDialogComponent } from "./bulk-move-dialog/bulk-move-dialog.component"; -import { BulkShareDialogComponent } from "./bulk-share-dialog/bulk-share-dialog.component"; @NgModule({ imports: [SharedModule], - declarations: [BulkDeleteDialogComponent, BulkMoveDialogComponent, BulkShareDialogComponent], - exports: [BulkDeleteDialogComponent, BulkMoveDialogComponent, BulkShareDialogComponent], + declarations: [BulkDeleteDialogComponent, BulkMoveDialogComponent], + exports: [BulkDeleteDialogComponent, BulkMoveDialogComponent], }) export class BulkDialogsModule {} diff --git a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts index b7f99fb7b44..27e441e946e 100644 --- a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts +++ b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts @@ -3,15 +3,16 @@ import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; import { Component, Inject, OnInit } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; -import { firstValueFrom, map, Observable } from "rxjs"; +import { firstValueFrom, Observable } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; export interface BulkMoveDialogParams { cipherIds?: string[]; @@ -48,8 +49,6 @@ export class BulkMoveDialogComponent implements OnInit { }); folders$: Observable; - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - constructor( @Inject(DIALOG_DATA) params: BulkMoveDialogParams, private dialogRef: DialogRef, @@ -58,13 +57,14 @@ export class BulkMoveDialogComponent implements OnInit { private i18nService: I18nService, private folderService: FolderService, private formBuilder: FormBuilder, + private toastService: ToastService, private accountService: AccountService, ) { this.cipherIds = params.cipherIds ?? []; } async ngOnInit() { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); this.folders$ = this.folderService.folderViews$(activeUserId); this.formGroup.patchValue({ folderId: (await firstValueFrom(this.folders$))[0].id, @@ -80,8 +80,17 @@ export class BulkMoveDialogComponent implements OnInit { return; } - await this.cipherService.moveManyWithServer(this.cipherIds, this.formGroup.value.folderId); - this.platformUtilsService.showToast("success", null, this.i18nService.t("movedItems")); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + await this.cipherService.moveManyWithServer( + this.cipherIds, + this.formGroup.value.folderId, + activeUserId, + ); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("movedItems"), + }); this.close(BulkMoveDialogResult.Moved); }; diff --git a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-share-dialog/bulk-share-dialog.component.html b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-share-dialog/bulk-share-dialog.component.html deleted file mode 100644 index 0f2f5f1fcde..00000000000 --- a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-share-dialog/bulk-share-dialog.component.html +++ /dev/null @@ -1,73 +0,0 @@ - - - {{ "moveSelectedToOrg" | i18n }} - - -

    {{ "moveManyToOrgDesc" | i18n }}

    -

    - {{ - "moveSelectedItemsCountDesc" - | i18n: this.ciphers.length : shareableCiphers.length : nonShareableCount - }} -

    - - {{ "organization" | i18n }} - - - -
    - -
    - - -
    -
    -
    - {{ "noCollectionsInList" | i18n }} -
    - - - - - - - -
    - - - {{ c.name }} -
    -
    - - - - -
    diff --git a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-share-dialog/bulk-share-dialog.component.ts b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-share-dialog/bulk-share-dialog.component.ts deleted file mode 100644 index 62798c21bca..00000000000 --- a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-share-dialog/bulk-share-dialog.component.ts +++ /dev/null @@ -1,159 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; -import { Component, Inject, OnDestroy, OnInit } from "@angular/core"; -import { firstValueFrom, map } from "rxjs"; - -import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { Checkable, isChecked } from "@bitwarden/common/types/checkable"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { DialogService } from "@bitwarden/components"; - -export interface BulkShareDialogParams { - ciphers: CipherView[]; - organizationId?: string; -} - -export enum BulkShareDialogResult { - Shared = "shared", - Canceled = "canceled", -} - -/** - * Strongly typed helper to open a BulkShareDialog - * @param dialogService Instance of the dialog service that will be used to open the dialog - * @param config Configuration for the dialog - */ -export const openBulkShareDialog = ( - dialogService: DialogService, - config: DialogConfig, -) => { - return dialogService.open( - BulkShareDialogComponent, - config, - ); -}; - -@Component({ - templateUrl: "bulk-share-dialog.component.html", -}) -export class BulkShareDialogComponent implements OnInit, OnDestroy { - ciphers: CipherView[] = []; - organizationId: string; - - nonShareableCount = 0; - collections: Checkable[] = []; - organizations: Organization[] = []; - shareableCiphers: CipherView[] = []; - - private writeableCollections: CollectionView[] = []; - - constructor( - @Inject(DIALOG_DATA) params: BulkShareDialogParams, - private dialogRef: DialogRef, - private cipherService: CipherService, - private platformUtilsService: PlatformUtilsService, - private i18nService: I18nService, - private collectionService: CollectionService, - private organizationService: OrganizationService, - private logService: LogService, - private accountService: AccountService, - ) { - this.ciphers = params.ciphers ?? []; - this.organizationId = params.organizationId; - } - - async ngOnInit() { - this.shareableCiphers = this.ciphers.filter( - (c) => !c.hasOldAttachments && c.organizationId == null, - ); - this.nonShareableCount = this.ciphers.length - this.shareableCiphers.length; - const allCollections = await this.collectionService.getAllDecrypted(); - this.writeableCollections = allCollections.filter((c) => !c.readOnly); - this.organizations = await this.organizationService.getAll(); - if (this.organizationId == null && this.organizations.length > 0) { - this.organizationId = this.organizations[0].id; - } - this.filterCollections(); - } - - ngOnDestroy() { - this.selectAll(false); - } - - filterCollections() { - this.selectAll(false); - if (this.organizationId == null || this.writeableCollections.length === 0) { - this.collections = []; - } else { - this.collections = this.writeableCollections.filter( - (c) => c.organizationId === this.organizationId, - ); - } - } - - submit = async () => { - const checkedCollectionIds = this.collections.filter(isChecked).map((c) => c.id); - try { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - await this.cipherService.shareManyWithServer( - this.shareableCiphers, - this.organizationId, - checkedCollectionIds, - activeUserId, - ); - const orgName = - this.organizations.find((o) => o.id === this.organizationId)?.name ?? - this.i18nService.t("organization"); - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("movedItemsToOrg", orgName), - ); - this.close(BulkShareDialogResult.Shared); - } catch (e) { - this.logService.error(e); - } - }; - - check(c: Checkable, select?: boolean) { - c.checked = select == null ? !c.checked : select; - } - - selectAll(select: boolean) { - const collections = select ? this.collections : this.writeableCollections; - collections.forEach((c) => this.check(c, select)); - } - - get canSave() { - if ( - this.shareableCiphers != null && - this.shareableCiphers.length > 0 && - this.collections != null - ) { - for (let i = 0; i < this.collections.length; i++) { - if (this.collections[i].checked) { - return true; - } - } - } - return false; - } - - protected cancel() { - this.close(BulkShareDialogResult.Canceled); - } - - private close(result: BulkShareDialogResult) { - this.dialogRef.close(result); - } -} diff --git a/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts b/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts index 88af1ef601b..272cfb130b9 100644 --- a/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts +++ b/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts @@ -45,7 +45,10 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent { logService, dialogService, formBuilder, + toastService, ); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions params?.folderId ? (this.folderId = params.folderId) : null; } @@ -87,11 +90,11 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent { const folder = await this.folderService.encrypt(this.folder, userKey); this.formPromise = this.folderApiService.save(folder, activeAccountId); await this.formPromise; - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t(this.editMode ? "editedFolder" : "addedFolder"), - ); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t(this.editMode ? "editedFolder" : "addedFolder"), + }); this.onSavedFolder.emit(this.folder); this.dialogRef.close(FolderAddEditDialogResult.Saved); } catch (e) { diff --git a/apps/web/src/app/vault/individual-vault/password-history.component.html b/apps/web/src/app/vault/individual-vault/password-history.component.html index 4eaca8f736e..ccbe20c5192 100644 --- a/apps/web/src/app/vault/individual-vault/password-history.component.html +++ b/apps/web/src/app/vault/individual-vault/password-history.component.html @@ -1,4 +1,4 @@ - + {{ "passwordHistory" | i18n }} diff --git a/apps/web/src/app/vault/individual-vault/pipes/pipes.module.ts b/apps/web/src/app/vault/individual-vault/pipes/pipes.module.ts index c33136b8262..16b95717718 100644 --- a/apps/web/src/app/vault/individual-vault/pipes/pipes.module.ts +++ b/apps/web/src/app/vault/individual-vault/pipes/pipes.module.ts @@ -1,11 +1,10 @@ import { NgModule } from "@angular/core"; -import { GetCollectionNameFromIdPipe } from "./get-collection-name.pipe"; import { GetGroupNameFromIdPipe } from "./get-group-name.pipe"; import { GetOrgNameFromIdPipe } from "./get-organization-name.pipe"; @NgModule({ - declarations: [GetOrgNameFromIdPipe, GetCollectionNameFromIdPipe, GetGroupNameFromIdPipe], - exports: [GetOrgNameFromIdPipe, GetCollectionNameFromIdPipe, GetGroupNameFromIdPipe], + declarations: [GetOrgNameFromIdPipe, GetGroupNameFromIdPipe], + exports: [GetOrgNameFromIdPipe, GetGroupNameFromIdPipe], }) export class PipesModule {} diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.spec.ts b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.spec.ts index 88fae02275f..4ce65b9f771 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.spec.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.spec.ts @@ -6,7 +6,11 @@ import { UserDecryptionOptionsServiceAbstraction, } from "@bitwarden/auth/common"; import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; +import { DeviceResponse } from "@bitwarden/common/auth/abstractions/devices/responses/device.response"; +import { DeviceView } from "@bitwarden/common/auth/abstractions/devices/views/device.view"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; +import { DeviceType } from "@bitwarden/common/enums"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { StateProvider } from "@bitwarden/common/platform/state"; @@ -36,6 +40,7 @@ describe("VaultBannersService", () => { const accounts$ = new BehaviorSubject>({ [userId]: { email: "test@bitwarden.com", emailVerified: true, name: "name" } as AccountInfo, }); + const devices$ = new BehaviorSubject([]); beforeEach(() => { lastSync$.next(new Date("2024-05-14")); @@ -79,6 +84,10 @@ describe("VaultBannersService", () => { userDecryptionOptionsById$: () => userDecryptionOptions$, }, }, + { + provide: DevicesServiceAbstraction, + useValue: { getDevices$: () => devices$ }, + }, ], }); }); @@ -274,4 +283,63 @@ describe("VaultBannersService", () => { expect(await service.shouldShowVerifyEmailBanner(userId)).toBe(false); }); }); + + describe("PendingAuthRequest", () => { + const now = new Date(); + let deviceResponse: DeviceResponse; + + beforeEach(() => { + deviceResponse = new DeviceResponse({ + Id: "device1", + UserId: userId, + Name: "Test Device", + Identifier: "test-device", + Type: DeviceType.Android, + CreationDate: now.toISOString(), + RevisionDate: now.toISOString(), + IsTrusted: false, + }); + // Reset devices list, single user state, and active user state before each test + devices$.next([]); + fakeStateProvider.singleUser.states.clear(); + fakeStateProvider.activeUser.states.clear(); + }); + + it("shows pending auth request banner when there is a pending request", async () => { + deviceResponse.devicePendingAuthRequest = { + id: "123", + creationDate: now.toISOString(), + }; + devices$.next([new DeviceView(deviceResponse)]); + + service = TestBed.inject(VaultBannersService); + + expect(await service.shouldShowPendingAuthRequestBanner(userId)).toBe(true); + }); + + it("does not show pending auth request banner when there are no pending requests", async () => { + deviceResponse.devicePendingAuthRequest = null; + devices$.next([new DeviceView(deviceResponse)]); + + service = TestBed.inject(VaultBannersService); + + expect(await service.shouldShowPendingAuthRequestBanner(userId)).toBe(false); + }); + + it("dismisses pending auth request banner", async () => { + deviceResponse.devicePendingAuthRequest = { + id: "123", + creationDate: now.toISOString(), + }; + devices$.next([new DeviceView(deviceResponse)]); + + service = TestBed.inject(VaultBannersService); + + expect(await service.shouldShowPendingAuthRequestBanner(userId)).toBe(true); + + await service.dismissBanner(userId, VisibleVaultBanner.PendingAuthRequest); + + expect(await service.shouldShowPendingAuthRequestBanner(userId)).toBe(false); + }); + }); }); diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts index 390b95fa2b1..1fa5ae1ad8b 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts @@ -3,6 +3,7 @@ import { Observable, combineLatest, firstValueFrom, map, filter, mergeMap, take import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { @@ -21,6 +22,7 @@ export enum VisibleVaultBanner { OutdatedBrowser = "outdated-browser", Premium = "premium", VerifyEmail = "verify-email", + PendingAuthRequest = "pending-auth-request", } type PremiumBannerReprompt = { @@ -60,8 +62,23 @@ export class VaultBannersService { private kdfConfigService: KdfConfigService, private syncService: SyncService, private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, + private devicesService: DevicesServiceAbstraction, ) {} + /** Returns true when the pending auth request banner should be shown */ + async shouldShowPendingAuthRequestBanner(userId: UserId): Promise { + const devices = await firstValueFrom(this.devicesService.getDevices$()); + const hasPendingRequest = devices.some( + (device) => device.response?.devicePendingAuthRequest != null, + ); + + const alreadyDismissed = (await this.getBannerDismissedState(userId)).includes( + VisibleVaultBanner.PendingAuthRequest, + ); + + return hasPendingRequest && !alreadyDismissed; + } + shouldShowPremiumBanner$(userId: UserId): Observable { const premiumBannerState = this.premiumBannerState(userId); const premiumSources$ = combineLatest([ @@ -204,6 +221,7 @@ export class VaultBannersService { private async isLowKdfIteration(userId: UserId) { const kdfConfig = await firstValueFrom(this.kdfConfigService.getKdfConfig$(userId)); return ( + kdfConfig != null && kdfConfig.kdfType === KdfType.PBKDF2_SHA256 && kdfConfig.iterations < PBKDF2KdfConfig.ITERATIONS.defaultValue ); diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.html b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.html index 29909e26716..e97f1579f57 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.html +++ b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.html @@ -50,6 +50,20 @@ + + {{ "youHaveAPendingLoginRequest" | i18n }} + + {{ "reviewLoginRequest" | i18n }} + + + { let component: VaultBannersComponent; let fixture: ComponentFixture; + let messageSubject: Subject<{ command: string }>; const premiumBanner$ = new BehaviorSubject(false); + const pendingAuthRequest$ = new BehaviorSubject(false); const mockUserId = Utils.newGuid() as UserId; const bannerService = mock({ - shouldShowPremiumBanner$: jest.fn((userId$: Observable) => premiumBanner$), + shouldShowPremiumBanner$: jest.fn((userId: UserId) => premiumBanner$), shouldShowUpdateBrowserBanner: jest.fn(), shouldShowVerifyEmailBanner: jest.fn(), shouldShowLowKDFBanner: jest.fn(), + shouldShowPendingAuthRequestBanner: jest.fn((userId: UserId) => + Promise.resolve(pendingAuthRequest$.value), + ), dismissBanner: jest.fn(), }); const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); beforeEach(async () => { + messageSubject = new Subject<{ command: string }>(); bannerService.shouldShowUpdateBrowserBanner.mockResolvedValue(false); bannerService.shouldShowVerifyEmailBanner.mockResolvedValue(false); bannerService.shouldShowLowKDFBanner.mockResolvedValue(false); - + pendingAuthRequest$.next(false); premiumBanner$.next(false); await TestBed.configureTestingModule({ @@ -74,6 +81,12 @@ describe("VaultBannersComponent", () => { provide: AccountService, useValue: accountService, }, + { + provide: MessageListener, + useValue: mock({ + allMessages$: messageSubject.asObservable(), + }), + }, ], }) .overrideProvider(VaultBannersService, { useValue: bannerService }) @@ -153,5 +166,76 @@ describe("VaultBannersComponent", () => { }); }); }); + + describe("PendingAuthRequest", () => { + beforeEach(async () => { + pendingAuthRequest$.next(true); + await component.ngOnInit(); + fixture.detectChanges(); + }); + + it("shows pending auth request banner", async () => { + expect(component.visibleBanners).toEqual([VisibleVaultBanner.PendingAuthRequest]); + }); + + it("dismisses pending auth request banner", async () => { + const dismissButton = fixture.debugElement.nativeElement.querySelector( + 'button[biticonbutton="bwi-close"]', + ); + + pendingAuthRequest$.next(false); + dismissButton.click(); + fixture.detectChanges(); + + expect(bannerService.dismissBanner).toHaveBeenCalledWith( + mockUserId, + VisibleVaultBanner.PendingAuthRequest, + ); + + // Wait for async operations to complete + await fixture.whenStable(); + await component.determineVisibleBanners(); + fixture.detectChanges(); + + expect(component.visibleBanners).toEqual([]); + }); + }); + }); + + describe("message listener", () => { + beforeEach(async () => { + bannerService.shouldShowPendingAuthRequestBanner.mockResolvedValue(true); + messageSubject.next({ command: "openLoginApproval" }); + fixture.detectChanges(); + }); + + it("adds pending auth request banner when openLoginApproval message is received", async () => { + await component.ngOnInit(); + messageSubject.next({ command: "openLoginApproval" }); + fixture.detectChanges(); + + expect(component.visibleBanners).toContain(VisibleVaultBanner.PendingAuthRequest); + }); + + it("does not add duplicate pending auth request banner", async () => { + await component.ngOnInit(); + messageSubject.next({ command: "openLoginApproval" }); + messageSubject.next({ command: "openLoginApproval" }); + fixture.detectChanges(); + + const bannerCount = component.visibleBanners.filter( + (b) => b === VisibleVaultBanner.PendingAuthRequest, + ).length; + expect(bannerCount).toBe(1); + }); + + it("ignores other message types", async () => { + bannerService.shouldShowPendingAuthRequestBanner.mockResolvedValue(false); + await component.ngOnInit(); + messageSubject.next({ command: "someOtherCommand" }); + fixture.detectChanges(); + + expect(component.visibleBanners).not.toContain(VisibleVaultBanner.PendingAuthRequest); + }); }); }); diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts index 161b2ccb7ef..5f5fc1e218d 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts @@ -1,15 +1,16 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Component, Input, OnInit } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { Router } from "@angular/router"; -import { firstValueFrom, map, Observable, switchMap } from "rxjs"; +import { firstValueFrom, map, Observable, switchMap, filter } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { MessageListener } from "@bitwarden/common/platform/messaging"; +import { UserId } from "@bitwarden/common/types/guid"; import { BannerModule } from "@bitwarden/components"; import { VerifyEmailComponent } from "../../../auth/settings/verify-email.component"; -import { FreeTrial } from "../../../core/types/free-trial"; +import { FreeTrial } from "../../../billing/types/free-trial"; import { SharedModule } from "../../../shared"; import { VaultBannersService, VisibleVaultBanner } from "./services/vault-banners.service"; @@ -34,10 +35,24 @@ export class VaultBannersComponent implements OnInit { private router: Router, private i18nService: I18nService, private accountService: AccountService, + private messageListener: MessageListener, ) { this.premiumBannerVisible$ = this.activeUserId$.pipe( + filter((userId): userId is UserId => userId != null), switchMap((userId) => this.vaultBannerService.shouldShowPremiumBanner$(userId)), ); + + // Listen for auth request messages and show banner immediately + this.messageListener.allMessages$ + .pipe( + filter((message: { command: string }) => message.command === "openLoginApproval"), + takeUntilDestroyed(), + ) + .subscribe(() => { + if (!this.visibleBanners.includes(VisibleVaultBanner.PendingAuthRequest)) { + this.visibleBanners = [...this.visibleBanners, VisibleVaultBanner.PendingAuthRequest]; + } + }); } async ngOnInit(): Promise { @@ -46,8 +61,10 @@ export class VaultBannersComponent implements OnInit { async dismissBanner(banner: VisibleVaultBanner): Promise { const activeUserId = await firstValueFrom(this.activeUserId$); + if (!activeUserId) { + return; + } await this.vaultBannerService.dismissBanner(activeUserId, banner); - await this.determineVisibleBanners(); } @@ -63,19 +80,26 @@ export class VaultBannersComponent implements OnInit { } /** Determine which banners should be present */ - private async determineVisibleBanners(): Promise { + async determineVisibleBanners(): Promise { const activeUserId = await firstValueFrom(this.activeUserId$); + if (!activeUserId) { + return; + } + const showBrowserOutdated = await this.vaultBannerService.shouldShowUpdateBrowserBanner(activeUserId); const showVerifyEmail = await this.vaultBannerService.shouldShowVerifyEmailBanner(activeUserId); const showLowKdf = await this.vaultBannerService.shouldShowLowKDFBanner(activeUserId); + const showPendingAuthRequest = + await this.vaultBannerService.shouldShowPendingAuthRequestBanner(activeUserId); this.visibleBanners = [ showBrowserOutdated ? VisibleVaultBanner.OutdatedBrowser : null, showVerifyEmail ? VisibleVaultBanner.VerifyEmail : null, showLowKdf ? VisibleVaultBanner.KDFSettings : null, - ].filter(Boolean); // remove all falsy values, i.e. null + showPendingAuthRequest ? VisibleVaultBanner.PendingAuthRequest : null, + ].filter((banner): banner is VisibleVaultBanner => banner !== null); // ensures the filtered array contains only VisibleVaultBanner values } freeTrialMessage(organization: FreeTrial) { diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts b/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts index 6788471dd04..37e3aca6cd5 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts @@ -1,7 +1,16 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Component, Inject, OnDestroy, OnInit } from "@angular/core"; -import { combineLatest, map, Observable, of, Subject, switchMap, takeUntil } from "rxjs"; +import { + combineLatest, + firstValueFrom, + map, + Observable, + of, + Subject, + switchMap, + takeUntil, +} from "rxjs"; import { OrganizationUserApiService, @@ -15,7 +24,9 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { 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"; @@ -60,6 +71,7 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy { private toastService: ToastService, private configService: ConfigService, private organizationService: OrganizationService, + private accountService: AccountService, ) {} async ngOnInit() { @@ -67,16 +79,19 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy { map((policies) => policies.filter((policy) => policy.type === PolicyType.ResetPassword)), ); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const managingOrg$ = this.configService .getFeatureFlag$(FeatureFlag.AccountDeprovisioning) .pipe( switchMap((isAccountDeprovisioningEnabled) => isAccountDeprovisioningEnabled - ? this.organizationService.organizations$.pipe( - map((organizations) => - organizations.find((o) => o.userIsManagedByOrganization === true), - ), - ) + ? this.organizationService + .organizations$(userId) + .pipe( + map((organizations) => + organizations.find((o) => o.userIsManagedByOrganization === true), + ), + ) : of(null), ), ); @@ -146,7 +161,11 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy { return this.syncService.fullSync(true); }); await this.actionPromise; - this.platformUtilsService.showToast("success", null, "Unlinked SSO"); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("unlinkedSso"), + }); } catch (e) { this.logService.error(e); } @@ -166,7 +185,11 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy { try { this.actionPromise = this.organizationApiService.leave(org.id); await this.actionPromise; - this.platformUtilsService.showToast("success", null, this.i18nService.t("leftOrganization")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("leftOrganization"), + }); } catch (e) { this.logService.error(e); } @@ -199,11 +222,11 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy { ); try { await this.actionPromise; - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("withdrawPasswordResetSuccess"), - ); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("withdrawPasswordResetSuccess"), + }); await this.syncService.fullSync(true); } catch (e) { this.logService.error(e); diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.html b/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.html index 8380f96840e..3ac5e708e8c 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.html +++ b/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.html @@ -1,12 +1,15 @@ -
    -
    +
    +
    -
    +
    {{ "filters" | i18n }}
    -
    +
    ; this.isLoaded = true; + + // Without refactoring the entire component, we need to manually update the organization filter whenever the policies update + merge( + this.policyService.get$(PolicyType.SingleOrg), + this.policyService.get$(PolicyType.PersonalOwnership), + ) + .pipe( + switchMap(() => this.addOrganizationFilter()), + takeUntil(this.destroy$), + ) + .subscribe((orgFilters) => { + this.filters.organizationFilter = orgFilters; + }); } ngOnDestroy() { @@ -122,11 +135,11 @@ export class VaultFilterComponent implements OnInit, OnDestroy { applyOrganizationFilter = async (orgNode: TreeNode): Promise => { if (!orgNode?.node.enabled) { - this.platformUtilsService.showToast( - "error", - null, - this.i18nService.t("disabledOrganizationFilterError"), - ); + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("disabledOrganizationFilterError"), + }); const metadata = await this.billingApiService.getOrganizationBillingMetadata(orgNode.node.id); await this.trialFlowService.handleUnpaidSubscriptionDialog(orgNode.node, metadata); } @@ -229,20 +242,17 @@ export class VaultFilterComponent implements OnInit, OnDestroy { }, { id: "note", - name: this.i18nService.t("typeSecureNote"), + name: this.i18nService.t("note"), type: CipherType.SecureNote, icon: "bwi-sticky-note", }, - ]; - - if (await this.configService.getFeatureFlag(FeatureFlag.SSHKeyVaultItem)) { - allTypeFilters.push({ + { id: "sshKey", name: this.i18nService.t("typeSshKey"), type: CipherType.SshKey, icon: "bwi-key", - }); - } + }, + ]; const typeFilterSection: VaultFilterSection = { data$: this.vaultFilterService.buildTypeTree( @@ -267,7 +277,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy { }, action: this.applyFolderFilter, edit: { - text: "editFolder", + filterName: this.i18nService.t("folder"), action: this.editFolder, }, }; diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/services/abstractions/vault-filter.service.ts b/apps/web/src/app/vault/individual-vault/vault-filter/services/abstractions/vault-filter.service.ts index b4f52180e52..b8494c8aa54 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/services/abstractions/vault-filter.service.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/services/abstractions/vault-filter.service.ts @@ -4,8 +4,8 @@ import { Observable } from "rxjs"; import { CollectionAdminView, CollectionView } from "@bitwarden/admin-console/common"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { FolderView } from "@bitwarden/common/src/vault/models/view/folder.view"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; +import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { CipherTypeFilter, diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.spec.ts b/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.spec.ts index 47003d51cae..15f5e1cd876 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.spec.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.spec.ts @@ -62,7 +62,7 @@ describe("vault filter service", () => { personalOwnershipPolicy = new ReplaySubject(1); singleOrgPolicy = new ReplaySubject(1); - organizationService.memberOrganizations$ = organizations; + organizationService.memberOrganizations$.mockReturnValue(organizations); folderService.folderViews$.mockReturnValue(folderViews); collectionService.decryptedCollections$ = collectionViews; policyService.policyAppliesToActiveUser$ @@ -71,7 +71,7 @@ describe("vault filter service", () => { policyService.policyAppliesToActiveUser$ .calledWith(PolicyType.SingleOrg) .mockReturnValue(singleOrgPolicy); - cipherService.cipherViews$ = cipherViews; + cipherService.cipherViews$.mockReturnValue(cipherViews); vaultFilterService = new VaultFilterService( organizationService, diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts b/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts index 97b44132e60..67c369fa0f2 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts @@ -5,6 +5,7 @@ import { BehaviorSubject, combineLatest, combineLatestWith, + filter, firstValueFrom, map, Observable, @@ -48,8 +49,12 @@ const NestingDelimiter = "/"; export class VaultFilterService implements VaultFilterServiceAbstraction { private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + memberOrganizations$ = this.activeUserId$.pipe( + switchMap((id) => this.organizationService.memberOrganizations$(id)), + ); + organizationTree$: Observable> = combineLatest([ - this.organizationService.memberOrganizations$, + this.memberOrganizations$, this.policyService.policyAppliesToActiveUser$(PolicyType.SingleOrg), this.policyService.policyAppliesToActiveUser$(PolicyType.PersonalOwnership), ]).pipe( @@ -64,14 +69,16 @@ export class VaultFilterService implements VaultFilterServiceAbstraction { switchMap((userId) => combineLatest([ this.folderService.folderViews$(userId), - this.cipherService.cipherViews$, + this.cipherService.cipherViews$(userId), this._organizationFilter, ]), ), + filter(([folders, ciphers, org]) => !!ciphers), // ciphers may be null, meaning decryption is in progress. Ignore this emission switchMap(([folders, ciphers, org]) => { return this.filterFolders(folders, ciphers, org); }), ); + folderTree$: Observable> = this.filteredFolders$.pipe( map((folders) => this.buildFolderTree(folders)), ); @@ -270,6 +277,7 @@ export class VaultFilterService implements VaultFilterServiceAbstraction { folderCopy.id = f.id; folderCopy.revisionDate = f.revisionDate; folderCopy.icon = "bwi-folder"; + folderCopy.fullName = f.name; // save full folder name before separating it into parts const parts = f.name != null ? f.name.replace(/^\/+|\/+$/g, "").split(NestingDelimiter) : []; ServiceUtils.nestedTraverse(nodes, 0, parts, folderCopy, null, NestingDelimiter); }); diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/shared/components/vault-filter-section.component.html b/apps/web/src/app/vault/individual-vault/vault-filter/shared/components/vault-filter-section.component.html index bb52dd4feb7..1485c1f5343 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/shared/components/vault-filter-section.component.html +++ b/apps/web/src/app/vault/individual-vault/vault-filter/shared/components/vault-filter-section.component.html @@ -89,7 +89,7 @@ *ngIf="editInfo && f.node.id" class="edit-button" (click)="onEdit(f)" - appA11yTitle="{{ editInfo.text | i18n }}" + appA11yTitle="{{ 'editWithName' | i18n: editInfo.filterName : f.node.name }}" > @@ -121,7 +121,7 @@ >
  • - +  {{ addInfo.text | i18n }} diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/shared/models/vault-filter-section.type.ts b/apps/web/src/app/vault/individual-vault/vault-filter/shared/models/vault-filter-section.type.ts index f89a72b5d2b..0f949e17146 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/shared/models/vault-filter-section.type.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/shared/models/vault-filter-section.type.ts @@ -31,7 +31,7 @@ export type VaultFilterSection = { }; action: (filterNode: TreeNode) => Promise; edit?: { - text: string; + filterName: string; action: (filter: VaultFilterType) => void; }; add?: { diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/shared/models/vault-filter.type.ts b/apps/web/src/app/vault/individual-vault/vault-filter/shared/models/vault-filter.type.ts index 0cd385bd19d..9259dd08114 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/shared/models/vault-filter.type.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/shared/models/vault-filter.type.ts @@ -1,8 +1,8 @@ import { CollectionAdminView } from "@bitwarden/admin-console/common"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { FolderView } from "@bitwarden/common/src/vault/models/view/folder.view"; import { CipherType } from "@bitwarden/common/vault/enums"; import { ITreeNodeObject } from "@bitwarden/common/vault/models/domain/tree-node"; +import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; export type CipherStatus = "all" | "favorites" | "trash" | CipherType; @@ -10,5 +10,13 @@ export type CipherTypeFilter = ITreeNodeObject & { type: CipherStatus; icon: str export type CollectionFilter = CollectionAdminView & { icon: string; }; -export type FolderFilter = FolderView & { icon: string }; +export type FolderFilter = FolderView & { + icon: string; + /** + * Full folder name. + * + * Used for when the folder `name` property is be separated into parts. + */ + fullName?: string; +}; export type OrganizationFilter = Organization & { icon: string; hideOptions?: boolean }; diff --git a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.html b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.html index 8ac6138db7c..538d511571c 100644 --- a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.html +++ b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.html @@ -59,7 +59,7 @@ @@ -69,88 +69,48 @@
    - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + +
    diff --git a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts index 75b39a2ca40..e9122864447 100644 --- a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts @@ -9,21 +9,32 @@ import { OnInit, Output, } from "@angular/core"; +import { Router } from "@angular/router"; import { firstValueFrom } from "rxjs"; -import { Unassigned, CollectionView } from "@bitwarden/admin-console/common"; +import { + Unassigned, + CollectionView, + CollectionAdminService, +} from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { 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 { CipherType } from "@bitwarden/common/vault/enums"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; -import { BreadcrumbsModule, MenuModule } from "@bitwarden/components"; +import { + BreadcrumbsModule, + DialogService, + MenuModule, + SimpleDialogOptions, +} from "@bitwarden/components"; +import { CollectionDialogTabType } from "../../../admin-console/organizations/shared/components/collection-dialog"; import { HeaderModule } from "../../../layouts/header/header.module"; import { SharedModule } from "../../../shared"; -import { CollectionDialogTabType } from "../../components/collection-dialog"; import { PipesModule } from "../pipes/pipes.module"; import { All, @@ -50,7 +61,6 @@ export class VaultHeaderComponent implements OnInit { protected All = All; protected CollectionDialogTabType = CollectionDialogTabType; protected CipherType = CipherType; - protected extensionRefreshEnabled: boolean; /** * Boolean to determine the loading state of the header. @@ -87,14 +97,13 @@ export class VaultHeaderComponent implements OnInit { constructor( private i18nService: I18nService, + private collectionAdminService: CollectionAdminService, + private dialogService: DialogService, + private router: Router, private configService: ConfigService, ) {} - async ngOnInit() { - this.extensionRefreshEnabled = await firstValueFrom( - this.configService.getFeatureFlag$(FeatureFlag.ExtensionRefresh), - ); - } + async ngOnInit() {} /** * The id of the organization that is currently being filtered on. @@ -210,6 +219,57 @@ export class VaultHeaderComponent implements OnInit { } async addCollection(): Promise { + const isBreadcrumbEventLogsEnabled = await firstValueFrom( + this.configService.getFeatureFlag$(FeatureFlag.PM12276_BreadcrumbEventLogs), + ); + + if (isBreadcrumbEventLogsEnabled) { + const organization = this.organizations?.find( + (org) => org.productTierType === ProductTierType.Free, + ); + + if (this.organizations?.length == 1 && !!organization) { + const collections = await this.collectionAdminService.getAll(organization.id); + if (collections.length === organization.maxCollections) { + await this.showFreeOrgUpgradeDialog(organization); + return; + } + } + } + this.onAddCollection.emit(); } + + private async showFreeOrgUpgradeDialog(organization: Organization): Promise { + const orgUpgradeSimpleDialogOpts: SimpleDialogOptions = { + title: this.i18nService.t("upgradeOrganization"), + content: this.i18nService.t( + organization.canEditSubscription + ? "freeOrgMaxCollectionReachedManageBilling" + : "freeOrgMaxCollectionReachedNoManageBilling", + organization.maxCollections, + ), + type: "primary", + }; + + if (organization.canEditSubscription) { + orgUpgradeSimpleDialogOpts.acceptButtonText = this.i18nService.t("upgrade"); + } else { + orgUpgradeSimpleDialogOpts.acceptButtonText = this.i18nService.t("ok"); + orgUpgradeSimpleDialogOpts.cancelButtonText = null; // hide secondary btn + } + + const simpleDialog = this.dialogService.openSimpleDialogRef(orgUpgradeSimpleDialogOpts); + const result: boolean | undefined = await firstValueFrom(simpleDialog.closed); + + if (!result) { + return; + } + + if (organization.canEditSubscription) { + await this.router.navigate(["/organizations", organization.id, "billing", "subscription"], { + queryParams: { upgrade: true }, + }); + } + } } diff --git a/apps/web/src/app/vault/individual-vault/vault-onboarding/services/abstraction/vault-onboarding.service.ts b/apps/web/src/app/vault/individual-vault/vault-onboarding/services/abstraction/vault-onboarding.service.ts index 379c97672e7..7d3ff32c0f8 100644 --- a/apps/web/src/app/vault/individual-vault/vault-onboarding/services/abstraction/vault-onboarding.service.ts +++ b/apps/web/src/app/vault/individual-vault/vault-onboarding/services/abstraction/vault-onboarding.service.ts @@ -1,10 +1,10 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Observable } from "rxjs"; +import { UserId } from "@bitwarden/common/types/guid"; + import { VaultOnboardingTasks } from "../vault-onboarding.service"; export abstract class VaultOnboardingService { - vaultOnboardingState$: Observable; - abstract setVaultOnboardingTasks(newState: VaultOnboardingTasks): Promise; + abstract setVaultOnboardingTasks(userId: UserId, newState: VaultOnboardingTasks): Promise; + abstract vaultOnboardingState$(userId: UserId): Observable; } diff --git a/apps/web/src/app/vault/individual-vault/vault-onboarding/services/vault-onboarding.service.ts b/apps/web/src/app/vault/individual-vault/vault-onboarding/services/vault-onboarding.service.ts index 95cb568a840..e6f8b815484 100644 --- a/apps/web/src/app/vault/individual-vault/vault-onboarding/services/vault-onboarding.service.ts +++ b/apps/web/src/app/vault/individual-vault/vault-onboarding/services/vault-onboarding.service.ts @@ -1,14 +1,13 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Injectable } from "@angular/core"; import { Observable } from "rxjs"; import { - ActiveUserState, + SingleUserState, StateProvider, UserKeyDefinition, VAULT_ONBOARDING, } from "@bitwarden/common/platform/state"; +import { UserId } from "@bitwarden/common/types/guid"; import { VaultOnboardingService as VaultOnboardingServiceAbstraction } from "./abstraction/vault-onboarding.service"; @@ -26,20 +25,20 @@ const VAULT_ONBOARDING_KEY = new UserKeyDefinition( clearOn: [], // do not clear tutorials }, ); - @Injectable() export class VaultOnboardingService implements VaultOnboardingServiceAbstraction { - private vaultOnboardingState: ActiveUserState; - vaultOnboardingState$: Observable; + constructor(private stateProvider: StateProvider) {} - constructor(private stateProvider: StateProvider) { - this.vaultOnboardingState = this.stateProvider.getActive(VAULT_ONBOARDING_KEY); - this.vaultOnboardingState$ = this.vaultOnboardingState.state$; + private vaultOnboardingState(userId: UserId): SingleUserState { + return this.stateProvider.getUser(userId, VAULT_ONBOARDING_KEY); } - async setVaultOnboardingTasks(newState: VaultOnboardingTasks): Promise { - await this.vaultOnboardingState.update(() => { - return { ...newState }; - }); + vaultOnboardingState$(userId: UserId): Observable { + return this.vaultOnboardingState(userId).state$; + } + + async setVaultOnboardingTasks(userId: UserId, newState: VaultOnboardingTasks): Promise { + const state = this.vaultOnboardingState(userId); + await state.update(() => ({ ...newState })); } } diff --git a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.html b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.html index b9647e3237d..aa56daac071 100644 --- a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.html +++ b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.html @@ -22,12 +22,7 @@

    {{ "onboardingImportDataDetailsPartOne" | i18n }} {{ "onboardingImportDataDetailsPartTwoNoOrgs" | i18n }} diff --git a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.spec.ts b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.spec.ts index e017bc9b35d..1a767bc8964 100644 --- a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.spec.ts +++ b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.spec.ts @@ -7,12 +7,16 @@ import { Subject, of } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; import { StateProvider } from "@bitwarden/common/platform/state"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherType } from "@bitwarden/common/vault/enums/cipher-type"; -import { VaultOnboardingMessages } from "@bitwarden/common/vault/enums/vault-onboarding.enum"; +import { VaultMessages } from "@bitwarden/common/vault/enums/vault-messages.enum"; import { VaultOnboardingService as VaultOnboardingServiceAbstraction } from "./services/abstraction/vault-onboarding.service"; import { VaultOnboardingComponent } from "./vault-onboarding.component"; @@ -25,10 +29,11 @@ describe("VaultOnboardingComponent", () => { let mockPolicyService: MockProxy; let mockI18nService: MockProxy; let mockVaultOnboardingService: MockProxy; - let mockStateProvider: Partial; let setInstallExtLinkSpy: any; let individualVaultPolicyCheckSpy: any; let mockConfigService: MockProxy; + const mockAccountService: FakeAccountService = mockAccountServiceWith(Utils.newGuid() as UserId); + let mockStateProvider: Partial; beforeEach(() => { mockPolicyService = mock(); @@ -38,6 +43,7 @@ describe("VaultOnboardingComponent", () => { getProfile: jest.fn(), }; mockVaultOnboardingService = mock(); + mockConfigService = mock(); mockStateProvider = { getActive: jest.fn().mockReturnValue( of({ @@ -47,7 +53,6 @@ describe("VaultOnboardingComponent", () => { }), ), }; - mockConfigService = mock(); // eslint-disable-next-line @typescript-eslint/no-floating-promises TestBed.configureTestingModule({ @@ -59,8 +64,9 @@ describe("VaultOnboardingComponent", () => { { provide: VaultOnboardingServiceAbstraction, useValue: mockVaultOnboardingService }, { provide: I18nService, useValue: mockI18nService }, { provide: ApiService, useValue: mockApiService }, - { provide: StateProvider, useValue: mockStateProvider }, { provide: ConfigService, useValue: mockConfigService }, + { provide: AccountService, useValue: mockAccountService }, + { provide: StateProvider, useValue: mockStateProvider }, ], }).compileComponents(); fixture = TestBed.createComponent(VaultOnboardingComponent); @@ -71,11 +77,15 @@ describe("VaultOnboardingComponent", () => { .mockReturnValue(undefined); jest.spyOn(component, "checkCreationDate").mockReturnValue(null); jest.spyOn(window, "postMessage").mockImplementation(jest.fn()); - (component as any).vaultOnboardingService.vaultOnboardingState$ = of({ - createAccount: true, - importData: false, - installExtension: false, - }); + (component as any).vaultOnboardingService.vaultOnboardingState$ = jest + .fn() + .mockImplementation(() => { + return of({ + createAccount: true, + importData: false, + installExtension: false, + }); + }); }); it("should create", () => { @@ -148,7 +158,7 @@ describe("VaultOnboardingComponent", () => { it("should call getMessages when showOnboarding is true", () => { const messageEventSubject = new Subject(); const messageEvent = new MessageEvent("message", { - data: VaultOnboardingMessages.HasBwInstalled, + data: VaultMessages.HasBwInstalled, }); const getMessagesSpy = jest.spyOn(component, "getMessages"); @@ -158,7 +168,7 @@ describe("VaultOnboardingComponent", () => { void fixture.whenStable().then(() => { expect(window.postMessage).toHaveBeenCalledWith({ - command: VaultOnboardingMessages.checkBwInstalled, + command: VaultMessages.checkBwInstalled, }); expect(getMessagesSpy).toHaveBeenCalled(); }); @@ -169,13 +179,16 @@ describe("VaultOnboardingComponent", () => { .spyOn((component as any).vaultOnboardingService, "setVaultOnboardingTasks") .mockReturnValue(Promise.resolve()); - (component as any).vaultOnboardingService.vaultOnboardingState$ = of({ - createAccount: true, - importData: false, - installExtension: false, - }); - - const eventData = { data: { command: VaultOnboardingMessages.HasBwInstalled } }; + (component as any).vaultOnboardingService.vaultOnboardingState$ = jest + .fn() + .mockImplementation(() => { + return of({ + createAccount: true, + importData: false, + installExtension: false, + }); + }); + const eventData = { data: { command: VaultMessages.HasBwInstalled } }; (component as any).showOnboarding = true; diff --git a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.ts b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.ts index 3535d31852e..dc4a014073a 100644 --- a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.ts @@ -18,11 +18,13 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +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 { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherType } from "@bitwarden/common/vault/enums/cipher-type"; -import { VaultOnboardingMessages } from "@bitwarden/common/vault/enums/vault-onboarding.enum"; +import { VaultMessages } from "@bitwarden/common/vault/enums/vault-messages.enum"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { LinkModule } from "@bitwarden/components"; @@ -58,25 +60,25 @@ export class VaultOnboardingComponent implements OnInit, OnChanges, OnDestroy { protected onboardingTasks$: Observable; protected showOnboarding = false; - protected extensionRefreshEnabled = false; + private activeId: UserId; constructor( protected platformUtilsService: PlatformUtilsService, protected policyService: PolicyService, private apiService: ApiService, private vaultOnboardingService: VaultOnboardingServiceAbstraction, private configService: ConfigService, + private accountService: AccountService, ) {} async ngOnInit() { - this.onboardingTasks$ = this.vaultOnboardingService.vaultOnboardingState$; + this.activeId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + this.onboardingTasks$ = this.vaultOnboardingService.vaultOnboardingState$(this.activeId); + await this.setOnboardingTasks(); this.setInstallExtLink(); this.individualVaultPolicyCheck(); this.checkForBrowserExtension(); - this.extensionRefreshEnabled = await this.configService.getFeatureFlag( - FeatureFlag.ExtensionRefresh, - ); } async ngOnChanges(changes: SimpleChanges) { @@ -87,7 +89,7 @@ export class VaultOnboardingComponent implements OnInit, OnChanges, OnDestroy { importData: this.ciphers.length > 0, installExtension: currentTasks.installExtension, }; - await this.vaultOnboardingService.setVaultOnboardingTasks(updatedTasks); + await this.vaultOnboardingService.setVaultOnboardingTasks(this.activeId, updatedTasks); } } @@ -104,19 +106,19 @@ export class VaultOnboardingComponent implements OnInit, OnChanges, OnDestroy { void this.getMessages(event); }); - window.postMessage({ command: VaultOnboardingMessages.checkBwInstalled }); + window.postMessage({ command: VaultMessages.checkBwInstalled }); } } async getMessages(event: any) { - if (event.data.command === VaultOnboardingMessages.HasBwInstalled && this.showOnboarding) { + if (event.data.command === VaultMessages.HasBwInstalled && this.showOnboarding) { const currentTasks = await firstValueFrom(this.onboardingTasks$); const updatedTasks = { createAccount: currentTasks.createAccount, importData: currentTasks.importData, installExtension: true, }; - await this.vaultOnboardingService.setVaultOnboardingTasks(updatedTasks); + await this.vaultOnboardingService.setVaultOnboardingTasks(this.activeId, updatedTasks); } } @@ -159,7 +161,7 @@ export class VaultOnboardingComponent implements OnInit, OnChanges, OnDestroy { private async saveCompletedTasks(vaultTasks: VaultOnboardingTasks) { this.showOnboarding = Object.values(vaultTasks).includes(false); - await this.vaultOnboardingService.setVaultOnboardingTasks(vaultTasks); + await this.vaultOnboardingService.setVaultOnboardingTasks(this.activeId, vaultTasks); } individualVaultPolicyCheck() { diff --git a/apps/web/src/app/vault/individual-vault/vault.component.html b/apps/web/src/app/vault/individual-vault/vault.component.html index 75332dcf72a..1a2a1fdbca6 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.html +++ b/apps/web/src/app/vault/individual-vault/vault.component.html @@ -24,19 +24,13 @@

    -
    -
    -
    - -
    -
    -
    +
    @@ -65,7 +59,7 @@ class="tw-mt-6 tw-flex tw-h-full tw-flex-col tw-items-center tw-justify-start" > 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 8c1d08b269c..d4441c73719 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -1,15 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { DialogRef } from "@angular/cdk/dialog"; -import { - ChangeDetectorRef, - Component, - NgZone, - OnDestroy, - OnInit, - ViewChild, - ViewContainerRef, -} from "@angular/core"; +import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { ActivatedRoute, Params, Router } from "@angular/router"; import { BehaviorSubject, @@ -32,6 +24,7 @@ import { take, takeUntil, tap, + catchError, } from "rxjs/operators"; import { @@ -42,21 +35,22 @@ import { Unassigned, } from "@bitwarden/admin-console/common"; import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe"; -import { ModalService } from "@bitwarden/angular/services/modal.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { 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 { + getOrganizationById, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/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"; @@ -68,12 +62,13 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; -import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { ServiceUtils } from "@bitwarden/common/vault/service-utils"; import { DialogService, Icons, ToastService } from "@bitwarden/components"; import { + AddEditFolderDialogComponent, + AddEditFolderDialogResult, CipherFormConfig, CollectionAssignmentResult, DecryptionFailureDialogComponent, @@ -81,15 +76,17 @@ import { PasswordRepromptService, } from "@bitwarden/vault"; -import { TrialFlowService } from "../../billing/services/trial-flow.service"; -import { FreeTrial } from "../../core/types/free-trial"; -import { SharedModule } from "../../shared/shared.module"; -import { AssignCollectionsWebComponent } from "../components/assign-collections"; +import { getNestedCollectionTree } from "../../admin-console/organizations/collections"; import { CollectionDialogAction, CollectionDialogTabType, openCollectionDialog, -} from "../components/collection-dialog"; +} from "../../admin-console/organizations/shared/components/collection-dialog"; +import { BillingNotificationService } from "../../billing/services/billing-notification.service"; +import { TrialFlowService } from "../../billing/services/trial-flow.service"; +import { FreeTrial } from "../../billing/types/free-trial"; +import { SharedModule } from "../../shared/shared.module"; +import { AssignCollectionsWebComponent } from "../components/assign-collections"; import { VaultItemDialogComponent, VaultItemDialogMode, @@ -98,15 +95,12 @@ import { import { VaultItem } from "../components/vault-items/vault-item"; import { VaultItemEvent } from "../components/vault-items/vault-item-event"; import { VaultItemsModule } from "../components/vault-items/vault-items.module"; -import { getNestedCollectionTree } from "../utils/collection-utils"; -import { AddEditComponent } from "./add-edit.component"; import { AttachmentDialogCloseResult, AttachmentDialogResult, AttachmentsV2Component, } from "./attachments-v2.component"; -import { AttachmentsComponent } from "./attachments.component"; import { BulkDeleteDialogResult, openBulkDeleteDialog, @@ -115,7 +109,6 @@ import { BulkMoveDialogResult, openBulkMoveDialog, } from "./bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component"; -import { FolderAddEditDialogResult, openFolderAddEditDialog } from "./folder-add-edit.component"; import { VaultBannersComponent } from "./vault-banners/vault-banners.component"; import { VaultFilterComponent } from "./vault-filter/components/vault-filter.component"; import { VaultFilterService } from "./vault-filter/services/abstractions/vault-filter.service"; @@ -156,15 +149,6 @@ const SearchTextDebounceInterval = 200; }) export class VaultComponent implements OnInit, OnDestroy { @ViewChild("vaultFilter", { static: true }) filterComponent: VaultFilterComponent; - @ViewChild("attachments", { read: ViewContainerRef, static: true }) - attachmentsModalRef: ViewContainerRef; - @ViewChild("folderAddEdit", { read: ViewContainerRef, static: true }) - folderAddEditModalRef: ViewContainerRef; - @ViewChild("cipherAddEdit", { read: ViewContainerRef, static: true }) - cipherAddEditModalRef: ViewContainerRef; - @ViewChild("share", { read: ViewContainerRef, static: true }) shareModalRef: ViewContainerRef; - @ViewChild("collectionsModal", { read: ViewContainerRef, static: true }) - collectionsModalRef: ViewContainerRef; trashCleanupWarning: string = null; kdfIterations: number; @@ -185,15 +169,17 @@ export class VaultComponent implements OnInit, OnDestroy { protected selectedCollection: TreeNode | undefined; protected canCreateCollections = false; protected currentSearchText$: Observable; - private activeUserId: UserId; private searchText$ = new Subject(); private refresh$ = new BehaviorSubject(null); private destroy$ = new Subject(); - private extensionRefreshEnabled: boolean; private hasSubscription$ = new BehaviorSubject(false); private vaultItemDialogRef?: DialogRef | undefined; - private readonly unpaidSubscriptionDialog$ = this.organizationService.organizations$.pipe( + private organizations$ = this.accountService.activeAccount$ + .pipe(map((a) => a?.id)) + .pipe(switchMap((id) => this.organizationService.organizations$(id))); + + private readonly unpaidSubscriptionDialog$ = this.organizations$.pipe( filter((organizations) => organizations.length === 1), map(([organization]) => organization), switchMap((organization) => @@ -212,9 +198,8 @@ export class VaultComponent implements OnInit, OnDestroy { ), ), ); - protected organizationsPaymentStatus$: Observable = combineLatest([ - this.organizationService.organizations$.pipe( + this.organizations$.pipe( map( (organizations) => organizations?.filter((org) => org.isOwner && org.canViewBillingHistory) ?? [], @@ -230,20 +215,25 @@ export class VaultComponent implements OnInit, OnDestroy { ownerOrgs.map((org) => combineLatest([ this.organizationApiService.getSubscription(org.id), - this.organizationBillingService.getPaymentSource(org.id), + from(this.organizationBillingService.getPaymentSource(org.id)).pipe( + catchError((error: unknown) => { + this.billingNotificationService.handleError(error); + return of(null); + }), + ), ]).pipe( - map(([subscription, paymentSource]) => { - return this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues( + map(([subscription, paymentSource]) => + this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues( org, subscription, paymentSource, - ); - }), + ), + ), ), ), ); }), - map((results) => results.filter((result) => result.shownBanner)), + map((results) => results.filter((result) => result !== null && result.shownBanner)), shareReplay({ refCount: false, bufferSize: 1 }), ); @@ -253,7 +243,6 @@ export class VaultComponent implements OnInit, OnDestroy { private router: Router, private changeDetectorRef: ChangeDetectorRef, private i18nService: I18nService, - private modalService: ModalService, private dialogService: DialogService, private messagingService: MessagingService, private platformUtilsService: PlatformUtilsService, @@ -271,7 +260,6 @@ export class VaultComponent implements OnInit, OnDestroy { private eventCollectionService: EventCollectionService, private searchService: SearchService, private searchPipe: SearchPipe, - private configService: ConfigService, private apiService: ApiService, private billingAccountProfileStateService: BillingAccountProfileStateService, private toastService: ToastService, @@ -281,6 +269,7 @@ export class VaultComponent implements OnInit, OnDestroy { protected billingApiService: BillingApiServiceAbstraction, private trialFlowService: TrialFlowService, private organizationBillingService: OrganizationBillingServiceAbstraction, + private billingNotificationService: BillingNotificationService, ) {} async ngOnInit() { @@ -290,9 +279,7 @@ export class VaultComponent implements OnInit, OnDestroy { : "trashCleanupWarning", ); - this.activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const firstSetup$ = this.route.queryParams.pipe( first(), @@ -356,19 +343,26 @@ export class VaultComponent implements OnInit, OnDestroy { this.currentSearchText$ = this.route.queryParams.pipe(map((queryParams) => queryParams.search)); const ciphers$ = combineLatest([ - this.cipherService.cipherViews$.pipe(filter((c) => c !== null)), + this.cipherService.cipherViews$(activeUserId).pipe(filter((c) => c !== null)), filter$, this.currentSearchText$, ]).pipe( filter(([ciphers, filter]) => ciphers != undefined && filter != undefined), concatMap(async ([ciphers, filter, searchText]) => { - const failedCiphers = await firstValueFrom(this.cipherService.failedToDecryptCiphers$); + const failedCiphers = await firstValueFrom( + this.cipherService.failedToDecryptCiphers$(activeUserId), + ); const filterFunction = createFilterFunction(filter); // Append any failed to decrypt ciphers to the top of the cipher list const allCiphers = [...failedCiphers, ...ciphers]; - if (await this.searchService.isSearchable(searchText)) { - return await this.searchService.searchCiphers(searchText, [filterFunction], allCiphers); + if (await this.searchService.isSearchable(activeUserId, searchText)) { + return await this.searchService.searchCiphers( + activeUserId, + searchText, + [filterFunction], + allCiphers, + ); } return allCiphers.filter(filterFunction); @@ -397,7 +391,7 @@ export class VaultComponent implements OnInit, OnDestroy { collectionsToReturn = selectedCollection?.children.map((c) => c.node) ?? []; } - if (await this.searchService.isSearchable(searchText)) { + if (await this.searchService.isSearchable(activeUserId, searchText)) { collectionsToReturn = this.searchPipe.transform( collectionsToReturn, searchText, @@ -430,15 +424,15 @@ export class VaultComponent implements OnInit, OnDestroy { firstSetup$ .pipe( switchMap(() => this.route.queryParams), - // Only process the queryParams if the dialog is not open (only when extension refresh is enabled) - filter(() => this.vaultItemDialogRef == undefined || !this.extensionRefreshEnabled), + // Only process the queryParams if the dialog is not open + filter(() => this.vaultItemDialogRef == undefined), switchMap(async (params) => { const cipherId = getCipherIdFromParams(params); if (cipherId) { - if (await this.cipherService.get(cipherId)) { + if (await this.cipherService.get(cipherId, activeUserId)) { let action = params.action; - // Default to "view" if extension refresh is enabled - if (action == null && this.extensionRefreshEnabled) { + // Default to "view" + if (action == null) { action = "view"; } @@ -478,7 +472,7 @@ export class VaultComponent implements OnInit, OnDestroy { firstSetup$ .pipe( - switchMap(() => this.cipherService.failedToDecryptCiphers$), + switchMap(() => this.cipherService.failedToDecryptCiphers$(activeUserId)), map((ciphers) => ciphers.filter((c) => !c.isDeleted)), filter((ciphers) => ciphers.length > 0), take(1), @@ -499,9 +493,9 @@ export class VaultComponent implements OnInit, OnDestroy { switchMap(() => combineLatest([ filter$, - this.billingAccountProfileStateService.hasPremiumFromAnySource$(this.activeUserId), + this.billingAccountProfileStateService.hasPremiumFromAnySource$(activeUserId), allCollections$, - this.organizationService.organizations$, + this.organizations$, ciphers$, collections$, selectedCollection$, @@ -537,11 +531,6 @@ export class VaultComponent implements OnInit, OnDestroy { this.refreshing = false; }, ); - - // Check if the extension refresh feature flag is enabled - this.extensionRefreshEnabled = await this.configService.getFeatureFlag( - FeatureFlag.ExtensionRefresh, - ); } ngOnDestroy() { @@ -601,20 +590,24 @@ export class VaultComponent implements OnInit, OnDestroy { await this.filterComponent.filters?.organizationFilter?.action(orgNode); } - addFolder = async (): Promise => { - openFolderAddEditDialog(this.dialogService); + addFolder = (): void => { + AddEditFolderDialogComponent.open(this.dialogService); }; editFolder = async (folder: FolderFilter): Promise => { - const dialog = openFolderAddEditDialog(this.dialogService, { - data: { - folderId: folder.id, + const dialogRef = AddEditFolderDialogComponent.open(this.dialogService, { + editFolderConfig: { + // Shallow copy is used so the original folder object is not modified + folder: { + ...folder, + name: folder.fullName ?? folder.name, // If the filter has a fullName populated, use that as the editable name + }, }, }); - const result = await lastValueFrom(dialog.closed); + const result = await lastValueFrom(dialogRef.closed); - if (result === FolderAddEditDialogResult.Deleted) { + if (result === AddEditFolderDialogResult.Deleted) { await this.router.navigate([], { queryParams: { folderId: null }, queryParamsHandling: "merge", @@ -631,8 +624,7 @@ export class VaultComponent implements OnInit, OnDestroy { * Handles opening the attachments dialog for a cipher. * Runs several checks to ensure that the user has the correct permissions * and then opens the attachments dialog. - * Uses the new AttachmentsV2Component if the extensionRefresh feature flag is enabled. - * + * Uses the new AttachmentsV2Component * @param cipher * @returns */ @@ -646,7 +638,9 @@ export class VaultComponent implements OnInit, OnDestroy { this.messagingService.send("premiumRequired"); return; } else if (cipher.organizationId != null) { - const org = await this.organizationService.get(cipher.organizationId); + const org = await firstValueFrom( + this.organizations$.pipe(getOrganizationById(cipher.organizationId)), + ); if (org != null && (org.maxStorageGb == null || org.maxStorageGb === 0)) { this.messagingService.send("upgradeOrganization", { organizationId: cipher.organizationId, @@ -655,51 +649,20 @@ export class VaultComponent implements OnInit, OnDestroy { } } - const canEditAttachments = await this.canEditAttachments(cipher); + const dialogRef = AttachmentsV2Component.open(this.dialogService, { + cipherId: cipher.id as CipherId, + }); - let madeAttachmentChanges = false; + const result: AttachmentDialogCloseResult = await lastValueFrom(dialogRef.closed); - if (this.extensionRefreshEnabled) { - const dialogRef = AttachmentsV2Component.open(this.dialogService, { - cipherId: cipher.id as CipherId, - }); - - const result: AttachmentDialogCloseResult = await lastValueFrom(dialogRef.closed); - - if ( - result.action === AttachmentDialogResult.Uploaded || - result.action === AttachmentDialogResult.Removed - ) { - this.refresh(); - } - - return; + if ( + result.action === AttachmentDialogResult.Uploaded || + result.action === AttachmentDialogResult.Removed + ) { + this.refresh(); } - const [modal] = await this.modalService.openViewRef( - AttachmentsComponent, - this.attachmentsModalRef, - (comp) => { - comp.cipherId = cipher.id; - comp.viewOnly = !canEditAttachments; - comp.onUploadedAttachment - .pipe(takeUntil(this.destroy$)) - .subscribe(() => (madeAttachmentChanges = true)); - comp.onDeletedAttachment - .pipe(takeUntil(this.destroy$)) - .subscribe(() => (madeAttachmentChanges = true)); - comp.onReuploadedAttachment - .pipe(takeUntil(this.destroy$)) - .subscribe(() => (madeAttachmentChanges = true)); - }, - ); - - modal.onClosed.pipe(takeUntil(this.destroy$)).subscribe(() => { - if (madeAttachmentChanges) { - this.refresh(); - } - madeAttachmentChanges = false; - }); + return; } /** @@ -717,6 +680,7 @@ export class VaultComponent implements OnInit, OnDestroy { mode, formConfig, activeCollectionId, + restore: this.restore, }); const result = await lastValueFrom(this.vaultItemDialogRef.closed); @@ -737,48 +701,13 @@ export class VaultComponent implements OnInit, OnDestroy { await this.go({ cipherId: null, itemId: null, action: null }); } - async addCipher(cipherType?: CipherType) { - const type = cipherType ?? this.activeFilter.cipherType; - - if (this.extensionRefreshEnabled) { - return this.addCipherV2(type); - } - - const component = (await this.editCipher(null)) as AddEditComponent; - component.type = type; - if ( - this.activeFilter.organizationId !== "MyVault" && - this.activeFilter.organizationId != null - ) { - component.organizationId = this.activeFilter.organizationId; - component.collections = ( - await firstValueFrom(this.vaultFilterService.filteredCollections$) - ).filter((c) => !c.readOnly && c.id != null); - } - const selectedColId = this.activeFilter.collectionId; - if (selectedColId !== "AllCollections" && selectedColId != null) { - const selectedCollection = ( - await firstValueFrom(this.vaultFilterService.filteredCollections$) - ).find((c) => c.id === selectedColId); - component.organizationId = selectedCollection?.organizationId; - if (!selectedCollection.readOnly) { - component.collectionIds = [selectedColId]; - } - } - component.folderId = this.activeFilter.folderId; - } - /** * Opens the add cipher dialog. * @param cipherType The type of cipher to add. - * @returns The dialog reference. */ - async addCipherV2(cipherType?: CipherType) { - const cipherFormConfig = await this.cipherFormConfigService.buildConfig( - "add", - null, - cipherType, - ); + async addCipher(cipherType?: CipherType) { + const type = cipherType ?? this.activeFilter.cipherType; + const cipherFormConfig = await this.cipherFormConfigService.buildConfig("add", null, type); const collectionId = this.activeFilter.collectionId !== "AllCollections" && this.activeFilter.collectionId != null ? this.activeFilter.collectionId @@ -809,8 +738,15 @@ export class VaultComponent implements OnInit, OnDestroy { return this.editCipherId(cipher?.id, cloneMode); } + /** + * Edit a cipher using the new VaultItemDialog. + * @param id + * @param cloneMode + * @returns + */ async editCipherId(id: string, cloneMode?: boolean) { - const cipher = await this.cipherService.get(id); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + const cipher = await this.cipherService.get(id, activeUserId); if ( cipher && @@ -822,49 +758,6 @@ export class VaultComponent implements OnInit, OnDestroy { return; } - if (this.extensionRefreshEnabled) { - await this.editCipherIdV2(cipher, cloneMode); - return; - } - - const [modal, childComponent] = await this.modalService.openViewRef( - AddEditComponent, - this.cipherAddEditModalRef, - (comp) => { - comp.cipherId = id; - comp.collectionId = this.selectedCollection?.node.id; - - comp.onSavedCipher.pipe(takeUntil(this.destroy$)).subscribe(() => { - modal.close(); - this.refresh(); - }); - comp.onDeletedCipher.pipe(takeUntil(this.destroy$)).subscribe(() => { - modal.close(); - this.refresh(); - }); - comp.onRestoredCipher.pipe(takeUntil(this.destroy$)).subscribe(() => { - modal.close(); - this.refresh(); - }); - }, - ); - - // 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 - modal.onClosedPromise().then(() => { - void this.go({ cipherId: null, itemId: null, action: null }); - }); - - return childComponent; - } - - /** - * Edit a cipher using the new VaultItemDialog. - * - * @param cipher - * @param cloneMode - */ - private async editCipherIdV2(cipher: Cipher, cloneMode?: boolean) { const cipherFormConfig = await this.cipherFormConfigService.buildConfig( cloneMode ? "clone" : "edit", cipher.id as CipherId, @@ -889,7 +782,8 @@ export class VaultComponent implements OnInit, OnDestroy { * @returns Promise */ async viewCipherById(id: string) { - const cipher = await this.cipherService.get(id); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + const cipher = await this.cipherService.get(id, activeUserId); // If cipher exists (cipher is null when new) and MP reprompt // is on for this cipher, then show password reprompt. if ( @@ -970,7 +864,9 @@ export class VaultComponent implements OnInit, OnDestroy { } async deleteCollection(collection: CollectionView): Promise { - const organization = await this.organizationService.get(collection.organizationId); + const organization = await firstValueFrom( + this.organizations$.pipe(getOrganizationById(collection.organizationId)), + ); if (!collection.canDelete(organization)) { this.showMissingPermissionsError(); return; @@ -1060,14 +956,10 @@ export class VaultComponent implements OnInit, OnDestroy { } } - const component = await this.editCipher(cipher, true); - - if (component != null) { - component.cloneMode = true; - } + await this.editCipher(cipher, true); } - async restore(c: CipherView): Promise { + restore = async (c: CipherView): Promise => { if (!c.isDeleted) { return; } @@ -1082,7 +974,8 @@ export class VaultComponent implements OnInit, OnDestroy { } try { - await this.cipherService.restoreWithServer(c.id); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + await this.cipherService.restoreWithServer(c.id, activeUserId); this.toastService.showToast({ variant: "success", title: null, @@ -1092,7 +985,7 @@ export class VaultComponent implements OnInit, OnDestroy { } catch (e) { this.logService.error(e); } - } + }; async bulkRestore(ciphers: CipherView[]) { if (ciphers.some((c) => !c.edit)) { @@ -1135,9 +1028,7 @@ export class VaultComponent implements OnInit, OnDestroy { .filter((i) => i.cipher === undefined) .map((i) => i.collection.organizationId); const orgs = await firstValueFrom( - this.organizationService.organizations$.pipe( - map((orgs) => orgs.filter((o) => orgIds.includes(o.id))), - ), + this.organizations$.pipe(map((orgs) => orgs.filter((o) => orgIds.includes(o.id)))), ); await this.bulkDelete(ciphers, collections, orgs); } @@ -1166,7 +1057,8 @@ export class VaultComponent implements OnInit, OnDestroy { } try { - await this.deleteCipherWithServer(c.id, permanent); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + await this.deleteCipherWithServer(c.id, activeUserId, permanent); this.toastService.showToast({ variant: "success", @@ -1262,7 +1154,8 @@ export class VaultComponent implements OnInit, OnDestroy { typeI18nKey = "password"; } else if (field === "totp") { aType = "TOTP"; - value = await this.totpService.getCode(cipher.login.totp); + const totpResponse = await firstValueFrom(this.totpService.getCode$(cipher.login.totp)); + value = totpResponse.code; typeI18nKey = "verificationCodeTotp"; } else { this.toastService.showToast({ @@ -1301,10 +1194,10 @@ export class VaultComponent implements OnInit, OnDestroy { } } - protected deleteCipherWithServer(id: string, permanent: boolean) { + protected deleteCipherWithServer(id: string, userId: UserId, permanent: boolean) { return permanent - ? this.cipherService.deleteWithServer(id) - : this.cipherService.softDeleteWithServer(id); + ? this.cipherService.deleteWithServer(id, userId) + : this.cipherService.softDeleteWithServer(id, userId); } protected async repromptCipher(ciphers: CipherView[]) { @@ -1317,15 +1210,6 @@ export class VaultComponent implements OnInit, OnDestroy { this.refresh$.next(); } - private async canEditAttachments(cipher: CipherView) { - if (cipher.organizationId == null || cipher.edit) { - return true; - } - - const organization = this.allOrganizations.find((o) => o.id === cipher.organizationId); - return organization.canEditAllCiphers; - } - private async go(queryParams: any = null) { if (queryParams == null) { queryParams = { diff --git a/apps/web/src/app/vault/individual-vault/vault.module.ts b/apps/web/src/app/vault/individual-vault/vault.module.ts index 712b86a9803..57d3df30df7 100644 --- a/apps/web/src/app/vault/individual-vault/vault.module.ts +++ b/apps/web/src/app/vault/individual-vault/vault.module.ts @@ -1,9 +1,9 @@ import { NgModule } from "@angular/core"; +import { CollectionNameBadgeComponent } from "../../admin-console/organizations/collections"; +import { GroupBadgeModule } from "../../admin-console/organizations/collections/group-badge/group-badge.module"; +import { CollectionDialogComponent } from "../../admin-console/organizations/shared/components/collection-dialog"; import { LooseComponentsModule, SharedModule } from "../../shared"; -import { CollectionDialogModule } from "../components/collection-dialog"; -import { CollectionBadgeModule } from "../org-vault/collection-badge/collection-badge.module"; -import { GroupBadgeModule } from "../org-vault/group-badge/group-badge.module"; import { BulkDialogsModule } from "./bulk-action-dialogs/bulk-dialogs.module"; import { OrganizationBadgeModule } from "./organization-badge/organization-badge.module"; @@ -17,12 +17,12 @@ import { ViewComponent } from "./view.component"; VaultRoutingModule, OrganizationBadgeModule, GroupBadgeModule, - CollectionBadgeModule, + CollectionNameBadgeComponent, PipesModule, SharedModule, LooseComponentsModule, BulkDialogsModule, - CollectionDialogModule, + CollectionDialogComponent, VaultComponent, ViewComponent, ], diff --git a/apps/web/src/app/vault/individual-vault/view.component.spec.ts b/apps/web/src/app/vault/individual-vault/view.component.spec.ts index bde9f564c4a..d1117258124 100644 --- a/apps/web/src/app/vault/individual-vault/view.component.spec.ts +++ b/apps/web/src/app/vault/individual-vault/view.component.spec.ts @@ -1,6 +1,7 @@ import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog"; import { ComponentFixture, TestBed } from "@angular/core/testing"; import { mock } from "jest-mock-extended"; +import { of } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; @@ -11,7 +12,9 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { mockAccountServiceWith } from "@bitwarden/common/spec"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; @@ -19,6 +22,7 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { DialogService, ToastService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; +import { ChangeLoginPasswordService, DefaultTaskService, TaskService } from "@bitwarden/vault"; import { ViewCipherDialogParams, ViewCipherDialogResult, ViewComponent } from "./view.component"; @@ -41,6 +45,8 @@ describe("ViewComponent", () => { const mockParams: ViewCipherDialogParams = { cipher: mockCipher, }; + const userId = Utils.newGuid() as UserId; + const accountService: FakeAccountService = mockAccountServiceWith(userId); beforeEach(async () => { await TestBed.configureTestingModule({ @@ -53,10 +59,14 @@ describe("ViewComponent", () => { { provide: CipherService, useValue: mock() }, { provide: ToastService, useValue: mock() }, { provide: MessagingService, useValue: mock() }, + { + provide: AccountService, + useValue: accountService, + }, { provide: LogService, useValue: mock() }, { provide: OrganizationService, - useValue: { get: jest.fn().mockResolvedValue(mockOrganization) }, + useValue: { organizations$: jest.fn().mockReturnValue(of([mockOrganization])) }, }, { provide: CollectionService, useValue: mock() }, { provide: FolderService, useValue: mock() }, @@ -74,7 +84,33 @@ describe("ViewComponent", () => { }, }, ], - }).compileComponents(); + }) + .overrideComponent(ViewComponent, { + remove: { + providers: [ + { provide: TaskService, useClass: DefaultTaskService }, + { provide: PlatformUtilsService, useValue: PlatformUtilsService }, + { + provide: ChangeLoginPasswordService, + useValue: ChangeLoginPasswordService, + }, + ], + }, + add: { + providers: [ + { + provide: TaskService, + useValue: mock(), + }, + { provide: PlatformUtilsService, useValue: mock() }, + { + provide: ChangeLoginPasswordService, + useValue: mock(), + }, + ], + }, + }) + .compileComponents(); fixture = TestBed.createComponent(ViewComponent); component = fixture.componentInstance; diff --git a/apps/web/src/app/vault/individual-vault/view.component.ts b/apps/web/src/app/vault/individual-vault/view.component.ts index e9ca2bf8f8c..7a2cf3bb2f4 100644 --- a/apps/web/src/app/vault/individual-vault/view.component.ts +++ b/apps/web/src/app/vault/individual-vault/view.component.ts @@ -3,16 +3,19 @@ import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; import { Component, EventEmitter, Inject, OnInit } from "@angular/core"; -import { Observable } from "rxjs"; +import { Observable, firstValueFrom, map } from "rxjs"; import { CollectionView } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { CollectionId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service"; import { CipherType } from "@bitwarden/common/vault/enums/cipher-type"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -23,9 +26,8 @@ import { DialogService, ToastService, } from "@bitwarden/components"; +import { CipherViewComponent, DefaultTaskService, TaskService } from "@bitwarden/vault"; -import { PremiumUpgradePromptService } from "../../../../../../libs/common/src/vault/abstractions/premium-upgrade-prompt.service"; -import { CipherViewComponent } from "../../../../../../libs/vault/src/cipher-view/cipher-view.component"; import { SharedModule } from "../../shared/shared.module"; import { WebVaultPremiumUpgradePromptService } from "../services/web-premium-upgrade-prompt.service"; import { WebViewPasswordHistoryService } from "../services/web-view-password-history.service"; @@ -72,6 +74,7 @@ export interface ViewCipherDialogCloseResult { providers: [ { provide: ViewPasswordHistoryService, useClass: WebViewPasswordHistoryService }, { provide: PremiumUpgradePromptService, useClass: WebVaultPremiumUpgradePromptService }, + { provide: TaskService, useClass: DefaultTaskService }, ], }) export class ViewComponent implements OnInit { @@ -94,6 +97,7 @@ export class ViewComponent implements OnInit { private toastService: ToastService, private organizationService: OrganizationService, private cipherAuthorizationService: CipherAuthorizationService, + private accountService: AccountService, ) {} /** @@ -103,8 +107,17 @@ export class ViewComponent implements OnInit { this.cipher = this.params.cipher; this.collections = this.params.collections; this.cipherTypeString = this.getCipherViewTypeString(); + + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + if (this.cipher.organizationId) { - this.organization = await this.organizationService.get(this.cipher.organizationId); + this.organization = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe( + map((organizations) => organizations.find((o) => o.id === this.cipher.organizationId)), + ), + ); } this.canDeleteCipher$ = this.cipherAuthorizationService.canDeleteCipher$(this.cipher, [ @@ -153,10 +166,11 @@ export class ViewComponent implements OnInit { */ protected async deleteCipher(): Promise { const asAdmin = this.organization?.canEditAllCiphers; + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); if (this.cipher.isDeleted) { - await this.cipherService.deleteWithServer(this.cipher.id, asAdmin); + await this.cipherService.deleteWithServer(this.cipher.id, userId, asAdmin); } else { - await this.cipherService.softDeleteWithServer(this.cipher.id, asAdmin); + await this.cipherService.softDeleteWithServer(this.cipher.id, userId, asAdmin); } } diff --git a/apps/web/src/app/vault/org-vault/add-edit.component.ts b/apps/web/src/app/vault/org-vault/add-edit.component.ts index 75042a63e91..89f3b79f1fb 100644 --- a/apps/web/src/app/vault/org-vault/add-edit.component.ts +++ b/apps/web/src/app/vault/org-vault/add-edit.component.ts @@ -2,6 +2,7 @@ // @ts-strict-ignore import { DatePipe } from "@angular/common"; import { Component } from "@angular/core"; +import { firstValueFrom } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; @@ -10,6 +11,7 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -26,7 +28,7 @@ import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { DialogService, ToastService } from "@bitwarden/components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; -import { PasswordRepromptService } from "@bitwarden/vault"; +import { PasswordRepromptService, SshImportPromptService } from "@bitwarden/vault"; import { AddEditComponent as BaseAddEditComponent } from "../individual-vault/add-edit.component"; @@ -62,6 +64,7 @@ export class AddEditComponent extends BaseAddEditComponent { cipherAuthorizationService: CipherAuthorizationService, toastService: ToastService, sdkService: SdkService, + sshImportPromptService: SshImportPromptService, ) { super( cipherService, @@ -86,6 +89,7 @@ export class AddEditComponent extends BaseAddEditComponent { cipherAuthorizationService, toastService, sdkService, + sshImportPromptService, ); } @@ -98,8 +102,9 @@ export class AddEditComponent extends BaseAddEditComponent { protected async loadCipher() { this.isAdminConsoleAction = true; + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); // Calling loadCipher first to assess if the cipher is unassigned. If null use apiService getCipherAdmin - const firstCipherCheck = await super.loadCipher(); + const firstCipherCheck = await super.loadCipher(activeUserId); if (!this.organization.canEditAllCiphers && firstCipherCheck != null) { return firstCipherCheck; @@ -123,7 +128,8 @@ export class AddEditComponent extends BaseAddEditComponent { protected async deleteCipher() { if (!this.organization.canEditAllCiphers) { - return super.deleteCipher(); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + return super.deleteCipher(activeUserId); } return this.cipher.isDeleted ? this.apiService.deleteCipherAdmin(this.cipherId) diff --git a/apps/web/src/app/vault/org-vault/attachments.component.ts b/apps/web/src/app/vault/org-vault/attachments.component.ts index c8badffb36f..c2ad82bc27a 100644 --- a/apps/web/src/app/vault/org-vault/attachments.component.ts +++ b/apps/web/src/app/vault/org-vault/attachments.component.ts @@ -1,12 +1,14 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Component, OnInit } from "@angular/core"; +import { firstValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; 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 { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -74,7 +76,8 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On protected async loadCipher() { if (!this.organization.canEditAllCiphers) { - return await super.loadCipher(); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + return await super.loadCipher(activeUserId); } const response = await this.apiService.getCipherAdmin(this.cipherId); return new Cipher(new CipherData(response)); @@ -89,9 +92,9 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On ); } - protected deleteCipherAttachment(attachmentId: string) { + protected deleteCipherAttachment(attachmentId: string, userId: UserId) { if (!this.organization.canEditAllCiphers) { - return super.deleteCipherAttachment(attachmentId); + return super.deleteCipherAttachment(attachmentId, userId); } return this.apiService.deleteCipherAttachmentAdmin(this.cipherId, attachmentId); } diff --git a/apps/web/src/app/vault/org-vault/collection-badge/collection-badge.module.ts b/apps/web/src/app/vault/org-vault/collection-badge/collection-badge.module.ts deleted file mode 100644 index 44c27e57c8d..00000000000 --- a/apps/web/src/app/vault/org-vault/collection-badge/collection-badge.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { NgModule } from "@angular/core"; - -import { SharedModule } from "../../../shared/shared.module"; -import { PipesModule } from "../../individual-vault/pipes/pipes.module"; - -import { CollectionNameBadgeComponent } from "./collection-name.badge.component"; - -@NgModule({ - imports: [SharedModule, PipesModule], - declarations: [CollectionNameBadgeComponent], - exports: [CollectionNameBadgeComponent], -}) -export class CollectionBadgeModule {} diff --git a/apps/web/src/app/vault/org-vault/services/admin-console-cipher-form-config.service.spec.ts b/apps/web/src/app/vault/org-vault/services/admin-console-cipher-form-config.service.spec.ts index 25976c4fb82..9ee13bf077a 100644 --- a/apps/web/src/app/vault/org-vault/services/admin-console-cipher-form-config.service.spec.ts +++ b/apps/web/src/app/vault/org-vault/services/admin-console-cipher-form-config.service.spec.ts @@ -7,7 +7,9 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { CipherId } from "@bitwarden/common/types/guid"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { mockAccountServiceWith } from "@bitwarden/common/spec"; +import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { RoutedVaultFilterService } from "../../individual-vault/vault-filter/services/routed-vault-filter.service"; @@ -25,6 +27,7 @@ describe("AdminConsoleCipherFormConfigService", () => { isMember: true, enabled: true, status: OrganizationUserStatusType.Confirmed, + userId: "UserId", }; const testOrg2 = { id: "333-999-888", @@ -33,6 +36,7 @@ describe("AdminConsoleCipherFormConfigService", () => { isMember: true, enabled: true, status: OrganizationUserStatusType.Confirmed, + userId: "UserId", }; const policyAppliesToActiveUser$ = new BehaviorSubject(true); const collection = { @@ -50,8 +54,7 @@ describe("AdminConsoleCipherFormConfigService", () => { readOnly: false, } as CollectionAdminView; - const organization$ = new BehaviorSubject(testOrg as Organization); - const organizations$ = new BehaviorSubject([testOrg, testOrg2] as Organization[]); + const orgs$ = new BehaviorSubject([testOrg, testOrg2] as Organization[]); const getCipherAdmin = jest.fn().mockResolvedValue(null); const getCipher = jest.fn().mockResolvedValue(null); @@ -65,7 +68,7 @@ describe("AdminConsoleCipherFormConfigService", () => { TestBed.configureTestingModule({ providers: [ AdminConsoleCipherFormConfigService, - { provide: OrganizationService, useValue: { get$: () => organization$, organizations$ } }, + { provide: OrganizationService, useValue: { organizations$: () => orgs$ } }, { provide: CollectionAdminService, useValue: { getAll: () => Promise.resolve([collection, collection2]) }, @@ -80,6 +83,7 @@ describe("AdminConsoleCipherFormConfigService", () => { }, { provide: ApiService, useValue: { getCipherAdmin } }, { provide: CipherService, useValue: { get: getCipher } }, + { provide: AccountService, useValue: mockAccountServiceWith("UserId" as UserId) }, ], }); adminConsoleConfigService = TestBed.inject(AdminConsoleCipherFormConfigService); @@ -196,7 +200,7 @@ describe("AdminConsoleCipherFormConfigService", () => { await adminConsoleConfigService.buildConfig("edit", cipherId); expect(getCipherAdmin).not.toHaveBeenCalled(); - expect(getCipher).toHaveBeenCalledWith(cipherId); + expect(getCipher).toHaveBeenCalledWith(cipherId, "UserId"); }); }); }); diff --git a/apps/web/src/app/vault/org-vault/services/admin-console-cipher-form-config.service.ts b/apps/web/src/app/vault/org-vault/services/admin-console-cipher-form-config.service.ts index 0d3db55d3d6..19259ba4033 100644 --- a/apps/web/src/app/vault/org-vault/services/admin-console-cipher-form-config.service.ts +++ b/apps/web/src/app/vault/org-vault/services/admin-console-cipher-form-config.service.ts @@ -9,17 +9,14 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { OrganizationUserStatusType, PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { CipherId } from "@bitwarden/common/types/guid"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; +import { CipherFormConfig, CipherFormConfigService, CipherFormMode } from "@bitwarden/vault"; -import { - CipherFormConfig, - CipherFormConfigService, - CipherFormMode, -} from "../../../../../../../libs/vault/src/cipher-form/abstractions/cipher-form-config.service"; import { RoutedVaultFilterService } from "../../individual-vault/vault-filter/services/routed-vault-filter.service"; /** Admin Console implementation of the `CipherFormConfigService`. */ @@ -31,6 +28,7 @@ export class AdminConsoleCipherFormConfigService implements CipherFormConfigServ private collectionAdminService: CollectionAdminService = inject(CollectionAdminService); private cipherService: CipherService = inject(CipherService); private apiService: ApiService = inject(ApiService); + private accountService: AccountService = inject(AccountService); private allowPersonalOwnership$ = this.policyService .policyAppliesToActiveUser$(PolicyType.PersonalOwnership) @@ -41,12 +39,16 @@ export class AdminConsoleCipherFormConfigService implements CipherFormConfigServ filter((filter) => filter !== undefined), ); - private allOrganizations$ = this.organizationService.organizations$.pipe( - map((orgs) => { - return orgs.filter( - (o) => o.isMember && o.enabled && o.status === OrganizationUserStatusType.Confirmed, - ); - }), + private allOrganizations$ = this.accountService.activeAccount$.pipe( + switchMap((account) => + this.organizationService.organizations$(account?.id).pipe( + map((orgs) => { + return orgs.filter( + (o) => o.isMember && o.enabled && o.status === OrganizationUserStatusType.Confirmed, + ); + }), + ), + ), ); private organization$ = combineLatest([this.allOrganizations$, this.organizationId$]).pipe( @@ -98,7 +100,7 @@ export class AdminConsoleCipherFormConfigService implements CipherFormConfigServ return null; } - const localCipher = await this.cipherService.get(id); + const localCipher = await this.cipherService.get(id, organization.userId as UserId); // Fetch from the API because we don't need the permissions in local state OR the cipher was not found (e.g. unassigned) if (organization.canEditAllCiphers || localCipher == null) { diff --git a/apps/web/src/app/vault/org-vault/vault.module.ts b/apps/web/src/app/vault/org-vault/vault.module.ts deleted file mode 100644 index db8d2256f52..00000000000 --- a/apps/web/src/app/vault/org-vault/vault.module.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { NgModule } from "@angular/core"; - -import { LooseComponentsModule } from "../../shared/loose-components.module"; -import { SharedModule } from "../../shared/shared.module"; -import { OrganizationBadgeModule } from "../../vault/individual-vault/organization-badge/organization-badge.module"; -import { CollectionDialogModule } from "../components/collection-dialog"; -import { ViewComponent } from "../individual-vault/view.component"; - -import { CollectionBadgeModule } from "./collection-badge/collection-badge.module"; -import { GroupBadgeModule } from "./group-badge/group-badge.module"; -import { VaultRoutingModule } from "./vault-routing.module"; -import { VaultComponent } from "./vault.component"; - -@NgModule({ - imports: [ - VaultRoutingModule, - SharedModule, - LooseComponentsModule, - GroupBadgeModule, - CollectionBadgeModule, - OrganizationBadgeModule, - CollectionDialogModule, - VaultComponent, - ViewComponent, - ], -}) -export class VaultModule {} diff --git a/apps/web/src/app/vault/services/browser-extension-prompt.service.spec.ts b/apps/web/src/app/vault/services/browser-extension-prompt.service.spec.ts new file mode 100644 index 00000000000..647af007eca --- /dev/null +++ b/apps/web/src/app/vault/services/browser-extension-prompt.service.spec.ts @@ -0,0 +1,173 @@ +import { TestBed } from "@angular/core/testing"; + +import { AnonLayoutWrapperDataService } from "@bitwarden/auth/angular"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { VaultMessages } from "@bitwarden/common/vault/enums/vault-messages.enum"; + +import { + BrowserExtensionPromptService, + BrowserPromptState, +} from "./browser-extension-prompt.service"; + +describe("BrowserExtensionPromptService", () => { + let service: BrowserExtensionPromptService; + const setAnonLayoutWrapperData = jest.fn(); + const isFirefox = jest.fn().mockReturnValue(false); + const postMessage = jest.fn(); + window.postMessage = postMessage; + + beforeEach(() => { + setAnonLayoutWrapperData.mockClear(); + postMessage.mockClear(); + isFirefox.mockClear(); + + TestBed.configureTestingModule({ + providers: [ + BrowserExtensionPromptService, + { provide: AnonLayoutWrapperDataService, useValue: { setAnonLayoutWrapperData } }, + { provide: PlatformUtilsService, useValue: { isFirefox } }, + ], + }); + jest.useFakeTimers(); + service = TestBed.inject(BrowserExtensionPromptService); + }); + + afterEach(() => { + jest.clearAllTimers(); + jest.useRealTimers(); + }); + + it("defaults page state to loading", (done) => { + service.pageState$.subscribe((state) => { + expect(state).toBe(BrowserPromptState.Loading); + done(); + }); + }); + + describe("start", () => { + it("posts message to check for extension", () => { + service.start(); + + expect(window.postMessage).toHaveBeenCalledWith({ + command: VaultMessages.checkBwInstalled, + }); + }); + + it("sets timeout for error state", () => { + service.start(); + + expect(service["extensionCheckTimeout"]).not.toBeNull(); + }); + + it("attempts to open the extension when installed", () => { + service.start(); + + window.dispatchEvent( + new MessageEvent("message", { data: { command: VaultMessages.HasBwInstalled } }), + ); + + expect(window.postMessage).toHaveBeenCalledTimes(2); + expect(window.postMessage).toHaveBeenCalledWith({ command: VaultMessages.OpenPopup }); + }); + }); + + describe("success state", () => { + beforeEach(() => { + service.start(); + + window.dispatchEvent( + new MessageEvent("message", { data: { command: VaultMessages.PopupOpened } }), + ); + }); + + it("sets layout title", () => { + expect(setAnonLayoutWrapperData).toHaveBeenCalledWith({ + pageTitle: { key: "openedExtension" }, + }); + }); + + it("sets success page state", (done) => { + service.pageState$.subscribe((state) => { + expect(state).toBe(BrowserPromptState.Success); + done(); + }); + }); + + it("clears the error timeout", () => { + expect(service["extensionCheckTimeout"]).toBeUndefined(); + }); + }); + + describe("firefox", () => { + beforeEach(() => { + isFirefox.mockReturnValue(true); + service.start(); + }); + + afterEach(() => { + isFirefox.mockReturnValue(false); + }); + + it("sets manual open state", (done) => { + service.pageState$.subscribe((state) => { + expect(state).toBe(BrowserPromptState.ManualOpen); + done(); + }); + }); + + it("sets error state after timeout", () => { + expect(setAnonLayoutWrapperData).toHaveBeenCalledWith({ + pageTitle: { key: "somethingWentWrong" }, + }); + }); + }); + + describe("mobile state", () => { + beforeEach(() => { + Utils.isMobileBrowser = true; + service.start(); + }); + + afterEach(() => { + Utils.isMobileBrowser = false; + }); + + it("sets mobile state", (done) => { + service.pageState$.subscribe((state) => { + expect(state).toBe(BrowserPromptState.MobileBrowser); + done(); + }); + }); + + it("sets desktop required title", () => { + expect(setAnonLayoutWrapperData).toHaveBeenCalledWith({ + pageTitle: { key: "desktopRequired" }, + }); + }); + + it("clears the error timeout", () => { + expect(service["extensionCheckTimeout"]).toBeUndefined(); + }); + }); + + describe("error state", () => { + beforeEach(() => { + service.start(); + jest.advanceTimersByTime(1000); + }); + + it("sets error state", (done) => { + service.pageState$.subscribe((state) => { + expect(state).toBe(BrowserPromptState.Error); + done(); + }); + }); + + it("sets error state after timeout", () => { + expect(setAnonLayoutWrapperData).toHaveBeenCalledWith({ + pageTitle: { key: "somethingWentWrong" }, + }); + }); + }); +}); diff --git a/apps/web/src/app/vault/services/browser-extension-prompt.service.ts b/apps/web/src/app/vault/services/browser-extension-prompt.service.ts new file mode 100644 index 00000000000..fec27758d3c --- /dev/null +++ b/apps/web/src/app/vault/services/browser-extension-prompt.service.ts @@ -0,0 +1,125 @@ +import { DestroyRef, Injectable } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { BehaviorSubject, fromEvent } from "rxjs"; + +import { AnonLayoutWrapperDataService } from "@bitwarden/auth/angular"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { VaultMessages } from "@bitwarden/common/vault/enums/vault-messages.enum"; + +export enum BrowserPromptState { + Loading = "loading", + Error = "error", + Success = "success", + ManualOpen = "manualOpen", + MobileBrowser = "mobileBrowser", +} + +type PromptErrorStates = BrowserPromptState.Error | BrowserPromptState.ManualOpen; + +@Injectable({ + providedIn: "root", +}) +export class BrowserExtensionPromptService { + private _pageState$ = new BehaviorSubject(BrowserPromptState.Loading); + + /** Current state of the prompt page */ + pageState$ = this._pageState$.asObservable(); + + /** Timeout identifier for extension check */ + private extensionCheckTimeout: number | undefined; + + constructor( + private anonLayoutWrapperDataService: AnonLayoutWrapperDataService, + private destroyRef: DestroyRef, + private platformUtilsService: PlatformUtilsService, + ) {} + + start(): void { + if (Utils.isMobileBrowser) { + this.setMobileState(); + return; + } + + // Firefox does not support automatically opening the extension, + // it currently requires a user gesture within the context of the extension to open. + // Show message to direct the user to manually open the extension. + // Mozilla Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1799344 + if (this.platformUtilsService.isFirefox()) { + this.setErrorState(BrowserPromptState.ManualOpen); + return; + } + + this.checkForBrowserExtension(); + } + + /** Post a message to the extension to open */ + openExtension() { + window.postMessage({ command: VaultMessages.OpenPopup }); + } + + /** Send message checking for the browser extension */ + private checkForBrowserExtension() { + fromEvent(window, "message") + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((event) => { + void this.getMessages(event); + }); + + window.postMessage({ command: VaultMessages.checkBwInstalled }); + + // Wait a second for the extension to respond and open, else show the error state + this.extensionCheckTimeout = window.setTimeout(() => { + this.setErrorState(); + }, 1000); + } + + /** Handle window message events */ + private getMessages(event: any) { + if (event.data.command === VaultMessages.HasBwInstalled) { + this.openExtension(); + } + + if (event.data.command === VaultMessages.PopupOpened) { + this.setSuccessState(); + } + } + + /** Show message that this page should be opened on a desktop browser */ + private setMobileState() { + this.clearExtensionCheckTimeout(); + this._pageState$.next(BrowserPromptState.MobileBrowser); + this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ + pageTitle: { + key: "desktopRequired", + }, + }); + } + + /** Show the open extension success state */ + private setSuccessState() { + this.clearExtensionCheckTimeout(); + this._pageState$.next(BrowserPromptState.Success); + this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ + pageTitle: { + key: "openedExtension", + }, + }); + } + + /** Show open extension error state */ + private setErrorState(errorState?: PromptErrorStates) { + this.clearExtensionCheckTimeout(); + this._pageState$.next(errorState ?? BrowserPromptState.Error); + this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ + pageTitle: { + key: "somethingWentWrong", + }, + }); + } + + private clearExtensionCheckTimeout() { + window.clearTimeout(this.extensionCheckTimeout); + this.extensionCheckTimeout = undefined; + } +} diff --git a/apps/web/src/app/vault/services/web-cipher-form-generation.service.ts b/apps/web/src/app/vault/services/web-cipher-form-generation.service.ts index 8fa51e34e2a..d7625d5b220 100644 --- a/apps/web/src/app/vault/services/web-cipher-form-generation.service.ts +++ b/apps/web/src/app/vault/services/web-cipher-form-generation.service.ts @@ -26,9 +26,9 @@ export class WebCipherFormGenerationService implements CipherFormGenerationServi return result.generatedValue; } - async generateUsername(): Promise { + async generateUsername(uri: string): Promise { const dialogRef = WebVaultGeneratorDialogComponent.open(this.dialogService, { - data: { type: "username" }, + data: { type: "username", uri: uri }, }); const result = await firstValueFrom(dialogRef.closed); diff --git a/apps/web/src/app/vault/services/web-view-password-history.service.ts b/apps/web/src/app/vault/services/web-view-password-history.service.ts index 756c2140ab5..b1451b268de 100644 --- a/apps/web/src/app/vault/services/web-view-password-history.service.ts +++ b/apps/web/src/app/vault/services/web-view-password-history.service.ts @@ -1,9 +1,9 @@ import { Injectable } from "@angular/core"; +import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { DialogService } from "@bitwarden/components"; -import { ViewPasswordHistoryService } from "../../../../../../libs/common/src/vault/abstractions/view-password-history.service"; import { openPasswordHistoryDialog } from "../individual-vault/password-history.component"; /** diff --git a/apps/web/src/app/vault/settings/purge-vault.component.ts b/apps/web/src/app/vault/settings/purge-vault.component.ts index 80b0448a39c..83955c0dc94 100644 --- a/apps/web/src/app/vault/settings/purge-vault.component.ts +++ b/apps/web/src/app/vault/settings/purge-vault.component.ts @@ -11,7 +11,7 @@ import { Verification } from "@bitwarden/common/auth/types/verification"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SyncService } from "@bitwarden/common/platform/sync"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; export interface PurgeVaultDialogData { organizationId: string; @@ -37,6 +37,7 @@ export class PurgeVaultComponent { private userVerificationService: UserVerificationService, private router: Router, private syncService: SyncService, + private toastService: ToastService, ) { this.organizationId = data && data.organizationId ? data.organizationId : null; } @@ -46,7 +47,11 @@ export class PurgeVaultComponent { .buildRequest(this.formGroup.value.masterPassword) .then((request) => this.apiService.postPurgeCiphers(request, this.organizationId)); await response; - this.platformUtilsService.showToast("success", null, this.i18nService.t("vaultPurged")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("vaultPurged"), + }); await this.syncService.fullSync(true); if (this.organizationId != null) { await this.router.navigate(["organizations", this.organizationId, "vault"]); diff --git a/apps/web/src/connectors/captcha.ts b/apps/web/src/connectors/captcha.ts index 01a14a79a23..aad6eaa3d47 100644 --- a/apps/web/src/connectors/captcha.ts +++ b/apps/web/src/connectors/captcha.ts @@ -5,8 +5,12 @@ import { b64Decode, getQsParam } from "./common"; declare let hcaptcha: any; if (window.location.pathname.includes("mobile")) { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./captcha-mobile.scss"); } else { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./captcha.scss"); } @@ -50,6 +54,8 @@ async function start() { let decodedData: any; try { decodedData = JSON.parse(b64Decode(data, true)); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { error("Cannot parse data."); return; diff --git a/apps/web/src/connectors/duo-redirect.ts b/apps/web/src/connectors/duo-redirect.ts index 0f067b583b9..b5300ff65e7 100644 --- a/apps/web/src/connectors/duo-redirect.ts +++ b/apps/web/src/connectors/duo-redirect.ts @@ -3,6 +3,8 @@ import { getQsParam } from "./common"; import { TranslationService } from "./translation.service"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./duo-redirect.scss"); const mobileDesktopCallback = "bitwarden://duo-callback"; diff --git a/apps/web/src/connectors/sso.ts b/apps/web/src/connectors/sso.ts index 3ec6a8f7a3d..4fdab71be3b 100644 --- a/apps/web/src/connectors/sso.ts +++ b/apps/web/src/connectors/sso.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { getQsParam } from "./common"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./sso.scss"); window.addEventListener("load", () => { @@ -14,17 +16,21 @@ window.addEventListener("load", () => { } else if (state != null && state.includes(":clientId=browser")) { initiateBrowserSso(code, state, false); } else { - window.location.href = window.location.origin + "/#/sso?code=" + code + "&state=" + state; - // Match any characters between "_returnUri='" and the next "'" - const returnUri = extractFromRegex(state, "(?<=_returnUri=')(.*)(?=')"); - if (returnUri) { - window.location.href = window.location.origin + `/#${returnUri}`; - } else { - window.location.href = window.location.origin + "/#/sso?code=" + code + "&state=" + state; - } + initiateWebAppSso(code, state); } }); +function initiateWebAppSso(code: string, state: string) { + // If we've initiated SSO from somewhere other than the SSO component on the web app, the SSO component will add + // a _returnUri to the state variable. Here we're extracting that URI and sending the user there instead of to the SSO component. + const returnUri = extractFromRegex(state, "(?<=_returnUri=')(.*)(?=')"); + if (returnUri) { + window.location.href = window.location.origin + `/#${returnUri}`; + } else { + window.location.href = window.location.origin + "/#/sso?code=" + code + "&state=" + state; + } +} + function initiateBrowserSso(code: string, state: string, lastpass: boolean) { window.postMessage({ command: "authResult", code: code, state: state, lastpass: lastpass }, "*"); const handOffMessage = ("; " + document.cookie) diff --git a/apps/web/src/connectors/webauthn-fallback.html b/apps/web/src/connectors/webauthn-fallback.html index 19d0a6a5eda..662eb0b6baf 100644 --- a/apps/web/src/connectors/webauthn-fallback.html +++ b/apps/web/src/connectors/webauthn-fallback.html @@ -1,39 +1,132 @@ - + + + + Bitwarden WebAuthn Connector - -
    -
    -
    - -
    -

    - -

    -
    -
    -
    -

    -
    - - -
    -
    -

    - -

    -
    + +
    + + Bitwarden + + +
    +
    + + + + + + + + + + + + + + +
    + +
    + +

    + +

    +
    + +
    +
    + +
    +
    +

    + +
    + +
    + +
    -
    + diff --git a/apps/web/src/connectors/webauthn-fallback.ts b/apps/web/src/connectors/webauthn-fallback.ts index 971bc5e44a1..3561f922e03 100644 --- a/apps/web/src/connectors/webauthn-fallback.ts +++ b/apps/web/src/connectors/webauthn-fallback.ts @@ -4,8 +4,6 @@ import { b64Decode, getQsParam } from "./common"; import { buildDataString, parseWebauthnJson } from "./common-webauthn"; import { TranslationService } from "./translation.service"; -require("./webauthn.scss"); - let parsed = false; let webauthnJson: any; let parentUrl: string = null; @@ -52,6 +50,8 @@ function parseParametersV2() { let dataObj: { data: any; btnText: string } = null; try { dataObj = JSON.parse(b64Decode(getQsParam("data"))); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { error("Cannot parse data."); return; @@ -71,17 +71,22 @@ document.addEventListener("DOMContentLoaded", async () => { await localeService.init(); - document.getElementById("msg").innerText = localeService.t("webAuthnFallbackMsg"); - document.getElementById("remember-label").innerText = localeService.t("rememberMe"); + document.getElementById("remember-label").innerText = localeService.t( + "dontAskAgainOnThisDeviceFor30Days", + ); const button = document.getElementById("webauthn-button"); - button.innerText = localeService.t("webAuthnAuthenticate"); + button.innerText = localeService.t("readSecurityKey"); button.onclick = start; - document.getElementById("spinner").classList.add("d-none"); - const content = document.getElementById("content"); - content.classList.add("d-block"); - content.classList.remove("d-none"); + const titleForSmallerScreens = document.getElementById("title-smaller-screens"); + const titleForLargerScreens = document.getElementById("title-larger-screens"); + + titleForSmallerScreens.innerText = localeService.t("verifyYourIdentity"); + titleForLargerScreens.innerText = localeService.t("verifyYourIdentity"); + + const subtitle = document.getElementById("subtitle"); + subtitle.innerText = localeService.t("followTheStepsBelowToFinishLoggingIn"); }); function start() { @@ -103,6 +108,8 @@ function start() { let json: any; try { json = parseWebauthnJson(webauthnJson); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { error("Cannot parse data."); return; @@ -140,20 +147,24 @@ function error(message: string) { el.textContent = message; el.classList.add("alert"); el.classList.add("alert-danger"); + el.classList.remove("tw-hidden"); } function success(message: string) { (document.getElementById("webauthn-button") as HTMLButtonElement).disabled = true; + (document.getElementById("remember") as HTMLInputElement).disabled = true; const el = document.getElementById("msg"); resetMsgBox(el); el.textContent = message; el.classList.add("alert"); el.classList.add("alert-success"); + el.classList.remove("tw-hidden"); } function resetMsgBox(el: HTMLElement) { el.classList.remove("alert"); el.classList.remove("alert-danger"); el.classList.remove("alert-success"); + el.classList.add("tw-hidden"); } diff --git a/apps/web/src/connectors/webauthn.html b/apps/web/src/connectors/webauthn.html index cc46e4932af..27f143f90d3 100644 --- a/apps/web/src/connectors/webauthn.html +++ b/apps/web/src/connectors/webauthn.html @@ -5,14 +5,11 @@ Bitwarden WebAuthn Connector - - - - - - -
    - -
    + + diff --git a/apps/web/src/connectors/webauthn.ts b/apps/web/src/connectors/webauthn.ts index 14ba8a280ee..dd38bdfb040 100644 --- a/apps/web/src/connectors/webauthn.ts +++ b/apps/web/src/connectors/webauthn.ts @@ -3,6 +3,8 @@ import { b64Decode, getQsParam } from "./common"; import { buildDataString, parseWebauthnJson } from "./common-webauthn"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./webauthn.scss"); const mobileCallbackUri = "bitwarden://webauthn-callback"; @@ -11,6 +13,7 @@ let parsed = false; let webauthnJson: any; let headerText: string = null; let btnText: string = null; +let btnAwaitingInteractionText: string = null; let btnReturnText: string = null; let parentUrl: string = null; let parentOrigin: string = null; @@ -19,21 +22,60 @@ let stopWebAuthn = false; let sentSuccess = false; let obj: any = null; +// For accessibility, we do not actually disable the button as it would +// become unfocusable by a screenreader. We just make it look disabled. +const disabledBtnClasses = [ + "tw-bg-secondary-300", + "tw-border-secondary-300", + "!tw-text-muted", + "!tw-cursor-not-allowed", + "hover:tw-bg-secondary-300", + "hover:tw-border-secondary-300", + "hover:!tw-text-muted", + "hover:tw-no-underline", +]; + +const enabledBtnClasses = [ + "tw-bg-primary-600", + "tw-border-primary-600", + "!tw-text-contrast", + "hover:tw-bg-primary-700", + "hover:tw-border-primary-700", + "hover:!tw-text-contrast", + "hover:tw-no-underline", +]; + document.addEventListener("DOMContentLoaded", () => { init(); - - parseParameters(); - if (headerText) { - const header = document.getElementById("webauthn-header"); - header.innerText = decodeURI(headerText); - } - if (btnText) { - const button = document.getElementById("webauthn-button"); - button.innerText = decodeURI(btnText); - button.onclick = executeWebAuthn; - } }); +function setDefaultWebAuthnButtonState() { + if (!btnText) { + return; + } + + const button = document.getElementById("webauthn-button"); + button.onclick = executeWebAuthn; + + button.innerText = decodeURI(btnText); + + // reset back to default button state + button.classList.remove(...disabledBtnClasses); + button.classList.add(...enabledBtnClasses); +} + +function setAwaitingInteractionWebAuthnButtonState() { + if (!btnAwaitingInteractionText) { + return; + } + const button = document.getElementById("webauthn-button"); + button.innerText = decodeURI(btnAwaitingInteractionText); + button.onclick = null; + + button.classList.remove(...enabledBtnClasses); + button.classList.add(...disabledBtnClasses); +} + function init() { start(); onMessage(); @@ -74,6 +116,7 @@ function parseParametersV1() { webauthnJson = b64Decode(data); headerText = getQsParam("headerText"); btnText = getQsParam("btnText"); + btnAwaitingInteractionText = getQsParam("btnAwaitingInteractionText"); btnReturnText = getQsParam("btnReturnText"); } @@ -88,6 +131,8 @@ function parseParametersV2() { } = null; try { dataObj = JSON.parse(b64Decode(getQsParam("data"))); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { error("Cannot parse data."); return; @@ -109,6 +154,14 @@ function start() { } parseParameters(); + + if (headerText) { + const header = document.getElementById("webauthn-header"); + header.innerText = decodeURI(headerText); + } + + setDefaultWebAuthnButtonState(); + if (!webauthnJson) { error("No data."); return; @@ -116,6 +169,8 @@ function start() { try { obj = parseWebauthnJson(webauthnJson); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { error("Cannot parse webauthn data."); return; @@ -135,9 +190,12 @@ function start() { function executeWebAuthn() { if (stopWebAuthn) { + // reset back to default button state + setDefaultWebAuthnButtonState(); return; } + setAwaitingInteractionWebAuthnButtonState(); navigator.credentials.get({ publicKey: obj }).then(success).catch(error); } @@ -150,6 +208,7 @@ function onMessage() { } if (event.data === "stop") { + setDefaultWebAuthnButtonState(); stopWebAuthn = true; } else if (event.data === "start" && stopWebAuthn) { start(); @@ -165,6 +224,7 @@ function error(message: string) { returnButton(mobileCallbackUri + "?error=" + encodeURIComponent(message)); } else { parent.postMessage("error|" + message, parentUrl); + setDefaultWebAuthnButtonState(); } } diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index f86b6ab497b..3b9168b29b2 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Critical applications" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "Welke tipe item is dit?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Notas" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Note" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Sleep om te sorteer" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Teks" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Wysig vouer" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "Basisdomein", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "bv.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filter" }, - "moveSelectedToOrg": { - "message": "Skuif seleksie na organisasie" - }, "deleteSelected": { "message": "Skrap seleksie" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Gekose items is na $ORGNAME$ geskuif", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "Nee" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "Teken aan of skep ’n nuwe rekening vir toegang tot u beveiligde kluis." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Dien in" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Wagwoordwenk" - }, - "enterEmailToGetHint": { - "message": "Voer u rekening-e-posadres in om u hoofwagwoordwenk te kry." - }, "getMasterPasswordHint": { "message": "Kry hoofwagwoordwenk" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "’n Kennisgewing is na u toestel gestuur." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "Weergawe $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Onthou my" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Stuur weer e-pos met bevestigingskode" }, "useAnotherTwoStepMethod": { "message": "Gebruik ’n ander tweestapaantekenmetode" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Plaas u YubiKey in u rekenaar se USB-poort en druk dan op sy knop." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Opsies vir tweestapaantekening" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Het u toegang tot al u tweestapaanbieders verloor? Gebruik dan u terugstelkode om alle tweestapaanbieders op u rekening te deaktiveer." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Gemigreer van FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "E-pos" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Kies ’n organisasie waarheen u hierdie item wil skuif. Deur te skuif kry die organisasie die einaarskap van die item. U is dan nie meer die direkte eienaar van die item wanneer dit geskuif is nie." }, - "moveManyToOrgDesc": { - "message": "Kies ’n organisasie waarheen u hierdie items wil skuif. Deur te skuif kry die organisasie die einaarskap van die items. U is dan nie meer die direkte eienaar van die items wanneer dit geskuif is nie." - }, "collectionsDesc": { "message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "U het $COUNT$ item(s) gekies. $MOVEABLE_COUNT$ item(s) kan na ’n organisasie geskuif word, $NONMOVEABLE_COUNT$ kan nie.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Bevestigingskode (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Hergenereer wagwoord" - }, "length": { "message": "Lengte" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Teken asb. weer aan." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Teken asb. weer aan. Indien u ander Bitwarden-toepassings gebruik, teken daarop ook weer uit en aan." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Gevaarsone" }, - "dangerZoneDesc": { - "message": "Versigtig, hierdie aksies is onomkeerbaar!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Deauthorize sessions" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Proceeding will also log you out of your current session, requiring you to log back in. You will also be prompted for two-step login again, if set up. Active sessions on other devices may continue to remain active for up to one hour." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "All sessions deauthorized" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Setting up two-step login can permanently lock you out of your Bitwarden account. A recovery code allows you to access your account in the event that you can no longer use your normal two-step login provider (example: you lose your device). Bitwarden support will not be able to assist you if you lose access to your account. We recommend you write down or print the recovery code and keep it in a safe place." }, + "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." + }, "viewRecoveryCode": { "message": "Bekyk terugstelkode" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Bestuur" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Deaktiveer" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Revoke access" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "Hierdie tweestapaantekenaanbieder is vir u rekening geaktiveer." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "There was a problem reading the security key. Try again." }, - "twoFactorWebAuthnWarning": { - "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used. Supported platforms:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Web vault and browser extensions on a desktop/laptop with a WebAuthn supported browser (Chrome, Opera, Vivaldi, or Firefox with FIDO U2F turned on)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "U herstelkode vir Bitwarden-tweestapaantekening" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Toestel" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "U gebruik ’n onondersteunde webblaaier. Die webkluis werk dalk nie soos normaal nie." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "If you cannot access your account through your normal two-step login methods, you can use your two-step login recovery code to turn off all two-step providers on your account." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Recover account two-step login" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Stel minimum vereistes vir hoofwagwoordsterkte." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Require two-step login" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Stel minimum vereistes vir opstelling van wagwoordgenereerder." }, - "passwordGeneratorPolicyInEffect": { - "message": "Een of meer organisasiebeleide beïnvloed u genereerderinstellings." - }, "masterPasswordPolicyInEffect": { "message": "Een of meer organisasiebeleide stel die volgende eise aan u hoofwagwoord:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Organisasie-eienaars en -administrateurs is vrygestel van die afdwing van hierdie beleid." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "Lêer" }, "sendTypeText": { "message": "Teks" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Skep nuwe Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Skrap Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Is u seker u wil hierdie Send skrap?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "Welke tipe Send is dit?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Skrapdatum" }, - "deletionDateDesc": { - "message": "Die Send sal outomaties op die aangewese datum en tyd geskrap word.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maksimum toegangsaantal" }, - "maxAccessCountDesc": { - "message": "Indien ingestel het gebruikers ne meer toegang tot hierdie Send sodra die maksimum aantal toegang bereik is.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Huidige toegangsaantal" - }, - "sendPasswordDesc": { - "message": "Vereis opsioneel ’n wagwoord vir gebruikers om toegang tot hierdie Send te verkry.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Privaat notas oor hierdie Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Gedeaktiveer" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Is u seker u wil die wagwoord verwyder?" }, - "hideEmail": { - "message": "Versteek my e-posadres vir ontvangers." - }, - "disableThisSend": { - "message": "Deaktiveer hierdie Send sodat niemand toegang daartoe het nie.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Alle Sends" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Word geskrap" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Verstreke" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "The following organization policies are currently in effect:" - }, - "sendDisableHideEmailInEffect": { - "message": "Users are not allowed 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." - }, "modifiedPolicyId": { "message": "Gewysigde beleid $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Deaktiveer persoonlike eienaarskap vir organisasiegebruikers" }, - "textHiddenByDefault": { - "message": "Versteek die teks be verstek wanneer die Send gebruik word", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "’n Vriendelike naam om hierdie Send te beskryf.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Die teks wat u wil verstuur." - }, - "sendFileDesc": { - "message": "Die lêer wat u wil verstuur." - }, - "copySendLinkOnSave": { - "message": "Kopieer die skakel om hierdie Send te deel tydens bewaar na my knipbord." - }, - "sendLinkLabel": { - "message": "Send-skakel", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "Klik op onderstaande knop om u 2FA te verifieer." }, "webAuthnAuthenticate": { "message": "Waarmerk WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn word nie in hierdie blaaier ondersteun nie." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Fout" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Genereerder", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "Wat wil u genereer?" - }, - "passwordType": { - "message": "Wagwoordtipe" - }, - "regenerateUsername": { - "message": "Hergenereer gebruikersnaam" - }, "generateUsername": { "message": "Genereer gebruikersnaam" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Gebruikersnaamtipe" - }, "plusAddressedEmail": { "message": "E-posadres met plus", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Gebruik u domein se opgestelde allesomvattende inmandjie." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Lukraak", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Gasheernaam", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API-toegangsteken" - }, "deviceVerification": { "message": "Toestelbevestiging" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Aantal gebruikers" }, - "loggingInAs": { - "message": "Teken tans aan as" - }, - "notYou": { - "message": "Nie u nie?" - }, "pickAnAvatarColor": { "message": "Kies 'n avatar kleur" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "Geen versameling" }, - "canView": { - "message": "Kan bekyk" - }, - "canViewExceptPass": { - "message": "Kan bekyk, behalwe wagwoorde" - }, - "canEdit": { - "message": "Kan wysig" - }, - "canEditExceptPass": { - "message": "Kan wysig behalwe wagwoorde" - }, "noCollectionsAdded": { "message": "Geen versamelings toegevoeg" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Keur versoek goed" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Geen toestelversoeke" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domain" - }, "alreadyHaveAccount": { "message": "Already have an account?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "maand per lid" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index ed843a62cd1..13777f942de 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -1,10 +1,13 @@ { "allApplications": { - "message": "All applications" + "message": "كل التطبيقات" }, "criticalApplications": { "message": "Critical applications" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -96,25 +99,58 @@ "message": "Apps marked as critical" }, "application": { - "message": "Application" + "message": "تطبيق" }, "atRiskPasswords": { "message": "At-risk passwords" }, "requestPasswordChange": { - "message": "Request password change" + "message": "طلب تغيير كلمة المرور" }, "totalPasswords": { "message": "Total passwords" }, "searchApps": { - "message": "Search applications" + "message": "البحث في التطبيقات" }, "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { - "message": "Total members" + "message": "إجمالي الأعضاء" }, "atRiskApplications": { "message": "At-risk applications" @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "ما هو نوع العنصر؟" }, @@ -159,8 +201,11 @@ "notes": { "message": "ملاحظات" }, + "privateNote": { + "message": "Private note" + }, "note": { - "message": "Note" + "message": "ملاحظة" }, "customFields": { "message": "حقول مخصصة" @@ -169,19 +214,19 @@ "message": "اسم حامل البطاقة" }, "loginCredentials": { - "message": "Login credentials" + "message": "معلومات الدخول" }, "personalDetails": { - "message": "Personal details" + "message": "البيانات الشخصية" }, "identification": { - "message": "Identification" + "message": "التعريف" }, "contactInfo": { - "message": "Contact info" + "message": "معلومات الاتصال" }, "cardDetails": { - "message": "Card details" + "message": "بيانات البطاقة" }, "cardBrandDetails": { "message": "$BRAND$ details", @@ -193,13 +238,13 @@ } }, "itemHistory": { - "message": "Item history" + "message": "تاريخ العنصر" }, "authenticatorKey": { - "message": "Authenticator key" + "message": "مفتاح المصادقة" }, "autofillOptions": { - "message": "Autofill options" + "message": "خيارات الملء التلقائي" }, "websiteUri": { "message": "Website (URI)" @@ -383,6 +428,9 @@ "dragToSort": { "message": "اسحب للفرز" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "نص" }, @@ -393,17 +441,17 @@ "message": "قيمة منطقية" }, "cfTypeCheckbox": { - "message": "Checkbox" + "message": "مربع اختيار" }, "cfTypeLinked": { "message": "مرتبط", "description": "This describes a field that is 'linked' (related) to another field." }, "fieldType": { - "message": "Field type" + "message": "نوع الحقل" }, "fieldLabel": { - "message": "Field label" + "message": "اسم الحقل" }, "remove": { "message": "إزالة" @@ -416,7 +464,7 @@ "description": "This is the folder for uncategorized items" }, "selfOwnershipLabel": { - "message": "You", + "message": "أنت", "description": "Used as a label to indicate that the user is the owner of an item." }, "addFolder": { @@ -425,6 +473,31 @@ "editFolder": { "message": "تعديل المجلد" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "مجلد جديد" + }, + "folderName": { + "message": "اسم المجلد" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "هل أنت متأكد من أنك تريد حذف هذا المجلد بشكل دائم؟" + }, "baseDomain": { "message": "النطاق الأساسي", "description": "Domain name. Example: website.com" @@ -469,7 +542,7 @@ "message": "توليد كلمة مرور" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "توليد عبارة مرور" }, "checkPassword": { "message": "تحقق مما إذا تم الكشف عن كلمة المرور." @@ -572,7 +645,7 @@ "message": "ملاحظة سرية" }, "typeSshKey": { - "message": "SSH key" + "message": "مفتاح بروتوكول النقل الآمن" }, "typeLoginPlural": { "message": "تسجيلات الدخول" @@ -605,7 +678,7 @@ "message": "الاسم الكامل" }, "address": { - "message": "Address" + "message": "العنوان" }, "address1": { "message": "العنوان 1" @@ -684,19 +757,10 @@ "message": "عنصر" }, "itemDetails": { - "message": "Item details" + "message": "تفاصيل العنصر" }, "itemName": { - "message": "Item name" - }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } + "message": "اسم العنصر" }, "ex": { "message": "مثال.", @@ -722,7 +786,7 @@ } }, "copySuccessful": { - "message": "Copy Successful" + "message": "تم النسخ بنجاح" }, "copyValue": { "message": "نسخ القيمة", @@ -733,11 +797,11 @@ "description": "Copy password to clipboard" }, "copyPassphrase": { - "message": "Copy passphrase", + "message": "نسخ عبارة المرور", "description": "Copy passphrase to clipboard" }, "passwordCopied": { - "message": "Password copied" + "message": "تم نسخ كلمة المرور" }, "copyUsername": { "message": "نسخ اسم المستخدم", @@ -756,7 +820,7 @@ "description": "Copy URI to clipboard" }, "copyCustomField": { - "message": "Copy $FIELD$", + "message": "نسخ $FIELD$", "placeholders": { "field": { "content": "$1", @@ -765,34 +829,34 @@ } }, "copyWebsite": { - "message": "Copy website" + "message": "نسخ الموقع الإلكتروني" }, "copyNotes": { - "message": "Copy notes" + "message": "نسخ الملاحظات" }, "copyAddress": { - "message": "Copy address" + "message": "نسخ العنوان" }, "copyPhone": { "message": "Copy phone" }, "copyEmail": { - "message": "Copy email" + "message": "نسخ البريد الإلكتروني" }, "copyCompany": { - "message": "Copy company" + "message": "نسخ الشركة" }, "copySSN": { - "message": "Copy Social Security number" + "message": "نسخ رقم الضمان الاجتماعي" }, "copyPassportNumber": { - "message": "Copy passport number" + "message": "نسخ رقم جواز السفر" }, "copyLicenseNumber": { - "message": "Copy license number" + "message": "نسخ رقم الرخصة" }, "copyName": { - "message": "Copy name" + "message": "نسخ الاسم" }, "me": { "message": "أنا" @@ -815,9 +879,6 @@ "filter": { "message": "تصفية" }, - "moveSelectedToOrg": { - "message": "نقل العناصر المختارة إلى منظمة" - }, "deleteSelected": { "message": "حذف العناصر المختارة" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "تم نقل العناصر المحددة إلى $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -952,22 +1004,22 @@ "message": "تم تسجيل الخروج" }, "loggedOutDesc": { - "message": "You have been logged out of your account." + "message": "لقد تم تسجيل خروجك من الحساب." }, "loginExpired": { "message": "انتهت صَلاحِيَة جَلسة الدخول." }, "restartRegistration": { - "message": "Restart registration" + "message": "إعادة التسجيل" }, "expiredLink": { - "message": "Expired link" + "message": "رابط منتهي الصلاحية" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "الرجاء إعادة تشغيل التسجيل أو محاولة تسجيل الدخول." }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "قد يكون لديك حساب بالفعل" }, "logOutConfirmation": { "message": "هل أنت متأكد من أنك تريد تسجيل الخروج؟" @@ -984,6 +1036,9 @@ "no": { "message": "لا" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "قم بتسجيل الدخول أو أنشئ حساباً جديداً لتتمكن من الوصول إلى خزانتك السرية." }, @@ -994,7 +1049,7 @@ "message": "تسجيل الدخول باستخدام الجهاز يجب أن يتم إعداده في إعدادات تطبيق Bitwarden. هل تحتاج إلى خيار آخر؟" }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "هل تحتاج إلى خيار آخر؟" }, "loginWithMasterPassword": { "message": "تسجيل الدخول باستخدام كلمة المرور الرئيسية" @@ -1009,13 +1064,13 @@ "message": "استخدم طريقة أخرى للولوج" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "تسجيل الدخول باستخدام مفتاح المرور" }, "useSingleSignOn": { "message": "Use single sign-on" }, "welcomeBack": { - "message": "Welcome back" + "message": "مرحباً بعودتك" }, "invalidPasskeyPleaseTryAgain": { "message": "مفتاح المرور غير صالح. الرجاء المحاولة مرة أخرى." @@ -1039,13 +1094,13 @@ "message": "Keep this window open and follow prompts from your browser." }, "errorCreatingPasskey": { - "message": "Error creating passkey" + "message": "خطأ في إنشاء مفتاح المرور" }, "errorCreatingPasskeyInfo": { "message": "There was a problem creating your passkey." }, "passkeySuccessfullyCreated": { - "message": "Passkey successfully created!" + "message": "تم إنشاء مفتاح المرور بنجاح!" }, "customPasskeyNameInfo": { "message": "Name your passkey to help you identify it." @@ -1093,16 +1148,16 @@ "message": "Passkey limit reached. Remove a passkey to add another." }, "tryAgain": { - "message": "Try again" + "message": "حاول مرة أخرى" }, "createAccount": { "message": "إنشاء حساب" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "هل أنت جديد في بيتواردن؟" }, "setAStrongPassword": { - "message": "Set a strong password" + "message": "تعيين كلمة مرور قوية" }, "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" @@ -1117,20 +1172,44 @@ "message": "تسجيل الدخول" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "تسجيل الدخول إلى بيتواردن" + }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "مهلة المصادقة" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "بَدْء تسجيل الدخول" }, + "logInRequestSent": { + "message": "تم إرسال الطلب" + }, "submit": { "message": "قدِّم" }, @@ -1184,7 +1263,7 @@ "message": "الإعدادات" }, "accountEmail": { - "message": "Account email" + "message": "البريد الإلكتروني للحساب" }, "requestHint": { "message": "Request hint" @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "تلميح كلمة المرور" - }, - "enterEmailToGetHint": { - "message": "أدخل عنوان البريد الإلكتروني لحسابك للحصول على تلميح كلمة المرور الرئيسية." - }, "getMasterPasswordHint": { "message": "احصل على تلميح لكلمة المرور الرئيسية" }, @@ -1236,7 +1309,7 @@ "message": "Your new account has been created!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "لقد قمت بتسجيل الدخول!" }, "trialAccountCreated": { "message": "تم إنشاء الحساب بنجاح." @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "تم إرسال إشعار إلى جهازك." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "هل تحاول الوصول إلى حسابك؟" + }, + "accessAttemptBy": { + "message": "محاولة تسجيل الدخول بواسطة $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "تأكيد الوصول" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "الإصدار $VERSION_NUMBER$", "placeholders": { @@ -1359,11 +1459,21 @@ "rememberMe": { "message": "تذكرني" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "إرسال رمز التحقق إلى البريد الإلكتروني مرة أخرى" }, "useAnotherTwoStepMethod": { - "message": "Use another two-step login method" + "message": "استخدام طريقة أخرى لتسجيل الدخول بخطوتين" + }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "خيارات تسجيل الدخول بخطوتين" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Lost access to all of your two-step login providers? Use your recovery code to turn off all two-step login providers from your account." }, @@ -1417,7 +1530,7 @@ "message": "FIDO U2F security a key" }, "webAuthnTitle": { - "message": "Passkey" + "message": "مفتاح المرور" }, "webAuthnDesc": { "message": "Use your device's biometrics or a FIDO2 compatible security key." @@ -1425,11 +1538,14 @@ "webAuthnMigrated": { "message": "(Migrated from FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "البريد الإلكتروني" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "أدخل الرمز الذي تم إرساله إلى بريدك الإلكتروني." }, "continue": { "message": "استمرار" @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." }, - "moveManyToOrgDesc": { - "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved." - }, "collectionsDesc": { "message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "رمز التحقق (TOTP)" }, @@ -1536,7 +1632,7 @@ "message": "تصدير" }, "exportFrom": { - "message": "Export from" + "message": "التصدير من" }, "exportVault": { "message": "تصدير الخزانة" @@ -1560,10 +1656,10 @@ "message": "تأكيد التنسيق" }, "filePassword": { - "message": "File password" + "message": "كلمة مرور الملف" }, "confirmFilePassword": { - "message": "Confirm file password" + "message": "تأكيد كلمة مرور الملف" }, "accountRestrictedOptionDescription": { "message": "Use your account encryption key, derived from your account's username and Master Password, to encrypt the export and restrict import to only the current Bitwarden account." @@ -1575,7 +1671,7 @@ "message": "نوع التصدير" }, "accountRestricted": { - "message": "Account restricted" + "message": "تم تقييد الحساب" }, "passwordProtected": { "message": "Password protected" @@ -1613,38 +1709,35 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "إعادة توليد كلمة المرور" - }, "length": { "message": "الطول" }, "passwordMinLength": { - "message": "Minimum password length" + "message": "الحد الأدنى لطول كلمة المرور" }, "uppercase": { - "message": "Uppercase (A-Z)", + "message": "أحرف كبيرة (من A إلى Z)", "description": "deprecated. Use uppercaseLabel instead." }, "lowercase": { - "message": "Lowercase (a-z)", + "message": "أحرف صغيرة (من a إلى z)", "description": "deprecated. Use lowercaseLabel instead." }, "numbers": { - "message": "Numbers (0-9)", + "message": "أرقام (من 0 إلى 9)", "description": "deprecated. Use numbersLabel instead." }, "specialCharacters": { - "message": "Special characters (!@#$%^&*)" + "message": "أحرف خاصة (!@#$%^&*)" }, "numWords": { "message": "عدد الكلمات" }, "wordSeparator": { - "message": "Word separator" + "message": "فاصل الكلمات" }, "capitalize": { - "message": "Capitalize", + "message": "كبِّر الحروف", "description": "Make the first letter of a word uppercase." }, "includeNumber": { @@ -1658,7 +1751,7 @@ "message": "سجل كلمات المرور" }, "generatorHistory": { - "message": "Generator history" + "message": "سجل المولد" }, "clearGeneratorHistoryTitle": { "message": "Clear generator history" @@ -1667,16 +1760,16 @@ "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" }, "noPasswordsInList": { - "message": "There are no passwords to list." + "message": "لا توجد كلمات مرور للعرض." }, "clearHistory": { - "message": "Clear history" + "message": "مسح السجل" }, "nothingToShow": { - "message": "Nothing to show" + "message": "لا يوجد شيء لعرضه" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "لم تقم بتوليد أي شيء مؤخراً" }, "clear": { "message": "مسح", @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "الرجاء تسجيل الدخول مرة أخرى." }, + "currentSession": { + "message": "الجلسة الحالية" + }, + "requestPending": { + "message": "الطلب معلًق" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "منطقة خطرة" }, - "dangerZoneDesc": { - "message": "احذر، لن تستطيع التراجع عن هذه الإجراءات!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Deauthorize sessions" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Proceeding will also log you out of your current session, requiring you to log back in. You will also be prompted for two-step login again, if set up. Active sessions on other devices may continue to remain active for up to one hour." }, + "newDeviceLoginProtection": { + "message": "تسجيل الدخول لجهاز جديد" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "All sessions deauthorized" }, @@ -1837,13 +1951,13 @@ "message": "Proceed below to delete your account and all vault data." }, "deleteAccountWarning": { - "message": "Deleting your account is permanent. It cannot be undone." + "message": "حذف حسابك بشكل نهائي. لا يمكن التراجع عنه." }, "accountDeleted": { - "message": "Account deleted" + "message": "تم حذف الحساب" }, "accountDeletedDesc": { - "message": "Your account has been closed and all associated data has been deleted." + "message": "لقد تم إغلاق حسابك وتم حذف جميع البيانات المرتبطة به." }, "deleteOrganizationWarning": { "message": "Deleting your organization is permanent. It cannot be undone." @@ -1862,11 +1976,11 @@ "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { - "message": "new item", + "message": "عنصر جديد", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLoginLink": { - "message": "new login", + "message": "تسجيل دخول جديد", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new login instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsPartTwoNoOrgs": { @@ -1911,22 +2025,22 @@ "message": "Data is not formatted correctly. Please check your import file and try again." }, "importNothingError": { - "message": "Nothing was imported." + "message": "لم يتم استيراد أي شيء." }, "importEncKeyError": { "message": "Error decrypting the exported file. Your encryption key does not match the encryption key used export the data." }, "destination": { - "message": "Destination" + "message": "الوجهة" }, "learnAboutImportOptions": { "message": "Learn about your import options" }, "selectImportFolder": { - "message": "Select a folder" + "message": "تحديد المجلد" }, "selectImportCollection": { - "message": "Select a collection" + "message": "اختر مجموعة" }, "importTargetHint": { "message": "Select this option if you want the imported file contents moved to a $DESTINATION$", @@ -1951,13 +2065,13 @@ "message": "اختيار ملف" }, "noFileChosen": { - "message": "No file chosen" + "message": "لم يتم اختيار ملف" }, "orCopyPasteFileContents": { "message": "او قم بنسخ ولصق محتويات مِلَفّ الاستيراد" }, "instructionsFor": { - "message": "$NAME$ Instructions", + "message": "تعليمات $NAME$", "description": "The title for the import tool instructions.", "placeholders": { "name": { @@ -1976,7 +2090,7 @@ "message": "تخصيص تجرِبة خزانة الويب الخاصة بك." }, "preferencesUpdated": { - "message": "Preferences saved" + "message": "تم حفظ التفضيلات" }, "language": { "message": "اللّغة" @@ -1991,7 +2105,7 @@ "message": "Show a recognizable image next to each login." }, "default": { - "message": "Default" + "message": "الافتراضي" }, "domainRules": { "message": "قواعد النطاق" @@ -2006,13 +2120,13 @@ "message": "Custom equivalent domains" }, "exclude": { - "message": "Exclude" + "message": "استثناء" }, "include": { - "message": "Include" + "message": "تضمين" }, "customize": { - "message": "Customize" + "message": "تخصيص" }, "newCustomDomain": { "message": "New custom domain" @@ -2060,21 +2174,24 @@ "twoStepLoginRecoveryWarning": { "message": "Setting up two-step login can permanently lock you out of your Bitwarden account. A recovery code allows you to access your account in the event that you can no longer use your normal two-step login provider (example: you lose your device). Bitwarden support will not be able to assist you if you lose access to your account. We recommend you write down or print the recovery code and keep it in a safe place." }, + "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." + }, "viewRecoveryCode": { "message": "View recovery code" }, "providers": { - "message": "Providers", + "message": "المزودون", "description": "Two-step login providers such as YubiKey, Duo, Authenticator apps, Email, etc." }, "enable": { - "message": "Turn on" + "message": "تفعيل" }, "enabled": { - "message": "Turned on" + "message": "تم التفعيل" }, "restoreAccess": { - "message": "Restore access" + "message": "استعادة الوصول" }, "premium": { "message": "بريميوم", @@ -2098,14 +2215,29 @@ "manage": { "message": "إدارة" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "عرض العناصر" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "تعديل العناصر" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { - "message": "Turn off" + "message": "إيقاف" }, "revokeAccess": { - "message": "Revoke access" + "message": "إلغاء الوصول" + }, + "revoke": { + "message": "Revoke" }, "twoStepLoginProviderEnabled": { "message": "This two-step login provider is active on your account." @@ -2117,16 +2249,16 @@ "message": "Download an authenticator app such as" }, "twoStepAuthenticatorInstructionInfix1": { - "message": "," + "message": "،" }, "twoStepAuthenticatorInstructionInfix2": { - "message": "or" + "message": "أو" }, "twoStepAuthenticatorInstructionSuffix": { "message": "." }, "continueToExternalUrlTitle": { - "message": "Continue to $URL$?", + "message": "هل تريد المتابعة إلى $URL$؟", "placeholders": { "url": { "content": "$1", @@ -2138,7 +2270,7 @@ "message": "You are leaving Bitwarden and launching an external website in a new window." }, "twoStepContinueToBitwardenUrlTitle": { - "message": "Continue to bitwarden.com?" + "message": "هل تريد المتابعة إلى 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." @@ -2150,10 +2282,10 @@ "message": "Could not load QR code. Try again or use the key below." }, "key": { - "message": "Key" + "message": "المفتاح" }, "twoStepAuthenticatorEnterCodeV2": { - "message": "Verification code" + "message": "رمز التحقق" }, "twoStepAuthenticatorReaddDesc": { "message": "In case you need to add it to another device, below is the QR code (or key) required by your authenticator app." @@ -2177,7 +2309,7 @@ "message": "Touch the YubiKey's button." }, "twoFactorYubikeySaveForm": { - "message": "Save the form." + "message": "حفظ النموذج." }, "twoFactorYubikeyWarning": { "message": "Due to platform limitations, YubiKeys cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when YubiKeys cannot be used. Supported platforms:" @@ -2234,7 +2366,7 @@ "message": "Enter the Bitwarden application information from your Duo Admin panel." }, "twoFactorDuoClientId": { - "message": "Client Id" + "message": "معرّف العميل" }, "twoFactorDuoClientSecret": { "message": "Client Secret" @@ -2279,7 +2411,7 @@ "message": "If the security key has a button, touch it." }, "twoFactorU2fSaveForm": { - "message": "Save the form." + "message": "حفظ النموذج." }, "twoFactorU2fWarning": { "message": "Due to platform limitations, FIDO U2F cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when FIDO U2F cannot be used. Supported platforms:" @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "There was a problem reading the security key. Try again." }, - "twoFactorWebAuthnWarning": { - "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used. Supported platforms:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Web vault and browser extensions on a desktop/laptop with a WebAuthn supported browser (Chrome, Opera, Vivaldi, or Firefox with FIDO U2F turned on)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Your Bitwarden two-step login recovery code" @@ -2309,7 +2438,7 @@ "message": "You have not set up any two-step login providers yet. After you have set up a two-step login provider you can check back here for your recovery code." }, "printCode": { - "message": "Print code", + "message": "طباعة الرمز", "description": "Print 2FA recovery code" }, "reports": { @@ -2324,13 +2453,13 @@ "description": "Vault health reports can be used to evaluate the security of your Bitwarden individual or organization vault." }, "unsecuredWebsitesReport": { - "message": "Unsecure websites" + "message": "مواقع الويب غير الآمنة" }, "unsecuredWebsitesReportDesc": { "message": "URLs that start with http:// don’t use the best available encryption. Change the login URIs for these accounts to https:// for safer browsing." }, "unsecuredWebsitesFound": { - "message": "Unsecured websites found" + "message": "تم العثور على مواقع ويب غير آمنة" }, "unsecuredWebsitesFoundReportDesc": { "message": "We found $COUNT$ items in your $VAULT$ with unsecured URIs. You should change their URI scheme to https:// if the website allows it.", @@ -2374,7 +2503,7 @@ "message": "No websites were found in your vault with a missing two-step login configuration." }, "instructions": { - "message": "Instructions" + "message": "التعليمات" }, "exposedPasswordsReport": { "message": "Exposed passwords" @@ -2417,13 +2546,13 @@ } }, "weakPasswordsReport": { - "message": "Weak passwords" + "message": "كلمات المرور الضعيفة" }, "weakPasswordsReportDesc": { "message": "Weak passwords can be easily guessed by attackers. Change these passwords to strong ones using the password generator." }, "weakPasswordsFound": { - "message": "Weak passwords found" + "message": "تم العثور على كلمات مرور ضعيفة" }, "weakPasswordsFoundReportDesc": { "message": "We found $COUNT$ items in your $VAULT$ with passwords that are not strong. You should update them to use stronger passwords.", @@ -2470,10 +2599,10 @@ "message": "No logins in your vault have passwords that are being reused." }, "timesReused": { - "message": "Times reused" + "message": "عدد مرات إعادة الاستخدام" }, "reusedXTimes": { - "message": "Reused $COUNT$ times", + "message": "تمت إعادة الاستخدام $COUNT$ مرة", "placeholders": { "count": { "content": "$1", @@ -2503,7 +2632,7 @@ } }, "goodNews": { - "message": "Good news", + "message": "أخبار جيدة!", "description": "ex. Good News, No Breached Accounts Found!" }, "breachUsernameFound": { @@ -2529,7 +2658,7 @@ "message": "موقع الويب" }, "affectedUsers": { - "message": "Affected users" + "message": "المستخدمون المتأثرون" }, "breachOccurred": { "message": "Breach occurred" @@ -2541,24 +2670,24 @@ "message": "An error occurred trying to load the report. Try again" }, "billing": { - "message": "Billing" + "message": "الفوترة" }, "billingPlanLabel": { - "message": "Billing plan" + "message": "خطة الفوترة" }, "paymentType": { - "message": "Payment type" + "message": "طريقة الدفع" }, "accountCredit": { "message": "Account credit", "description": "Financial term. In the case of Bitwarden, a positive balance means that you owe money, while a negative balance means that you have a credit (Bitwarden owes you money)." }, "accountBalance": { - "message": "Account balance", + "message": "رصيد الحساب", "description": "Financial term. In the case of Bitwarden, a positive balance means that you owe money, while a negative balance means that you have a credit (Bitwarden owes you money)." }, "addCredit": { - "message": "Add credit", + "message": "إضافة رصيد", "description": "Add more credit to your account's balance." }, "amount": { @@ -2631,10 +2760,10 @@ "message": "Bitwarden Families plan." }, "addons": { - "message": "Add-ons" + "message": "الإضافات" }, "premiumAccess": { - "message": "Premium access" + "message": "الوصول المميز" }, "premiumAccessDesc": { "message": "You can add Premium access to all members of your organization for $PRICE$ /$INTERVAL$.", @@ -2682,13 +2811,13 @@ "message": "سنة" }, "yr": { - "message": "yr" + "message": "سنة" }, "month": { "message": "شهر" }, "monthAbbr": { - "message": "mo.", + "message": "شهر", "description": "Short abbreviation for 'month'" }, "paymentChargedAnnually": { @@ -2710,10 +2839,10 @@ "message": "Your plan comes with a free 7 day trial. Your payment method will not be charged until the trial has ended. You may cancel at any time." }, "paymentInformation": { - "message": "Payment information" + "message": "معلومات الدفع" }, "billingInformation": { - "message": "Billing information" + "message": "معلومات الفوترة" }, "billingTrialSubLabel": { "message": "Your payment method will not be charged during the 7 day free trial." @@ -2725,13 +2854,13 @@ "message": "Select the PayPal button to log into your PayPal account, then click the Submit button below to continue." }, "cancelSubscription": { - "message": "Cancel subscription" + "message": "إلغاء الاشتراك" }, "subscriptionExpiration": { - "message": "Subscription expiration" + "message": "تاريخ انتهاء الاشتراك" }, "subscriptionCanceled": { - "message": "The subscription has been canceled." + "message": "تم إلغاء الاشتراك." }, "pendingCancellation": { "message": "Pending cancellation" @@ -2752,40 +2881,40 @@ "message": "Are you sure you want to cancel? You will lose access to all of this subscription's features at the end of this billing cycle." }, "canceledSubscription": { - "message": "Subscription canceled" + "message": "تم إلغاء الإشتراك" }, "neverExpires": { - "message": "Never expires" + "message": "صلاحية غير محدودة" }, "status": { - "message": "Status" + "message": "الحالة" }, "nextCharge": { - "message": "Next charge" + "message": "الدفعة القادمة" }, "details": { "message": "التفاصيل" }, "downloadLicense": { - "message": "Download license" + "message": "تنزيل الرخصة" }, "viewBillingToken": { "message": "View Billing Token" }, "updateLicense": { - "message": "Update license" + "message": "تحديث الرخصة" }, "manageSubscription": { - "message": "Manage subscription" + "message": "إدارة الاشتراك" }, "launchCloudSubscription": { "message": "Launch Cloud Subscription" }, "storage": { - "message": "Storage" + "message": "مساحة التخزين" }, "addStorage": { - "message": "Add storage" + "message": "إضافة مساحة تخزين" }, "removeStorage": { "message": "Remove storage" @@ -2804,47 +2933,47 @@ } }, "paymentMethod": { - "message": "Payment method" + "message": "طريقة الدفع" }, "noPaymentMethod": { "message": "No payment method on file." }, "addPaymentMethod": { - "message": "Add payment method" + "message": "إضافة طريقة دفع" }, "changePaymentMethod": { - "message": "Change payment method" + "message": "تغيير طريقة الدفع" }, "invoices": { - "message": "Invoices" + "message": "الفواتير" }, "noUnpaidInvoices": { - "message": "No unpaid invoices." + "message": "لا يوجد فواتير غير مدفوعة." }, "noPaidInvoices": { - "message": "No paid invoices." + "message": "لا يوجد فواتير مدفوعة." }, "paid": { - "message": "Paid", + "message": "مدفوع", "description": "Past tense status of an invoice. ex. Paid or unpaid." }, "unpaid": { - "message": "Unpaid", + "message": "غير مدفوع", "description": "Past tense status of an invoice. ex. Paid or unpaid." }, "transactions": { - "message": "Transactions", + "message": "الحركات المالية", "description": "Payment/credit transactions." }, "noTransactions": { - "message": "No transactions." + "message": "لا يوجد حركات مالية." }, "chargeNoun": { "message": "Charge", "description": "Noun. A charge from a payment method." }, "refundNoun": { - "message": "Refund", + "message": "استرداد مالي", "description": "Noun. A refunded payment that was charged." }, "chargesStatement": { @@ -2881,7 +3010,7 @@ "message": "Contact customer support" }, "contactSupportShort": { - "message": "Contact Support" + "message": "الاتصال بالدعم الفني" }, "updatedPaymentMethod": { "message": "Updated payment method." @@ -2926,7 +3055,7 @@ "message": "البريد الإلكتروني للفوترة" }, "businessName": { - "message": "Business name" + "message": "الاسم التجاري" }, "chooseYourPlan": { "message": "اختر خِطَّة اشتراكك" @@ -2973,7 +3102,7 @@ } }, "planNameFamilies": { - "message": "Families" + "message": "العائلات" }, "planDescFamilies": { "message": "For personal use, to share with family & friends." @@ -3006,7 +3135,7 @@ } }, "additionalUsers": { - "message": "Additional users" + "message": "مستخدمين إضافيين" }, "costPerUser": { "message": "$COST$ per user", @@ -3123,13 +3252,13 @@ "message": "شهريا" }, "annually": { - "message": "Annually" + "message": "سنويّاً" }, "annual": { - "message": "Annual" + "message": "سنوي" }, "basePrice": { - "message": "Base price" + "message": "السعر الأساسي" }, "organizationCreated": { "message": "Organization created" @@ -3141,7 +3270,7 @@ "message": "Organization upgraded" }, "leave": { - "message": "Leave" + "message": "خروج" }, "leaveOrganizationConfirmation": { "message": "Are you sure you want to leave this organization?" @@ -3225,25 +3354,25 @@ "message": "Access control" }, "readOnly": { - "message": "Read only" + "message": "للقراءة فقط" }, "newCollection": { - "message": "New collection" + "message": "مجموعة جديدة" }, "addCollection": { - "message": "Add collection" + "message": "إضافة مجموعة" }, "editCollection": { - "message": "Edit collection" + "message": "تعديل المجموعة" }, "collectionInfo": { - "message": "Collection info" + "message": "بيانات المجموعة" }, "deleteCollectionConfirmation": { - "message": "Are you sure you want to delete this collection?" + "message": "هل أنت متأكد من أنك تريد حذف هذه المجموعة؟" }, "editMember": { - "message": "Edit member" + "message": "تعديل العضو" }, "fieldOnTabRequiresAttention": { "message": "A field on the '$TAB$' tab requires your attention.", @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "تبقى لك دعوة واحدة." + }, + "inviteZeroEmailDesc": { + "message": "لم يتبق لك أي دعوات." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3321,10 +3456,10 @@ "message": "Event" }, "unknown": { - "message": "Unknown" + "message": "مجهول" }, "loadMore": { - "message": "Load more" + "message": "تحميل المزيد" }, "mobile": { "message": "Mobile", @@ -3381,7 +3516,7 @@ "message": "Incorrect PIN" }, "pin": { - "message": "PIN", + "message": "رقم التعريف الشخصي", "description": "PIN code. Ex. The short code (often numeric) that you use to unlock a device." }, "exportedVault": { @@ -3553,10 +3688,10 @@ } }, "deletedCollections": { - "message": "Deleted collections" + "message": "المجموعات المحذوفة" }, "deletedCollectionId": { - "message": "Deleted collection $ID$.", + "message": "المجموعة المحذوفة $ID$.", "placeholders": { "id": { "content": "$1", @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "الجهاز" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3781,13 +3989,13 @@ "message": "No email?" }, "goBack": { - "message": "Go back" + "message": "الرجوع إلى الخلف" }, "toEditYourEmailAddress": { "message": "to edit your email address." }, "view": { - "message": "View" + "message": "عرض" }, "invalidDateRange": { "message": "Invalid date range." @@ -3796,10 +4004,10 @@ "message": "لقد حدث خطأ." }, "userAccess": { - "message": "User access" + "message": "وصول المستخدم" }, "userType": { - "message": "User type" + "message": "نوع المستخدم" }, "groupAccess": { "message": "Group access" @@ -3814,7 +4022,7 @@ "message": "Resend invitation" }, "resendEmail": { - "message": "Resend email" + "message": "إعادة إرسال البريد الإلكتروني" }, "hasBeenReinvited": { "message": "$USER$ reinvited", @@ -3826,10 +4034,10 @@ } }, "confirm": { - "message": "Confirm" + "message": "تأكيد" }, "confirmUser": { - "message": "Confirm user" + "message": "تأكيد المستخدم" }, "hasBeenConfirmed": { "message": "$USER$ confirmed.", @@ -3841,7 +4049,7 @@ } }, "confirmUsers": { - "message": "Confirm members" + "message": "تأكيد الأعضاء" }, "usersNeedConfirmed": { "message": "You have members that have accepted their invitation, but still need to be confirmed. Members will not have access to the organization until they are confirmed." @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "If you cannot access your account through your normal two-step login methods, you can use your two-step login recovery code to turn off all two-step providers on your account." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Recover account two-step login" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Set requirements for master password strength." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Require two-step login" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Set requirements for password generator." }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." - }, "masterPasswordPolicyInEffect": { "message": "One or more organization policies require your master password to meet the following requirements:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Organization owners and admins are exempt from this policy's enforcement." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "ملف" }, "sendTypeText": { "message": "نص" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "إرسال جديد", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Delete Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Are you sure you want to delete this Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "What type of Send is this?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maximum access count" }, - "maxAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Disabled" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Are you sure you want to remove the password?" }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, - "disableThisSend": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "All Sends" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Pending deletion" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Expired" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "The following organization policies are currently in effect:" - }, - "sendDisableHideEmailInEffect": { - "message": "Users are not allowed 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." - }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Remove individual ownership for organization users" }, - "textHiddenByDefault": { - "message": "When accessing the Send, hide the text by default", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to Send." - }, - "sendFileDesc": { - "message": "The file you want to Send." - }, - "copySendLinkOnSave": { - "message": "Copy the link to share this Send to my clipboard upon save." - }, - "sendLinkLabel": { - "message": "Send link", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "To verify your 2FA please click the button below." }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn is not supported in this browser." }, @@ -5655,6 +5916,20 @@ "error": { "message": "خطأ" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "المولّد", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "ما الذي ترغب في توليده؟" - }, - "passwordType": { - "message": "نوع كلمة المرور" - }, - "regenerateUsername": { - "message": "إعادة توليد اسم المستخدم" - }, "generateUsername": { "message": "Generate username" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Username type" - }, "plusAddressedEmail": { "message": "Plus addressed email", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "اسم المضيف", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API access token" - }, "deviceVerification": { "message": "Device verification" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Number of users" }, - "loggingInAs": { - "message": "تسجيل الدخول كـ" - }, - "notYou": { - "message": "ليس حسابك؟" - }, "pickAnAvatarColor": { "message": "اختر لون الصورة الرمزية" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "No collection" }, - "canView": { - "message": "Can view" - }, - "canViewExceptPass": { - "message": "Can view, except passwords" - }, - "canEdit": { - "message": "Can edit" - }, - "canEditExceptPass": { - "message": "Can edit, except passwords" - }, "noCollectionsAdded": { "message": "No collections added" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domain" - }, "alreadyHaveAccount": { "message": "Already have an account?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index b8111a0e997..f092620b00a 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Kritik tətbiqlər" }, + "noCriticalAppsAtRisk": { + "message": "Risk altında heç bir kritik tətbiq yoxdur" + }, "accessIntelligence": { "message": "Müraciət Kəşfiyyatı" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "Riskli üzvlər" }, + "atRiskMembersWithCount": { + "message": "Risk altındakı üzvlər ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Risk altındakı tətbiqlər ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "Bu üzvlər, tətbiqlərə zəif, ifşa olunmuş və ya təkrar istifadə olunan parollarla giriş edir." + }, + "atRiskApplicationsDescription": { + "message": "Bu tətbiqlər zəif, ifşa olunmuş və ya təkrar istifadə edilmiş parollara sahibdir." + }, + "atRiskMembersDescriptionWithApp": { + "message": "Bu üzvlər, $APPNAME$ tətbiqinə zəif, ifşa olunmuş və ya təkrar istifadə olunan parollarla giriş edir.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Cəmi üzv" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Cəmi tətbiq" }, + "unmarkAsCriticalApp": { + "message": "Kritik tətbiq kimi işarəni götür" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Kritik tətbiq işarəsi uğurla götürüldü" + }, "whatTypeOfItem": { "message": "Bu elementin növü nədir?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Notlar" }, + "privateNote": { + "message": "Şəxsi not" + }, "note": { "message": "Not" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Sıralamaq üçün sürüklə" }, + "dragToReorder": { + "message": "Yenidən sıralamaq üçün sürüklə" + }, "cfTypeText": { "message": "Mətn" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Qovluğa düzəliş et" }, + "editWithName": { + "message": "$ITEM$ - düzəliş et: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "Yeni qovluq" + }, + "folderName": { + "message": "Qovluq adı" + }, + "folderHintText": { + "message": "Ana qovluğun adından sonra \"/\" əlavə edərək qovluğu ardıcıl yerləşdirin. Nümunə: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Bu qovluğu həmişəlik silmək istədiyinizə əminsiniz?" + }, "baseDomain": { "message": "Baza domeni", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Element adı" }, - "cannotRemoveViewOnlyCollections": { - "message": "\"Yalnız baxma\" icazələrinə sahib kolleksiyaları silə bilməzsiniz: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "məs.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filtr" }, - "moveSelectedToOrg": { - "message": "Seçiləni təşkilata daşı" - }, "deleteSelected": { "message": "Seçiləni sil" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Seçilən elementlər $ORGNAME$ təşkilatına daşınıldı", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Elementlər bura daşındı: $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "Xeyr" }, + "location": { + "message": "Yerləşmə" + }, "loginOrCreateNewAccount": { "message": "Güvənli seyfinizə müraciət etmək üçün giriş edin və ya yeni bir hesab yaradın." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Bitwarden-ə giriş edin" }, + "enterTheCodeSentToYourEmail": { + "message": "E-poçtunuza göndərilən kodu daxil edin" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Kimlik doğrulayıcı tətbiqinizdəki kodu daxil edin" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Kimliyi doğrulamaq üçün YubiKey-inizə basın" + }, "authenticationTimeout": { "message": "Kimlik doğrulama vaxtı bitdi" }, "authenticationSessionTimedOut": { "message": "Kimlik doğrulama seansının vaxtı bitdi. Lütfən giriş prosesini yenidən başladın." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Kimliyinizi doğrulayın" }, + "weDontRecognizeThisDevice": { + "message": "Bu cihazı tanımırıq. Kimliyinizi doğrulamaq üçün e-poçtunuza göndərilən kodu daxil edin." + }, + "continueLoggingIn": { + "message": "Giriş etməyə davam" + }, + "whatIsADevice": { + "message": "Cihaz nədir?" + }, + "aDeviceIs": { + "message": "Cihaz, giriş etdiyiniz Bitwarden tətbiqinin unikal quraşdırmasıdır. Yenidən quraşdırılması, tətbiq datasının təmizlənməsi və ya çərəzlərin təmizlənməsi, cihazın bir neçə dəfə görünməsinə səbəb ola bilər." + }, "logInInitiated": { "message": "Giriş etmə başladıldı" }, + "logInRequestSent": { + "message": "Tələb göndərildi" + }, "submit": { "message": "Göndər" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Hesabınızın e-poçt ünvanını daxil edin və parolunuz üçün ipucu sizə göndəriləcək" }, - "passwordHint": { - "message": "Parol məsləhəti" - }, - "enterEmailToGetHint": { - "message": "Ana parol məsləhətini alacağınız hesabınızın e-poçt ünvanını daxil edin." - }, "getMasterPasswordHint": { "message": "Ana parol üçün məsləhət alın" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "Cihazınıza bir bildiriş göndərildi." }, + "notificationSentDevicePart1": { + "message": "Cihazınızda Bitwarden kilidini açın, ya da " + }, + "areYouTryingToAccessYourAccount": { + "message": "Hesabınıza müraciət etməyə çalışırsınız?" + }, + "accessAttemptBy": { + "message": "$EMAIL$ ilə müraciət cəhdi", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Müraciəti təsdiqlə" + }, + "denyAccess": { + "message": "Müraciətə rədd cavabı ver" + }, + "notificationSentDeviceAnchor": { + "message": "veb tətbiqinizdə" + }, + "notificationSentDevicePart2": { + "message": "Təsdiqləməzdən əvvəl Barmaq izi ifadəsinin aşağıdakı ifadə ilə uyuşduğuna əmin olun." + }, + "notificationSentDeviceComplete": { + "message": "Cihazınızda Bitwarden-in kilidini açın. Təsdiqləməzdən əvvəl Barmaq izi ifadəsinin aşağıdakı ifadə ilə uyuşduğuna əmin olun." + }, "aNotificationWasSentToYourDevice": { "message": "Cihazınıza bir bildiriş göndərildi" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Hesabınızın kilidinin açıq olduğuna və barmaq izi ifadəsinin digər cihazda uyuşduğuna əmin olun" - }, "versionNumber": { "message": "Versiya $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Məni xatırla" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Bu cihazda 30 gün ərzində soruşulmasın" + }, "sendVerificationCodeEmailAgain": { "message": "Doğrulama kodu olan e-poçtu yenidən göndər" }, "useAnotherTwoStepMethod": { "message": "Başqa bir iki addımlı giriş üsulu istifadə edin" }, + "selectAnotherMethod": { + "message": "Başqa üsul seçin", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Geri qaytarma kodunuzu istifadə edin" + }, "insertYubiKey": { "message": "\"YubiKey\"i kompüterinizin USB portuna taxın, daha sonra düyməsinə toxunun." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "İki addımlı giriş seçimləri" }, + "selectTwoStepLoginMethod": { + "message": "İki addımlı giriş üsulunu seçin" + }, "recoveryCodeDesc": { "message": "İki addımlı giriş provayderlərinə müraciəti itirmisiniz? Hesabınızdakı bütün iki addımlı giriş provayderlərini söndürmək üçün geri qaytarma kodunuzu istifadə edin." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(FIDO-dan köçürüldü)" }, + "openInNewTab": { + "message": "Yeni vərəqdə aç" + }, "emailTitle": { "message": "E-poçt" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Bu elementi daşımaq istədiyiniz təşkilatı seçin. Bir təşkilata daşımaq, elementin sahibliyini də həmin təşkilata daşıyacaq. Daşıdıqdan sonra bu elementə birbaşa sahibliyiniz olmayacaq." }, - "moveManyToOrgDesc": { - "message": "Bu elementləri daşımaq istədiyiniz təşkilatı seçin. Bir təşkilata daşımaq, elementin sahibliyini də həmin təşkilata daşıyacaq. Daşıdıqdan sonra bu elementlərə birbaşa sahibliyiniz olmayacaq." - }, "collectionsDesc": { "message": "Bu elementin paylaşıldığı kolleksiyalara düzəliş edin. Yalnız bu kolleksiyalara müraciəti olan təşkilat istifadəçiləri bu elementi görə bilər." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "$COUNT$ element seçdiniz. $MOVEABLE_COUNT$ element bir təşkilata daşınıla bilər, $NONMOVEABLE_COUNT$ ədədi isə daşınıla bilməz.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Doğrulama kodu (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Anlaşılmaz xarakterlərdən çəkin", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Parolu yenidən yarat" - }, "length": { "message": "Uzunluq" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Lütfən yenidən giriş edin." }, + "currentSession": { + "message": "Hazırkı seans" + }, + "requestPending": { + "message": "Tələb gözlənir" + }, "logBackInOthersToo": { "message": "Lütfən yenidən giriş edin. Digər Bitwarden tətbiqlərini istifadə edirsinizsə, onlardan da çıxış edib təkrar giriş etməlisiniz." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Təhlükəli zona" }, - "dangerZoneDesc": { - "message": "Diqqətli olun, bu əməliyyatları geri qaytara bilməzsiniz!" - }, - "dangerZoneDescSingular": { - "message": "Diqqətli olun, bu əməliyyatı geri qaytara bilməzsiniz!" - }, "deauthorizeSessions": { "message": "Seansların səlahiyyətlərini götür" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Davam etsəniz, hazırkı seansınız bitəcək, təkrar giriş etməyiniz tələb olunacaq. Fəallaşdırılıbsa, iki addımlı giriş üçün yenidən soruşulacaq. Digər cihazlardakı aktiv seanslar, bir saata qədər aktiv qalmağa davam edə bilər." }, + "newDeviceLoginProtection": { + "message": "Yeni cihaz girişi" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Yeni cihaz girişi qorumasını söndür" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Yeni cihaz girişi qorumasını işə sal" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Yeni bir cihazdan giriş etdiyiniz zaman Bitwarden göndərən doğrulama e-poçtlarını dayandırmaq üçün aşağıdakı addımları izləyin." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Yeni bir cihazdan giriş etdiyiniz zaman Bitwarden-in sizə doğrulama e-poçtlarını göndərməsi üçün aşağıdakı addımları izləyin." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "Yeni cihaz girişi qorumasını söndürsəniz, ana parolunuzu bilən hər kəs, istənilən cihazdan hesabınıza müraciət edə bilər. Hesabınızı doğrulama e-poçtları olmadan qorumaq üçün iki addımlı girişi qurun." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "Yeni cihaz girişi qoruması dəyişiklikləri saxlanıldı" + }, "sessionsDeauthorized": { "message": "Bütün seansların səlahiyyəti götürüldü" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "İki addımlı girişi qurmaq, Bitwarden hesabınızı birdəfəlik kilidləyə bilər. Geri qaytarma kodu, normal iki addımlı giriş provayderinizi artıq istifadə edə bilmədiyiniz hallarda (məs. cihazınızı itirəndə) hesabınıza müraciət etməyinizə imkan verir. Hesabınıza müraciəti itirsəniz, Bitwarden dəstəyi sizə kömək edə bilməyəcək. Geri qaytarma kodunuzu bir yerə yazmağınızı və ya çap etməyinizi və onu etibarlı bir yerdə saxlamağınızı məsləhət görürük." }, + "yourSingleUseRecoveryCode": { + "message": "İki addımlı giriş provayderinizə müraciəti itirdiyiniz halda, iki addımlı girişi söndürmək üçün təkistifadəlik geri qaytarma kodunu istifadə edə bilərsiniz. Bitwarden tövsiyə edir ki, geri qaytarma kodunuzu bir yerə yazıb güvənli bir yerdə saxlayın." + }, "viewRecoveryCode": { "message": "Geri qaytarma koduna bax" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "İdarə et" }, - "canManage": { - "message": "İdarə edə bilər" + "manageCollection": { + "message": "Kolleksiyanı idarə et" + }, + "viewItems": { + "message": "Elementlərə bax" + }, + "viewItemsHidePass": { + "message": "Elementlərə, gizli parollara bax" + }, + "editItems": { + "message": "Elementlərə düzəliş et" + }, + "editItemsHidePass": { + "message": "Elementlərə, gizli parollara düzəliş et" }, "disable": { "message": "Sıradan çıxart" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Müraciəti ləğv et" }, + "revoke": { + "message": "Geri al" + }, "twoStepLoginProviderEnabled": { "message": "Bu iki addımlı giriş provayderi hesabınızda fəallaşdırılıb." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "Güvənlik açarı oxunarkən problem yarandı. Yenidən sınayın." }, - "twoFactorWebAuthnWarning": { - "message": "Platforma məhdudiyyətlərinə görə, WebAuthn bütün Bitwarden tətbiqlərində istifadə edilə bilmir. WebAuthn istifadə edilə bilməyəndə, hesabınıza müraciət edə bilməyiniz üçün başqa bir iki addımlı giriş provayderini fəallaşdırmalısınız. Dəstəklənən platformalar:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "WebAuthn dəstəkli brauzerə sahib masaüstü/dizüstü kompüterdə veb seyf və brauzer uzantıları (FIDO U2F açıq olan Chrome, Opera, Vivaldi və ya Firefox)." + "twoFactorWebAuthnWarning1": { + "message": "Platforma məhdudiyyətlərinə görə, WebAuthn bütün Bitwarden tətbiqlərində istifadə edilə bilmir. WebAuthn istifadə edilə bilməyəndə, hesabınıza müraciət edə bilməyiniz üçün başqa bir iki addımlı giriş provayderini fəallaşdırmalısınız." }, "twoFactorRecoveryYourCode": { "message": "Bitwarden iki addımlı giriş üçün geri qaytarma kodunuz" @@ -2941,7 +3070,7 @@ "message": "Əlavə istifadəçi yerləri" }, "userSeatsDesc": { - "message": "İstifadəçi sayı" + "message": "# / istifadəçi yeri" }, "userSeatsAdditionalDesc": { "message": "Planınızda $BASE_SEATS$ istifadəçi yeri var. İstifadəçiləri, istifadəçi/ay başına $SEAT_PRICE$ qarşılığında əlavə edə bilərsiniz.", @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "1 dəvət haqqınız var." + }, + "inviteZeroEmailDesc": { + "message": "0 dəvət haqqınız var." + }, "userUsingTwoStep": { "message": "Bu istifadəçinin hesabını qorumaq üçün iki addımlı giriş istifadə edilir." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Əlaqələndirilməmiş SSO." + }, "unlinkedSsoUser": { "message": "$ID$ istifadəçisi üçün SSO əlaqəsi kəsildi.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Cihaz" }, + "loginStatus": { + "message": "Giriş statusu" + }, + "firstLogin": { + "message": "İlk giriş" + }, + "trusted": { + "message": "Güvənli" + }, + "needsApproval": { + "message": "Təsdiq lazımdır" + }, + "areYouTryingtoLogin": { + "message": "Giriş etməyə çalışırsınız?" + }, + "logInAttemptBy": { + "message": "$EMAIL$ tərəfindən giriş cəhdi", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Cihaz növü" + }, + "ipAddress": { + "message": "IP Ünvan" + }, + "confirmLogIn": { + "message": "Girişi təsdiqlə" + }, + "denyLogIn": { + "message": "Girişə rədd cavabı ver" + }, + "thisRequestIsNoLongerValid": { + "message": "Bu tələb artıq yararsızdır." + }, + "logInConfirmedForEmailOnDevice": { + "message": "$DEVICE$ cihazında $EMAIL$ üçün giriş təsdiqləndi", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "Başqa bir cihazdan giriş cəhdinə rədd cavabı verdiniz. Bu həqiqətən siz idinizsə, cihazla yenidən giriş etməyə çalışın." + }, + "loginRequestHasAlreadyExpired": { + "message": "Giriş tələbinin müddəti artıq bitib." + }, + "justNow": { + "message": "İndicə" + }, + "requestedXMinutesAgo": { + "message": "$MINUTES$ dəqiqə əvvəl tələb göndərildi", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Hesab yaradılır" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "Dəstəklənməyən bir veb brauzer istifadə edirsiniz. Veb seyf düzgün işləməyə bilər." }, + "youHaveAPendingLoginRequest": { + "message": "Başqa bir cihazdan gözləyən bir giriş tələbiniz var." + }, + "reviewLoginRequest": { + "message": "Giriş tələbini incələ" + }, "freeTrialEndPromptCount": { "message": "Ödənişsiz sınaq müddətiniz $COUNT$ günə bitir.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "Hesabınıza normal iki addımlı giriş üsulları ilə müraciət edə bilmirsinizsə, hesabınızdakı bütün iki addımlı provayderləri söndürmək üçün iki addımlı giriş üzrə geri qaytarma kodunuzu istifadə edə bilərsiniz." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Təkistifadəlik geri qaytarma kodunuzu istifadə edərək aşağıda giriş edin. Bu, hesabınızdakı bütün iki addımlı provayderləri söndürəcək." + }, "recoverAccountTwoStep": { "message": "İki addımlı giriş ilə hesabı geri qaytarın" }, @@ -4133,7 +4350,7 @@ "message": "Sirr Menecerinə abunəliyiniz üçün bir limit müəyyən edin. Bu limitə çatanda, yeni istifadəçiləri dəvət edə bilməyəcəksiniz." }, "maxSeatLimit": { - "message": "Maksimum yer limiti (ixtiyari)", + "message": "Yer limiti (ixtiyari)", "description": "Upper limit of seats to allow through autoscaling" }, "maxSeatCost": { @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Şifrələmə açarı güncəlləməsi davam edə bilmir" }, + "editFieldLabel": { + "message": "$LABEL$ - düzəliş et", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "$LABEL$ - yenidən sırala. Ox düyməsi ilə elementi yuxarı və ya aşağı daşıyın.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ yuxarı daşındı, mövqeyi $INDEX$/$LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ aşağı daşındı, mövqeyi $INDEX$/$LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "Şifrələmə açarınızı güncəlləyərkən qovluqlarınızın şifrəsi açıla bilmədi. Güncəlləmənin davam etməsi üçün qovluqlarınız silinməlidir. Davam etsəniz, heç bir seyf elementi silinməyəcək." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Ana parol gücü üçün tələbləri ayarla." }, + "passwordStrengthScore": { + "message": "Parolun güc xalı: $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "İki addımlı girişi tələb et" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Parol yaradıcı üçün tələbləri ayarla." }, - "passwordGeneratorPolicyInEffect": { - "message": "Bir və ya daha çox təşkilat siyasətləri yaradıcı seçimlərinizə təsir edir." - }, "masterPasswordPolicyInEffect": { "message": "Bir və ya daha çox təşkilat siyasəti, aşağıdakı tələbləri qarşılamaq üçün ana parolunuzu tələb edir:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Təşkilat sahibləri və administratorlar, bu siyasətin tətbiq edilməsindən azaddırlar." }, + "limitSendViews": { + "message": "Baxışları limitlə" + }, + "limitSendViewsHint": { + "message": "Limitə çatdıqdan sonra bu Send-ə heç kim baxa bilməz.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ baxış qaldı", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send detalları", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Paylaşılacaq mətn" + }, "sendTypeFile": { "message": "Fayl" }, "sendTypeText": { "message": "Mətn" }, + "sendPasswordDescV3": { + "message": "Alıcıların bu \"Send\"ə müraciət etməsi üçün ixtiyari bir parol əlavə edin.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Yeni \"Send\" yarat", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "\"Send\"i sil", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Bu \"Send\"i silmək istədiyinizə əminsiniz?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "\"Send\"in növü nədir?", + "deleteSendPermanentConfirmation": { + "message": "Bu Send-i həmişəlik silmək istədiyinizə əminsiniz?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Silinmə tarixi" }, - "deletionDateDesc": { - "message": "\"Send\" göstərilən tarix və saatda birdəfəlik silinəcək.", + "deletionDateDescV2": { + "message": "Send, bu tarixdə həmişəlik silinəcək.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maksimal müraciət sayı" }, - "maxAccessCountDesc": { - "message": "Əgər ayarlanıbsa, istifadəçilər maksimal müraciət sayına çatdıqdan sonra bu \"Send\"ə müraciət edə bilməyəcək.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Hazırkı müraciət sayı" - }, - "sendPasswordDesc": { - "message": "İstəyinizə görə istifadəçilərdən bu \"Send\"ə müraciət edərkən parol tələb edə bilərsiniz.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Bu \"Send\" ilə bağlı gizli qeydlər.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Sıradan çıxarıldı" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Parolu çıxartmaq istədiyinizə əminsiniz?" }, - "hideEmail": { - "message": "E-poçt ünvanımı alıcılardan gizlət." - }, - "disableThisSend": { - "message": "Heç kimin müraciət edə bilməməsi üçün bu \"Send\"i sıradan çıxart.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Bütün \"Send\"lər" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Silinməsi gözlənilir" }, + "hideTextByDefault": { + "message": "Mətni ilkin olaraq gizlət" + }, "expired": { "message": "Müddəti bitib" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "Aşağıdakı təşkilat siyasətləri hal-hazırda qüvvədədir:" - }, - "sendDisableHideEmailInEffect": { - "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ə verilmir.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "modifiedPolicyId": { "message": "$ID$ siyasətinə düzəliş edildi.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Təşkilat istifadəçiləri üçün fərdi sahibliyi sıradan çıxart" }, - "textHiddenByDefault": { - "message": "\"Send\"ə müraciət edəndə ilkin olaraq mətni gizlədin", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Bu \"Send\"i açıqlayan bir ad.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Göndərmək istədiyiniz mətn." - }, - "sendFileDesc": { - "message": "Göndərmək istədiyiniz fayl." - }, - "copySendLinkOnSave": { - "message": "Saxladıqdan sonra bu \"Send\"in paylaşma keçidini lövhəmə kopyala." - }, - "sendLinkLabel": { - "message": "\"Send\" keçidi", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "Silinmə və son istifadə tarixlərini saxlayarkən xəta baş verdi." }, + "hideYourEmail": { + "message": "E-poçt ünvanınız baxanlardan gizlədilsin." + }, "webAuthnFallbackMsg": { "message": "2FA-nı doğrulamaq üçün lütfən aşağıdakı düyməyə klikləyin." }, "webAuthnAuthenticate": { "message": "WebAuthn kimlik doğrulama" }, + "readSecurityKey": { + "message": "Güvənlik açarını oxu" + }, + "awaitingSecurityKeyInteraction": { + "message": "Güvənlik açarı ilə əlaqə gözlənilir..." + }, "webAuthnNotSupported": { "message": "WebAuthn bu brauzerdə dəstəklənmir." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Xəta" }, + "decryptionError": { + "message": "Şifrə açma xətası" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden, aşağıda sadalanan seyf element(lər)inin şifrəsini aça bilmədi." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Əlavə data itkisini önləmək üçün", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "müştəri dəstəyi ilə əlaqə saxlayın.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "İdarə edilən istifadəçilərə həm də \"Hesab geri qaytarılmasını idarə et\" icazəsi verilməlidir" }, @@ -6516,15 +6791,6 @@ "message": "Yaradıcı", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "Nə yaratmaq istəyirsiniz?" - }, - "passwordType": { - "message": "Parol növü" - }, - "regenerateUsername": { - "message": "İstifadəçi adını yenidən yarat" - }, "generateUsername": { "message": "İstifadəçi adı yarat" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "İstifadəçi adı növü" - }, "plusAddressedEmail": { "message": "Plyus ünvanlı e-poçt", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Domeninizin konfiqurasiya edilmiş hamısını yaxalama gələn qutusunu istifadə edin." }, + "useThisEmail": { + "message": "Bu e-poçtu istifadə et" + }, "random": { "message": "Təsadüfi", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ tələbinizə rədd cavabı verdi. Lütfən kömək üçün xidmət provayderinizlə əlaqə saxlayın.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ tələbinizə rədd cavabı verdi: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "$SERVICENAME$ maskalı e-poçt hesab kimliyi alına bilmir.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Host adı", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API müraciət tokeni" - }, "deviceVerification": { "message": "Cihaz doğrulaması" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Hesabınız üçün DUO iki addımlı giriş tələb olunur." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Hesabınız üçün Duo iki addımlı giriş tələb olunur. Giriş prosesini tamamlamaq üçün aşağıdakı addımları izləyin." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Giriş prosesini tamamlamaq üçün aşağıdakı addımları izləyin." + }, "launchDuo": { "message": "DUO-nu başlat" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "İstifadəçi sayı" }, - "loggingInAs": { - "message": "Giriş et" - }, - "notYou": { - "message": "Siz deyilsiniz?" - }, "pickAnAvatarColor": { "message": "Bir avatar rəngi götürün" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "Kolleksiya yoxdur" }, - "canView": { - "message": "Baxa bilər" - }, - "canViewExceptPass": { - "message": "Parollar istisna olmaqla baxa bilər" - }, - "canEdit": { - "message": "Düzəliş edə bilər" - }, - "canEditExceptPass": { - "message": "Parollar istisna olmaqla düzəliş edə bilər" - }, "noCollectionsAdded": { "message": "Heç bir kolleksiya əlavə edilmədi" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Admin təsdiqini tələb et" }, - "approveWithMasterPassword": { - "message": "Ana parolla təsdiqlə" - }, "trustedDeviceEncryption": { "message": "Güvənli cihaz şifrələməsi" }, "trustedDevices": { "message": "Güvənli cihazlar" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Kimlik doğrulandıqdan sonra üzvlər, cihazlarından saxlanılan açarı istifadə edərək seyf datasının şifrəsini aça biləcək", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "SSO ilə giriş edərkən üzvlərin ana parola ehtiyacı olmayacaq. Ana parol, cihazda saxlanılan bir şifrələmə açarı ilə əvəz olunur və bu da cihazı güvənli hala gətirir. Üzvün hesabını yaradıb giriş etdiyi ilk cihaz, güvənli hesab olunacaq. Yeni cihazların mövcud güvənli cihaz və ya bir inzibatçı tərəfindən təsdiqlənməsi lazım gələcək.", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "vahid təşkilat", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "Vahid təşkilat", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "siyasəti,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "SSO tələb olunur", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO tələb olunan", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "siyasəti və", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "siyasət və", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "hesab geri qaytarma administrasiyası", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "bu seçim istifadə edildikdə avto-yazılma ilə işə salınacaq.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "siyasəti, bu seçim istifadə olunduqda işə düşəcək.", + "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": "Təşkilatınızın icazələri güncəlləndi və bir ana parol ayarlamağınızı tələb edir.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Tələbi təsdiqlə" }, + "deviceApproved": { + "message": "Cihaz təsdiqləndi" + }, + "deviceRemoved": { + "message": "Cihaz silindi" + }, + "removeDevice": { + "message": "Cihazı sil" + }, + "removeDeviceConfirmation": { + "message": "Bu cihazı silmək istədiyinizə əminsiniz?" + }, "noDeviceRequests": { "message": "Heç bir cihaz tələbi yoxdur" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Tələbiniz admininizə göndərildi." }, - "youWillBeNotifiedOnceApproved": { - "message": "Təsdiqləndikdən sonra məlumatlandırılacaqsınız." - }, "troubleLoggingIn": { "message": "Girişdə problem var?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Kolleksiya silinməsini sahibləri və adminləri ilə məhdudlaşdır" }, + "limitItemDeletionDesc": { + "message": "Elementin silinməsini \"İdarə edə bilər\" icazəsinə sahib üzvlərlə məhdudlaşdır" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Sahiblər və adminlər bütün kolleksiyaları və elementləri idarə edə bilər" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL-si", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Domen ləqəbi" - }, "alreadyHaveAccount": { "message": "Artıq bir hesabınız var?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "Bu kolleksiyanı idarə etmək üçün müraciətiniz yoxdur." }, - "grantAddAccessCollectionWarningTitle": { - "message": "İdarə edə bilər icazələri əskikdir" + "grantManageCollectionWarningTitle": { + "message": "\"Kolleksiyanı idarə etmə\" icazələri əskikdir" }, - "grantAddAccessCollectionWarning": { - "message": "Kolleksiyanın silinməsi daxil olmaqla tam kolleksiya idarəetməsinə icazə vermək üçün \"İdarə edə bilər\" icazələrini verin." + "grantManageCollectionWarning": { + "message": "Kolleksiyanın silinməsi daxil olmaqla tam kolleksiya idarəetməsinə icazə vermək üçün \"Kolleksiyanı idarə etmə\" icazələrini verin." }, "grantCollectionAccess": { "message": "Qrup və ya üzvlərin bu kolleksiyaya müraciətinə icazə verin." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Platformanız üçün icra bələdçisini istifadə edərək Bitwarden üçün cihaz idarəetməsini konfiqurasiya edin." }, + "deviceIdMissing": { + "message": "Cihaz kimliyi əskikdir" + }, + "deviceTypeMissing": { + "message": "Cihaz növü əskikdir" + }, + "deviceCreationDateMissing": { + "message": "Cihaz yaradılma tarixi əskikdir" + }, + "desktopRequired": { + "message": "Masaüstü tələb olunur" + }, + "reopenLinkOnDesktop": { + "message": "Bu keçidi e-poçtunuzdan masaüstündə yenidən açın." + }, "integrationCardTooltip": { "message": "$INTEGRATION$ icra bələdçisini başlat.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "üzv başına ay" }, + "monthPerMemberBilledAnnually": { + "message": "üzv üzrə aylıq illik hesablama" + }, "seats": { "message": "Yer" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Bitwarden-nin API-si haqqında daha ətraflı" }, + "fileSend": { + "message": "Fayl \"Send\"i" + }, "fileSends": { "message": "Fayl \"Send\"ləri" }, + "textSend": { + "message": "Mətn \"Send\"i" + }, "textSends": { "message": "Mətn \"Send\"ləri" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Açar alqoritmi" }, + "sshPrivateKey": { + "message": "Private açar" + }, + "sshPublicKey": { + "message": "Public açar" + }, + "sshFingerprint": { + "message": "Barmaq izi" + }, "sshKeyFingerprint": { "message": "Barmaq izi" }, @@ -9774,10 +10088,6 @@ "message": "Xüsusi xarakterləri daxil et", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Qoşma əlavə et" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Açıqlayıcı kod" }, + "cannotRemoveViewOnlyCollections": { + "message": "\"Yalnız baxma\" icazələrinə sahib kolleksiyaları silə bilməzsiniz: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Vacib bildiriş" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Üzvləri çıxart" }, + "devices": { + "message": "Cihazlar" + }, + "deviceListDescription": { + "message": "Aşağıdakı cihazların hər birində hesabınıza giriş edilib. Tanımadığınız cihaz varsa, onu silin." + }, + "deviceListDescriptionTemp": { + "message": "Aşağıdakı cihazların hər birində hesabınıza giriş edilib." + }, "claimedDomains": { "message": "Götürülmüş domenlər" }, @@ -10020,7 +10348,69 @@ "organizationNameMaxLength": { "message": "Təşkilat adı 50 xarakterdən çox ola bilməz." }, - "resellerRenewalWarning": { + "sshKeyWrongPassword": { + "message": "Daxil etdiyiniz parol yanlışdır." + }, + "importSshKey": { + "message": "Daxilə köçür" + }, + "confirmSshKeyPassword": { + "message": "Parolu təsdiqlə" + }, + "enterSshKeyPasswordDesc": { + "message": "SSH açarı üçün parolu daxil edin." + }, + "enterSshKeyPassword": { + "message": "Parolu daxil edin" + }, + "invalidSshKey": { + "message": "SSH açarı yararsızdır" + }, + "sshKeyTypeUnsupported": { + "message": "SSH açar növü dəstəklənmir" + }, + "importSshKeyFromClipboard": { + "message": "Açarı lövhədən daxilə köçür" + }, + "sshKeyImported": { + "message": "SSH açarı uğurla daxilə köçürüldü" + }, + "copySSHPrivateKey": { + "message": "Private açarı kopyala" + }, + "openingExtension": { + "message": "Bitwarden brauzer uzantısı açılır" + }, + "somethingWentWrong": { + "message": "Nəsə səhv getdi..." + }, + "openingExtensionError": { + "message": "Bitwarden brauzer uzantısı açılarkən xəta ilə üzləşdik. İndi açmaq üçün düyməyə klikləyin." + }, + "openExtension": { + "message": "Uzantını aç" + }, + "doNotHaveExtension": { + "message": "Bitwarden brauzer uzantısı yoxdur?" + }, + "installExtension": { + "message": "Uzantını quraşdır" + }, + "openedExtension": { + "message": "Brauzer uzantısı açıldı" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Bitwarden brauzer uzantısı uğurla açıldı. Riskli parollarınıza nəzər sala bilərsiniz." + }, + "openExtensionManuallyPart1": { + "message": "Bitwarden brauzer uzantısı açılarkən xəta ilə üzləşdik. Alət çubuğundan", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "Bitwarden ikonunu açın.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { "message": "Abunəliyiniz tezliklə yenilənəcək. Kəsintisiz xidməti təmin etmək və yeniləməni $RENEWAL_DATE$ tarixindən əvvəl təsdiqləmək üçün $RESELLER$ ilə əlaqə saxlayın.", "placeholders": { "reseller": { @@ -10033,7 +10423,7 @@ } } }, - "resellerOpenInvoiceWarning": { + "resellerOpenInvoiceWarningMgs": { "message": "Abunəliyinizə aid faktura $ISSUED_DATE$ tarixində təqdim edildi. Kəsintisiz xidməti təmin etmək və yeniləməni $DUE_DATE$ tarixindən əvvəl təsdiqləmək üçün $RESELLER$ ilə əlaqə saxlayın.", "placeholders": { "reseller": { @@ -10050,7 +10440,7 @@ } } }, - "resellerPastDueWarning": { + "resellerPastDueWarningMsg": { "message": "Abunəliyinizə aid faktura üzrə ödəniş edilmədi. Kəsintisiz xidməti təmin etmək və yeniləməni $GRACE_PERIOD_END$ tarixindən əvvəl təsdiqləmək üçün $RESELLER$ ilə əlaqə saxlayın.", "placeholders": { "reseller": { @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Təşkilat abunəliyi yenidən başladıldı" + }, + "restartSubscription": { + "message": "Abunəliyinizi yenidən başladın" + }, + "suspendedManagedOrgMessage": { + "message": "Kömək üçün $PROVIDER$ ilə əlaqə saxlayın.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "İnzibatçılar, artıq götürülmüş domenlərə aid üzv hesablarını silmə imkanına sahibdir." + }, + "deleteManagedUserWarningDesc": { + "message": "Bu əməliyyat, seyfdəki bütün elementlər daxil olmaqla üzv hesabını siləcək. Bu, əvvəlki Sil əməliyyatını əvəz edir." + }, + "deleteManagedUserWarning": { + "message": "Silmək, yeni bir əməliyyatdır!" + }, + "seatsRemaining": { + "message": "Bu təşkilata təyin edilmiş $TOTAL$ yerdən $REMAINING$ yeriniz qalıb. Abunəliyinizi idarə etmək üçün provayderinizlə əlaqə saxlayın.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Mövcud təşkilat" + }, + "selectOrganizationProviderPortal": { + "message": "Provayder Portalınıza əlavə ediləcək təşkilatı seçin." + }, + "noOrganizations": { + "message": "Sadalanacaq heç bir təşkilat yoxdur." + }, + "yourProviderSubscriptionCredit": { + "message": "Provayder abunəliyiniz, təşkilatınızın abunəliyində qalan vaxt üçün bir kredit alacaq." + }, + "doYouWantToAddThisOrg": { + "message": "Bu təşkilatı bura əlavə etmək istəyirsiniz: $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Mövcud təşkilat əlavə edildi" + }, + "assignedExceedsAvailable": { + "message": "Təyin edilmiş yer sayı, boş yer sayından çoxdur." + }, + "changeAtRiskPassword": { + "message": "Riskli parolları dəyişdir" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Kilidi PIN ilə açmanı ləğv et" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Üzvlərin öz hesablarının kilidini PIN ilə açmasına icazə verilməsin." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ planında real event log-larına müraciət yoxdur", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Teams və ya Enterprise planına yüksəldərək təşkilatın event log-larına tam müraciət əldə edin." + }, + "upgradeEventLogTitle": { + "message": "Real event log dataları üçün yüksəlt" + }, + "upgradeEventLogMessage": { + "message": "Bu event-lər sadəcə nümunədir və Bitwarden təşkilatınızdakı real event-ləri əks etdirmir." + }, + "cannotCreateCollection": { + "message": "Ödənişsiz təşkilatların ən çox 2 kolleksiyası ola bilər. Daha çox kolleksiya əlavə etmək üçün ödənişli bir plana yüksəldin." } } diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index ea705b38b4e..2e5aeabff32 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -1,24 +1,27 @@ { "allApplications": { - "message": "All applications" + "message": "Усе праграмы" }, "criticalApplications": { - "message": "Critical applications" + "message": "Крытычныя праграмы" + }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" }, "accessIntelligence": { - "message": "Access Intelligence" + "message": "Кіраванне доступам" }, "riskInsights": { - "message": "Risk Insights" + "message": "Разуменне рызык" }, "passwordRisk": { - "message": "Password Risk" + "message": "Рызыка пароля" }, "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." + "message": "Праглядайце паролі, якія знаходзяцца ў зоне рызыкі (ненадзейныя, скампраметаваныя або паўторна выкарыстаныя) ва ўсіх вашых праграмах. Выберыце найбольш крытычныя праграмы для вызначэння прыярытэту бяспекі дзеянняў для вашых карыстальнікаў, якія выкарыстоўваюць рызыкоўныя паролі." }, "dataLastUpdated": { - "message": "Data last updated: $DATE$", + "message": "Апошняе абнаўленне даных: $DATE$", "placeholders": { "date": { "content": "$1", @@ -27,19 +30,19 @@ } }, "notifiedMembers": { - "message": "Notified members" + "message": "Апавешчаныя ўдзельнікі" }, "revokeMembers": { - "message": "Revoke members" + "message": "Адклікаць удзельнікаў" }, "restoreMembers": { - "message": "Restore members" + "message": "Аднавіць удзельнікаў" }, "cannotRestoreAccessError": { - "message": "Cannot restore organization access" + "message": "Немагчыма аднавіць доступ да арганізацыі" }, "allApplicationsWithCount": { - "message": "All applications ($COUNT$)", + "message": "Усе праграмы ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -48,10 +51,10 @@ } }, "createNewLoginItem": { - "message": "Create new login item" + "message": "Стварыць новы элемент запісу ўваходу" }, "criticalApplicationsWithCount": { - "message": "Critical applications ($COUNT$)", + "message": "Крытычныя праграмы ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -60,7 +63,7 @@ } }, "notifiedMembersWithCount": { - "message": "Notified members ($COUNT$)", + "message": "Апавешчаныя ўдзельнікі ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -69,7 +72,7 @@ } }, "noAppsInOrgTitle": { - "message": "No applications found in $ORG NAME$", + "message": "Праграмы ў $ORG NAME$ не знойдзены", "placeholders": { "org name": { "content": "$1", @@ -78,49 +81,88 @@ } }, "noAppsInOrgDescription": { - "message": "As users save logins, applications appear here, showing any at-risk passwords. Mark critical apps and notify users to update passwords." + "message": "Тут будуць адлюстроўвацца праграмы па меры таго, як карыстальнікі будуць захоўваць запісы ўваходу, якія знаходзяцца ў зоне рызыкі. Пазначце крытычныя праграмы і апавяшчайце карыстальнікаў аб неабходнасці абнавіць паролі." }, "noCriticalAppsTitle": { - "message": "You haven't marked any applications as a Critical" + "message": "Вы не пазначылі ніводную праграму ў якасці кратычнай" }, "noCriticalAppsDescription": { - "message": "Select your most critical applications to discover at-risk passwords, and notify users to change those passwords." + "message": "Выберыце найбольш крытычныя праграмы для выяўлення пароляў, якія знаходзяцца ў зоне рызыкі. Апавяшчайце карыстальнікаў аб неабходнасці змяніць іх." }, "markCriticalApps": { - "message": "Mark critical apps" + "message": "Пазначыць крытычныя праграмы" }, "markAppAsCritical": { - "message": "Mark app as critical" + "message": "Пазначыць праграму як крытычную" }, "appsMarkedAsCritical": { - "message": "Apps marked as critical" + "message": "Праграмы пазначаныя як крытычныя" }, "application": { - "message": "Application" + "message": "Праграма" }, "atRiskPasswords": { - "message": "At-risk passwords" + "message": "Паролі ў зоне рызыкі" }, "requestPasswordChange": { - "message": "Request password change" + "message": "Запытаць змену пароля" }, "totalPasswords": { - "message": "Total passwords" + "message": "Усяго пароляў" }, "searchApps": { - "message": "Search applications" + "message": "Пошук праграм" }, "atRiskMembers": { - "message": "At-risk members" + "message": "Удзельнікі ў зоне рызыкі" + }, + "atRiskMembersWithCount": { + "message": "Удзельнікі ў зоне рызыкі ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Праграмы ў зоне рызыкі ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "Гэтыя ўдзельнікі ўваходзяць у праграму з ненадзейнымі, скампраметаванымі або паўторна выкарыстанымі паролямі." + }, + "atRiskApplicationsDescription": { + "message": "Гэтыя праграмы маюць ненадзейныя, скампраметаваныя або паўторна выкарыстаныя паролі." + }, + "atRiskMembersDescriptionWithApp": { + "message": "Гэтыя ўдзельнікі ўваходзяць у праграму $APPNAME$ з ненадзейнымі, скампраметаванымі або паўторна выкарыстанымі паролямі.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } }, "totalMembers": { - "message": "Total members" + "message": "Усяго ўдзельнікаў" }, "atRiskApplications": { - "message": "At-risk applications" + "message": "Праграмы ў зоне рызыкі" }, "totalApplications": { - "message": "Total applications" + "message": "Усяго праграм" + }, + "unmarkAsCriticalApp": { + "message": "Зняць пазнаку крытычнай праграмы" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Пазнака з крытычнай праграмы паспяхова знята" }, "whatTypeOfItem": { "message": "Які гэта элемент запісу?" @@ -159,6 +201,9 @@ "notes": { "message": "Нататкі" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Нататка" }, @@ -221,10 +266,10 @@ "message": "Дадаць вэб-сайт" }, "deleteWebsite": { - "message": "Delete website" + "message": "Выдаліць вэб-сайт" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "Прадвызначана ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -234,7 +279,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "Паказаць выяўленне супадзенняў $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -243,7 +288,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "Схаваць выяўленне супадзенняў $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -252,7 +297,7 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "Аўтазапаўняць пры загрузцы старонкі?" }, "number": { "message": "Нумар" @@ -267,7 +312,7 @@ "message": "Код бяспекі (CVV)" }, "securityCodeSlashCVV": { - "message": "Security code / CVV" + "message": "Код бяспекі (CVV)" }, "identityName": { "message": "Імя пасведчання" @@ -345,10 +390,10 @@ "message": "Доктар" }, "cardExpiredTitle": { - "message": "Expired card" + "message": "Картка пратэрмінавана" }, "cardExpiredMessage": { - "message": "If you've renewed it, update the card's information" + "message": "Калі вы падаўжалі картку, то абнавіце яе звесткі" }, "expirationMonth": { "message": "Месяц завяршэння" @@ -360,7 +405,7 @@ "message": "Ключ аўтэнтыфікацыі (TOTP)" }, "totpHelperTitle": { - "message": "Make 2-step verification seamless" + "message": "Спрасціце двухэтапную праверку" }, "totpHelper": { "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." @@ -383,6 +428,9 @@ "dragToSort": { "message": "Перацягніце для сартавання" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Тэкст" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Рэдагаваць папку" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "Асноўны дамен", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "напр.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Фільтр" }, - "moveSelectedToOrg": { - "message": "Перамясціць выбранае ў арганізацыю" - }, "deleteSelected": { "message": "Выдаліць выбраныя" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Выбраныя элементы перамешчаны ў $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "Не" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "Увайдзіце або стварыце новы ўліковы запіс для доступу да бяспечнага сховішча." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Ініцыяваны ўваход" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Адправіць" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Падказка да пароля" - }, - "enterEmailToGetHint": { - "message": "Увядзіце адрас электроннай пошты ўліковага запісу для атрымання падказкі да асноўнага пароля." - }, "getMasterPasswordHint": { "message": "Атрымаць падказку да асноўнага пароля" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "Апавяшчэнне было адпраўлена на вашу прыладу." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "Версія $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Запомніць мяне" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Адправіць праверачны код яшчэ раз" }, "useAnotherTwoStepMethod": { "message": "Выкарыстоўваць іншы метад двухэтапнага ўваходу" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Устаўце свой YubiKey у порт USB камп'ютара, а потым націсніце на кнопку." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Параметры двухэтапнага ўваходу" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Згубілі доступ да ўсіх варыянтаў доступу пастаўшчыкоў двухэтапнай аўтэнтыфікацыі? Скарыстайцеся кодам аднаўлення, каб адключыць праверку пастаўшчыкоў двухэтапнай аўтэнтыфікацыі для вашага ўліковага запісу." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Перанесена з FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "Электронная пошта" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Выберыце арганізацыю, у якую вы хочаце перамясціць гэты элемент. Пры перамяшчэнні ў арганізацыю ўсе правы ўласнасці на дадзены элемент пяройдуць да гэтай арганізацыі. Вы больш не будзеце адзіным уласнікам гэтага элемента пасля яго перамяшчэння." }, - "moveManyToOrgDesc": { - "message": "Выберыце арганізацыю, у якую вы хочаце перамясціць гэтыя элементы. Пры перамяшчэнні ў арганізацыю ўсе правы ўласнасці на дадзеныя элементы пяройдуць да гэтай арганізацыі. Вы больш не будзеце адзіным уласнікам гэтых элементаў пасля іх перамяшчэння." - }, "collectionsDesc": { "message": "Рэдагуйце калекцыі, з якімі гэты элемент знаходзіцца ў агульным доступе. Толькі карыстальнікі арганізацыі з доступам да гэтых калекцый змогуць бачыць гэты элемент." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "Вы выбралі наступную колькасць элементаў: $COUNT$ шт. З іх будуць перамешчаны ў арганізацыю: $MOVEABLE_COUNT$ шт. Не перамешчанымі застануцца: $NONMOVEABLE_COUNT$ шт.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Праверачны код (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Паўторна генерыраваць пароль" - }, "length": { "message": "Даўжыня" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Калі ласка, увайдзіце паўторна." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Калі ласка, увайдзіце паўторна. Калі вы выкарыстоўваеце іншыя праграмы Bitwarden, выйдзіце з іх, а потым увайдзіце яшчэ раз." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Небяспечная зона" }, - "dangerZoneDesc": { - "message": "Асцярожна, гэтыя дзеянні з'яўляюцца незваротнымі!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Скасаваць аўтарызацыю сеанса" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Працягваючы, вы таксама выйдзіце з бягучага сеанса і вам неабходна будзе ўвайсці паўторна. Вы таксама атрымаеце паўторны запыт двухэтапнага ўваходу, калі гэта функцыя ў вас уключана. Сеансы на іншых прыладах могуць заставацца актыўнымі на працягу адной гадзіны." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "Аўтарызацыя ўсіх сеансаў скасавана" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Уключэнне двухэтапнага ўваходу можа цалкам заблакіраваць доступ да ўліковага запісу Bitwarden. Код аднаўлення дае магчымасць атрымаць доступ да вашага ўліковага запісу ў выпадку, калі вы не можаце скарыстацца звычайным спосабам пастаўшчыка двухэтапнага ўваходу (напрыклад, вы згубілі сваю прыладу). Падтрымка Bitwarden не зможа вам дапамагчы, калі вы згубіце доступ да свайго ўліковага запісу. Мы рэкамендуем вам запісаць або раздрукаваць код аднаўлення і захоўваць яго ў надзейным месцы." }, + "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." + }, "viewRecoveryCode": { "message": "Паглядзець код аднаўлення" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Кіраванне" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Адключыць" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Адклікаць доступ" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "Гэты пастаўшчык двухэтапнага ўваходу ўключаны для вашага ўліковага запісу." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "Праблема чытання ключа бяспекі. Паспрабуйце яшчэ раз." }, - "twoFactorWebAuthnWarning": { - "message": "У сувязі з абмежаваннямі платформы, WebAuthn немагчыма выкарыстоўваць ва ўсіх праграмах Bitwarden. Вам неабходна актываваць іншага пастаўшчыка двухэтапнага ўваходу, каб вы маглі атрымаць доступ да свайго ўліковага запісу, калі немагчыма скарыстацца WebAuthn. Платформы, які падтрымліваюцца:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Вэб-сховішча і пашырэнні браўзера на камп'ютары/ноўтбуку з браўзерам, які падтрымлівае WebAuthn (Chrome, Opera, Vivaldi або Firefox з уключаным FIDO U2F)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Ваш код аднаўлення двухэтапнага ўваходу Bitwarden" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "Гэты карыстальнік выкарыстоўвае двухэтапны ўваход для абароны свайго ўліковага запісу." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Адлучаныя SSO для карыстальніка $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Прылада" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "Ваш браўзер не падтрымліваецца. Вэб-сховішча можа працаваць няправільна." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "Калі вы не можаце атрымаць доступ да свайго ўліковага запісу з дапамогай двухэтапнага ўваходу, то вы можаце скарыстацца кодам аднаўлення, каб адключыць усіх пастаўшчыкоў двухэтапнага ўваходу для вашага ўліковага запісу." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Аднавіць двухэтапны ўваход уліковага запісу" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Прызначце мінімальныя патрабаванні да надзейнасці асноўнага пароля." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Патрабуецца двухэтапны ўваход" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Прызначыць патрабаванні для генератара пароляў." }, - "passwordGeneratorPolicyInEffect": { - "message": "Адна або больш палітык арганізацыі ўплывае на налады генератара." - }, "masterPasswordPolicyInEffect": { "message": "Адна або больш палітык арганізацыі патрабуе, каб ваш асноўны пароль адпавядаў наступным патрабаванням:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "На ўладальнікаў арганізацыі і адміністратараў гэта палітыка не аказвае ўплыву." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "Файл" }, "sendTypeText": { "message": "Тэкст" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Стварыць новы Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Выдаліць Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Вы сапраўды хочаце выдаліць гэты Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "Які гэта тып Send'a?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Дата выдалення" }, - "deletionDateDesc": { - "message": "Send будзе незваротна выдалены ў азначаныя дату і час.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Максімальная колькасць доступаў" }, - "maxAccessCountDesc": { - "message": "Калі прызначана, то карыстальнікі больш не змогуць атрымаць доступ да гэтага Send пасля таго, як будзе дасягнута максімальная колькасць зваротаў.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Бягучая колькасць доступаў" - }, - "sendPasswordDesc": { - "message": "Па магчымасці запытваць у карыстальнікаў пароль для доступу да гэтага Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Прыватныя нататкі пра гэты Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Адключана" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Вы сапраўды хочаце выдаліць пароль?" }, - "hideEmail": { - "message": "Схаваць мой адрас электроннай пошты ад атрымальнікаў." - }, - "disableThisSend": { - "message": "Адключыць гэты Send, каб ніхто не змог атрымаць да яго доступ.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Усе Send'ы" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Чакаецца выдаленне" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Пратэрмінавана" }, @@ -5161,13 +5441,6 @@ "message": "Заўсёды паказваць атрымальнікам адрас электроннай пошты ўдзельніка пры стварэнні або рэдагаванні Send'a.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendOptionsPolicyInEffect": { - "message": "Зараз дзейнічаюць наступныя палітыкі арганізацыі:" - }, - "sendDisableHideEmailInEffect": { - "message": "Карыстальнікам не дазваляецца хаваць свой адрас электроннай пошты ад атрымальнікаў пры стварэнні або рэдагаванні Send'a.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "modifiedPolicyId": { "message": "Змяненне палітыкі $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Адключыць асабістую ўласнасць для карыстальнікаў арганізацыі" }, - "textHiddenByDefault": { - "message": "Пры доступе да Send прадвызначана хаваць тэкст", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Зразумелая назва для апісання гэтага Send'a.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Тэкст, які вы хочаце адправіць." - }, - "sendFileDesc": { - "message": "Файл, які вы хочаце адправіць." - }, - "copySendLinkOnSave": { - "message": "Скапіяваць спасылку ў буфер абмену пасля захавання, каб абагуліць гэты Send." - }, - "sendLinkLabel": { - "message": "Спасылка на Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "Адбылася памылка пры захаванні дат выдалення і завяршэння тэрміну дзеяння." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "Для праверкі вашага 2ФА, націсніце кнопку ніжэй." }, "webAuthnAuthenticate": { "message": "Аўтэнтыфікатар WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn не падтрымліваецца ў гэтым браўзеры." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Памылка" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Для кіравання карыстальнікамі неабходна даць дазвол на кіраванне аднаўленнем уліковым запісам" }, @@ -6516,15 +6791,6 @@ "message": "Генератар", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "Што вы хочаце генерыраваць?" - }, - "passwordType": { - "message": "Тып пароля" - }, - "regenerateUsername": { - "message": "Паўторна генерыраваць імя карыстальніка" - }, "generateUsername": { "message": "Генерыраваць імя карыстальніка" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Тып імя карыстальніка" - }, "plusAddressedEmail": { "message": "Адрасы электроннай пошты з плюсам", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Выкарыстоўвайце сваю сканфігураваную скрыню для ўсёй пошты дамена." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Выпадкова", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Назва вузла", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "Токен доступу да API" - }, "deviceVerification": { "message": "Праверка прылады" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Колькасць карыстальнікаў" }, - "loggingInAs": { - "message": "Увайсці як" - }, - "notYou": { - "message": "Не вы?" - }, "pickAnAvatarColor": { "message": "Выберыце колер аватара" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "Няма калекцый" }, - "canView": { - "message": "Можа праглядаць" - }, - "canViewExceptPass": { - "message": "Можа праглядаць (без пароляў)" - }, - "canEdit": { - "message": "Можа рэдагаваць" - }, - "canEditExceptPass": { - "message": "Можа рэдагаваць (без пароляў)" - }, "noCollectionsAdded": { "message": "Няма дадзеных калекцый" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Запытаць ухваленне адміністратара" }, - "approveWithMasterPassword": { - "message": "Ухваліць з дапамогай асноўнага пароля" - }, "trustedDeviceEncryption": { "message": "Шыфраванне даверанай прылады" }, "trustedDevices": { "message": "Давераныя прылады" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Пасля аўтэнтыфікацыі ўдзельнікі расшыфроўваюць даныя сховішча з выкарыстаннем ключа, які захоўваецца на іх прыладах.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "адзіная арганізацыя", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "single organization", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": "палітыка", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "policy,", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "патрабуецца SSO", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO required", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "палітыка і", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "policy, and", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "адміністраванне аднаўлення ўліковага запісу", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "account recovery administration", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "палітыка з аўтаматычнай рэгістрацыяй уключаецца пры выкарыстанні гэтага параметра.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Ухваліць запыт" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Няма запытаў ад прылады" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Ваш запыт адпраўлены адміністратару." }, - "youWillBeNotifiedOnceApproved": { - "message": "Вы атрымаеце апавяшчэння пасля яго ўхвалення." - }, "troubleLoggingIn": { "message": "Праблемы з уваходам?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Мянушка дамена" - }, "alreadyHaveAccount": { "message": "Ужо маеце ўліковы запіс?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index 1f5fb8d8c7e..04d4e25bb7c 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Важни приложения" }, + "noCriticalAppsAtRisk": { + "message": "Няма важни приложения в риск" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -15,7 +18,7 @@ "message": "Рискова парола" }, "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." + "message": "Прегледайте паролите в риск (слаби, преизползвани или разобличени) в различните приложения. Изберете най-важните си приложения, за да дадете приоритет на действията по сигурността за потребителите си, така че те да обърнат внимание на паролите си в риск." }, "dataLastUpdated": { "message": "Последно обновяване на данните: $DATE$", @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "Членове в риск" }, + "atRiskMembersWithCount": { + "message": "Членове в риск ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Приложения в риск ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "Тези членове се вписват в приложенията със слаби, преизползвани или разобличени пароли." + }, + "atRiskApplicationsDescription": { + "message": "Тези приложения имат слаби, преизползвани или разобличени пароли." + }, + "atRiskMembersDescriptionWithApp": { + "message": "Тези членове се вписват в $APPNAME$ със слаби, преизползвани или разобличени пароли.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Общо членове" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Общо приложения" }, + "unmarkAsCriticalApp": { + "message": "Премахване на приложението от важните" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Приложението е премахнато от важните" + }, "whatTypeOfItem": { "message": "Вид на елемента" }, @@ -159,6 +201,9 @@ "notes": { "message": "Бележки" }, + "privateNote": { + "message": "Лична бележка" + }, "note": { "message": "Бележка" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Подредба чрез влачене" }, + "dragToReorder": { + "message": "Плъзнете за пренареждане" + }, "cfTypeText": { "message": "Текст" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Редактиране на папка" }, + "editWithName": { + "message": "Редактиране на $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "Нова папка" + }, + "folderName": { + "message": "Име на папката" + }, + "folderHintText": { + "message": "Можете да вложите една папка в друга като въведете името на горната папка, а след това „/“. Пример: Социални/Форуми" + }, + "deleteFolderPermanently": { + "message": "Наистина ли искате да изтриете тази папка окончателно?" + }, "baseDomain": { "message": "Основен домейн", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Име на елемента" }, - "cannotRemoveViewOnlyCollections": { - "message": "Не можете да премахвате колекции с права „Само за преглед“: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "напр.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Филтриране" }, - "moveSelectedToOrg": { - "message": "Преместване на избраните в организация" - }, "deleteSelected": { "message": "Изтриване на избраното" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Избраните записи бяха преместени в $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Елементите са преместени в $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "Не" }, + "location": { + "message": "Местоположение" + }, "loginOrCreateNewAccount": { "message": "Впишете се или създайте нов абонамент, за да достъпите защитен трезор." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Впишете се в Битуорден" }, + "enterTheCodeSentToYourEmail": { + "message": "Въведете кода изпратен на е-пощата Ви" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Въведете кода от Вашето приложение за удостоверяване" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Натиснете бутона на своя YubiKey за удостоверяване" + }, "authenticationTimeout": { "message": "Време на давност за удостоверяването" }, "authenticationSessionTimedOut": { "message": "Сесията за удостоверяване е изтекла. Моля, започнете отначало процеса по вписване." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Потвърдете самоличността си" }, + "weDontRecognizeThisDevice": { + "message": "Това устройство е непознато. Въведете кода изпратен на е-пощата Ви, за да потвърдите самоличността си." + }, + "continueLoggingIn": { + "message": "Продължаване с вписването" + }, + "whatIsADevice": { + "message": "Какво представлява едно устройство?" + }, + "aDeviceIs": { + "message": "Едно устройство наричаме инсталацията на приложението Битуорден, където сте се вписали. Ако преинсталирате приложението, изчистите данните му или изтриете бисквитките си, това устройство може да се появи няколко пъти в списъка." + }, "logInInitiated": { "message": "Вписването е стартирано" }, + "logInRequestSent": { + "message": "Заявката е изпратена" + }, "submit": { "message": "Подаване" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Въведете е-пощата на регистрацията си и подсказката за паролата ще Ви бъде изпратена" }, - "passwordHint": { - "message": "Подсказка за паролата" - }, - "enterEmailToGetHint": { - "message": "Въведете адреса на електронната си поща, за да получите подсказка за главната парола." - }, "getMasterPasswordHint": { "message": "Подсказка за главната парола" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "Към устройството Ви е изпратено известие." }, + "notificationSentDevicePart1": { + "message": "Отключете Битоурден на устройството си или в " + }, + "areYouTryingToAccessYourAccount": { + "message": "Опитвате ли се да получите достъп до акаунта си?" + }, + "accessAttemptBy": { + "message": "Опит за достъп от $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Разрешаване на достъпа" + }, + "denyAccess": { + "message": "Отказване на достъпа" + }, + "notificationSentDeviceAnchor": { + "message": "приложението по уеб" + }, + "notificationSentDevicePart2": { + "message": "Уверете се, че уникалната фраза съвпада с тази по-долу, преди да одобрите." + }, + "notificationSentDeviceComplete": { + "message": "Отключете Битоурден на устройството си. Уверете се, че уникалната фраза съвпада с тази по-долу, преди да одобрите." + }, "aNotificationWasSentToYourDevice": { "message": "Към устройството Ви е изпратено известие" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Уверете се, че регистрацията Ви е отключена и че уникалната фраза съвпада с другото устройство" - }, "versionNumber": { "message": "Версия $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Запомняне" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Не ме питайте отново на това устройство за 30 дни" + }, "sendVerificationCodeEmailAgain": { "message": "Повторно изпращане на писмото за потвърждение" }, "useAnotherTwoStepMethod": { "message": "Използвайте друг начин на двустепенно удостоверяване" }, + "selectAnotherMethod": { + "message": "Изберете друг метод", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Използване на код за възстановяване" + }, "insertYubiKey": { "message": "Поставете устройството на YubiKey в USB порт на компютъра и натиснете бутона на устройството." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Настройки на двустепенното удостоверяване" }, + "selectTwoStepLoginMethod": { + "message": "Изберете начин за двустепенно удостоверяване" + }, "recoveryCodeDesc": { "message": "Ако сте загубили достъп до двустепенното удостоверяване, може да използвате код за възстановяване, за да изключите двустепенното удостоверяване в абонамента си." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Мигрирано от FIDO)" }, + "openInNewTab": { + "message": "Отваряне в нов раздел" + }, "emailTitle": { "message": "Електронна поща" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Изберете организацията, в която искате да преместите записа. Преместването прехвърля собствеността му към новата организация. След това няма вече директно да го притежавате." }, - "moveManyToOrgDesc": { - "message": "Изберете организацията, в която искате да преместите избраните записи. Преместването прехвърля собствеността им към новата организация. След това няма вече директно да ги притежавате." - }, "collectionsDesc": { "message": "Редактиране на колекциите, с които записът е споделен. Само потребителите на организациите, с които колекцията е споделена, ще виждат записа." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "Избрали сте $COUNT$ запис(а). От тях могат да се преместят: $MOVEABLE_COUNT$, не могат да се преместят: $NONMOVEABLE_COUNT$.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Код за потвърждаване (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Без нееднозначни знаци", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Друга парола" - }, "length": { "message": "Дължина" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Впишете се отново." }, + "currentSession": { + "message": "Текуща сесия" + }, + "requestPending": { + "message": "Чакаща заявка" + }, "logBackInOthersToo": { "message": "Впишете се отново. Ако използвате и други приложения на Битуорден, впишете се отново и в тях." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "ОПАСНО" }, - "dangerZoneDesc": { - "message": "Внимание, тези действия са необратими!" - }, - "dangerZoneDescSingular": { - "message": "Внимание, това действие е необратимо!" - }, "deauthorizeSessions": { "message": "Прекратяване на сесии" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Действието ще прекрати и текущата ви сесия, след което ще се наложи отново да се впишете. Ако сте включили двустепенна идентификация, ще се наложи да повторите и нея. Активните сесии на другите устройства може да останат такива до един час." }, + "newDeviceLoginProtection": { + "message": "Вписване от ново устройство" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Изключване на защитата за вписване от ново устройство" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Включване на защитата за вписване от ново устройство" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Продължете по-долу, за да изключите е-писмата за потвърждение, които Битуорден изпраща, когато се вписвате от ново устройство." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Продължете по-долу, за да посочите, че искате Биуорден да изпраща е-писма за потвърждение, когато се вписвате от ново устройство." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "Ако защитата за вписване от ново устройство е изключена, всеки, който знае главната Ви парола, ще може да получи достъп до акаунта Ви от всяко устройство. Ако искате да защитите акаунта си без потвърждение чрез е-поща, настройте двустепенното удостоверяване." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "Промените на защитата за вписване от ново устройство са запазени" + }, "sessionsDeauthorized": { "message": "Всички сесии са прекратени" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Включването на двустепенна идентификация може завинаги да предотврати вписването ви в абонамента към Битуорден. Кодът за възстановяване ще ви позволи да достъпите абонамента дори и да имате проблем с доставчика на двустепенна идентификация (напр. ако изгубите устройството си). Дори и екипът по поддръжката към няма да ви помогне в такъв случай. Силно препоръчваме да отпечатате или запишете кодовете и да ги пазете на надеждно място." }, + "yourSingleUseRecoveryCode": { + "message": "Вашият еднократен код за възстановяване може да бъде използван, за да изключите двустепенното удостоверяване, в случай че нямате достъп до доставчика си за двустепенно вписване. Битуорден препоръчва да запишете кода си за възстановяване и да го пазите на сигурно място." + }, "viewRecoveryCode": { "message": "Извеждане на кодовете за възстановяване" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Управление" }, - "canManage": { - "message": "Може да управлява" + "manageCollection": { + "message": "Управление на колекцията" + }, + "viewItems": { + "message": "Преглед на елементите" + }, + "viewItemsHidePass": { + "message": "Преглед на елементите, със скрити пароли" + }, + "editItems": { + "message": "Редактиране на елементите" + }, + "editItemsHidePass": { + "message": "Редактиране на елементите, със скрити пароли" }, "disable": { "message": "Изключване" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Отнемане на достъпа" }, + "revoke": { + "message": "Отнемане" + }, "twoStepLoginProviderEnabled": { "message": "Този доставчик на двустепенно удостоверяване е включен за абонамента ви." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "Проблем при изчитането на ключа за сигурност. Пробвайте отново." }, - "twoFactorWebAuthnWarning": { - "message": "Поради платформени ограничения устройствата на WebAuthn не могат да се използват с всички приложения на Битуорден. В такъв случай ще трябва да добавите друг доставчик на двустепенно удостоверяване, за да имате достъп до абонамента си, дори когато WebAuthn не работи. Поддържани платформи:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Трезорът по уеб както и разширенията за браузърите с поддръжка на WebAuthn (Chrome, Opera, Vivaldi и Firefox с поддръжка на FIDO U2F)." + "twoFactorWebAuthnWarning1": { + "message": "Поради платформени ограничения устройствата на WebAuthn не могат да се използват с всички приложения на Битуорден. Ще трябва да настроите друг доставчик на двустепенно удостоверяване, за да имате достъп до акаунта си, когато WebAuthn не може да се ползва." }, "twoFactorRecoveryYourCode": { "message": "Код за възстановяване на достъпа до Битуорден при двустепенна идентификация" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Имате 1 оставаща покана." + }, + "inviteZeroEmailDesc": { + "message": "Имате 0 оставащи покани." + }, "userUsingTwoStep": { "message": "Този потребител използва двустепенна защита за достъп." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Еднократната идентификация е прекъсната." + }, "unlinkedSsoUser": { "message": "Самоличността $ID$ е извадено от еднократното вписване.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Устройство" }, + "loginStatus": { + "message": "Състояние на вписването" + }, + "firstLogin": { + "message": "Първо вписване" + }, + "trusted": { + "message": "Доверено" + }, + "needsApproval": { + "message": "Изисква одобрение" + }, + "areYouTryingtoLogin": { + "message": "Опитвате се да се впишете ли?" + }, + "logInAttemptBy": { + "message": "Опит за вписване от $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Вид устройство" + }, + "ipAddress": { + "message": "IP адрес" + }, + "confirmLogIn": { + "message": "Потвърждаване на вписването" + }, + "denyLogIn": { + "message": "Отказване на вписването" + }, + "thisRequestIsNoLongerValid": { + "message": "Тази заявка вече не е активна." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Вписването за $EMAIL$ на $DEVICE$ е одобрено", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "Вие отказахте опит за вписване от друго устройство. Ако това наистина сте били Вие, опитайте да се впишете от устройството отново." + }, + "loginRequestHasAlreadyExpired": { + "message": "Заявката за вписване вече е изтекла." + }, + "justNow": { + "message": "Току-що" + }, + "requestedXMinutesAgo": { + "message": "Заявено преди $MINUTES$ минути", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Създаване на регистрация в" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "Ползвате неподдържан браузър. Трезорът по уеб може да не сработи правилно." }, + "youHaveAPendingLoginRequest": { + "message": "Имате чакаща заявка за вписване от друго устройство." + }, + "reviewLoginRequest": { + "message": "Преглед на заявката за вписване" + }, "freeTrialEndPromptCount": { "message": "Вашият безплатен пробен период приключва след $COUNT$ дни.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "Ако не може да достъпите абонамента си с нормалната двустепенна идентификация, може да ползвате код за възстановяване, за да я изключите." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Впишете се по-долу с еднократния си код за възстановяване. Така ще изключите доставчиците на двустепенно удостоверяване за акаунта си." + }, "recoverAccountTwoStep": { "message": "Възстановяване на абонамент с двустепенна идентификация" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Актуализирането на шифриращия ключ не може да продължи" }, + "editFieldLabel": { + "message": "Редактиране на $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Преместване на $LABEL$. Използвайте стрелките, за да преместите елемента нагоре или надолу.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "Преместено нагоре: $LABEL$. Позиция $INDEX$ от $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "Преместено надолу: $LABEL$. Позиция $INDEX$ от $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "Ако актуализирате шифроващия ключ, папките Ви няма да могат да бъдат дешифрирани. За да продължите с промяната, папките трябва да бъдат изтрити. Елементите в трезора няма да бъдат изтрити, ако продължите." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Задаване на минимална сила на главната парола." }, + "passwordStrengthScore": { + "message": "Оценка на сложността на паролата: $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Изискване на двустепенно удостоверяване" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Задаване на минимална сила на генератора на пароли." }, - "passwordGeneratorPolicyInEffect": { - "message": "Поне една политика на организация влияе на настройките на генерирането на паролите." - }, "masterPasswordPolicyInEffect": { "message": "Поне една политика на организация има следните изисквания към главната ви парола:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Тази политика не се прилага към собствениците и администраторите на организацията." }, + "limitSendViews": { + "message": "Ограничаване на преглежданията" + }, + "limitSendViewsHint": { + "message": "Никой няма да може да преглежда това Изпращане след достигане на ограничението.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "Остават $ACCESSCOUNT$ преглеждания", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Подробности за Изпращането", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Текст за споделяне" + }, "sendTypeFile": { "message": "Файл" }, "sendTypeText": { "message": "Текст" }, + "sendPasswordDescV3": { + "message": "Добавете незадължителна парола, с която получателите да имат достъп до това Изпращане.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Създаване на изпращане", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Изтриване на изпращане", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Сигурни ли сте, че искате да изтриете това изпращане?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "Вид на изпратеното", + "deleteSendPermanentConfirmation": { + "message": "Наистина ли искате да изтриете завинаги това Изпращане?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Дата на изтриване" }, - "deletionDateDesc": { - "message": "Изпращането ще бъде окончателно изтрито на зададената дата и време.", + "deletionDateDescV2": { + "message": "Изпращането ще бъде окончателно изтрито на тази дата.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Максимален брой достъпвания" }, - "maxAccessCountDesc": { - "message": "При задаване — това изпращане ще се изключи след определен брой достъпвания.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Текущ брой на достъпванията" - }, - "sendPasswordDesc": { - "message": "Изискване на парола за достъп до това изпращане.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Скрити бележки за това изпращане.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Изключено" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Сигурни ли сте, че искате да премахнете паролата?" }, - "hideEmail": { - "message": "Скриване на е-пощата ми от получателите." - }, - "disableThisSend": { - "message": "Пълно спиране на това изпращане — никой няма да има достъп.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Всички изпращания" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Предстои изтриване" }, + "hideTextByDefault": { + "message": "Скриване на текста по подразбиране" + }, "expired": { "message": "Изтекъл" }, @@ -5161,13 +5441,6 @@ "message": "Потребителите да не могат да скриват адреса на е-пощата си от получателите, когато създават или редактират изпращания.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendOptionsPolicyInEffect": { - "message": "В момента са в сила следните политики на организацията:" - }, - "sendDisableHideEmailInEffect": { - "message": "Потребителите не могат да скриват адреса на е-пощата си от получателите, когато създават или редактират изпращания.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "modifiedPolicyId": { "message": "Редактирана политика № $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Изключване на индивидуалното притежание за потребителите в организацията" }, - "textHiddenByDefault": { - "message": "При достъп до изпращането стандартно текстът да се скрива", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Описателно име за това изпращане.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Текст за изпращане." - }, - "sendFileDesc": { - "message": "Файл за изпращане." - }, - "copySendLinkOnSave": { - "message": "Копиране на връзката към изпращането при запазването му за лесно споделяне." - }, - "sendLinkLabel": { - "message": "Изпращане на връзката", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Изпращане", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "Грешка при запазване на датата на валидност и изтриване." }, + "hideYourEmail": { + "message": "Скриване на Вашата е-поща от получателите." + }, "webAuthnFallbackMsg": { "message": "За да потвърдите двустепенното удостоверяване, натиснете бутона по-долу." }, "webAuthnAuthenticate": { "message": "Идентификация WebAuthn" }, + "readSecurityKey": { + "message": "Прочитане на ключа за сигурност" + }, + "awaitingSecurityKeyInteraction": { + "message": "Изчакване на действие с ключ за сигурност…" + }, "webAuthnNotSupported": { "message": "Този браузър не поддържа WebAuthn." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Грешка" }, + "decryptionError": { + "message": "Грешка при дешифриране" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Битоурден не може да дешифрира елементите от трезора посочени по-долу." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Свържете се с поддръжката", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "за да избегнете загубата на данни.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Управлението на потребителите трябва да бъде дадено заедно с разрешението за Управление на възстановяването на регистрации" }, @@ -6516,15 +6791,6 @@ "message": "Генератор", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "Какво бихте искали да генерирате?" - }, - "passwordType": { - "message": "Тип парола" - }, - "regenerateUsername": { - "message": "Повторно генериране на потр. име" - }, "generateUsername": { "message": "Генериране на потр. име" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Тип потребителско име" - }, "plusAddressedEmail": { "message": "Адрес на е-поща с плюс", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Използване на тази е-поща" + }, "random": { "message": "Произволно", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ отказа заявката Ви. Свържете се с доставчика на услугата за съдействие.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ отказа заявката Ви: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Не може да бъде получен идентификатор на маскиран чрез е-поща акаунт от $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Име на сървъра", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "Идентификатор за достъп до API" - }, "deviceVerification": { "message": "Потвърждаване на устройствата" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Вашата регистрация изисква двустепенно удостоверяване чрез DUO." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Вашият акаунт изисква вписване чрез двустепенно удостоверяване с Duo. Следвайте стъпките по-долу, за да завършите вписването." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Следвайте стъпките по-долу, за да завършите вписването." + }, "launchDuo": { "message": "Стартиране на DUO" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Брой потребители" }, - "loggingInAs": { - "message": "Вписване като" - }, - "notYou": { - "message": "Това не сте Вие?" - }, "pickAnAvatarColor": { "message": "Изберете цвят за аватара" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "Няма колекция" }, - "canView": { - "message": "Може да преглежда" - }, - "canViewExceptPass": { - "message": "Може да преглежда, без пароли" - }, - "canEdit": { - "message": "Може да редактира" - }, - "canEditExceptPass": { - "message": "Може да редактира, без пароли" - }, "noCollectionsAdded": { "message": "Няма добавени колекции" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Подаване на заявка за одобрение от администратор" }, - "approveWithMasterPassword": { - "message": "Одобряване с главната парола" - }, "trustedDeviceEncryption": { "message": "Шифроване чрез доверено устройство" }, "trustedDevices": { "message": "Доверени устройства" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "След вписване, членовете ще дешифрират данните от трезорите си чрез ключ, който се съхранява на устройство. Политиката за", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "единствена организация", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": ",", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "политиката за изискване на еднократно удостоверяване", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "и", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "политиката за управление на възстановяването на регистрации", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "с автоматично включване ще бъдат активирани при използването на тази настройка.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "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.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Правата Ви в организацията бяха променени, необходимо е да зададете главна парола.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Одобряване на заявката" }, + "deviceApproved": { + "message": "Устройството е одобрено" + }, + "deviceRemoved": { + "message": "Устройството е премахнато" + }, + "removeDevice": { + "message": "Премахване на устройството" + }, + "removeDeviceConfirmation": { + "message": "Наистина ли искате да премахнете това устройство?" + }, "noDeviceRequests": { "message": "Няма заявки от устройства" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Вашата заявка беше изпратена до администратора Ви." }, - "youWillBeNotifiedOnceApproved": { - "message": "Ще получите известие, когато тя бъде одобрена." - }, "troubleLoggingIn": { "message": "Имате проблем с вписването?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Ограничаване на изтриването на колекции, така че да може да се извършва само от собствениците и администраторите" }, + "limitItemDeletionDesc": { + "message": "Ограничаване на изтриването на елементи, така че да може да се извършва само от членове с правомощие за управление" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Собствениците и администраторите могат да управляват всички колекции и елементи" }, @@ -8494,9 +8778,6 @@ "message": "Адрес на собствения сървър", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Псевдонимен домейн" - }, "alreadyHaveAccount": { "message": "Вече имате регистрация?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "Нямате достъп за управление на тази колекция." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Липсват правомощия за управление" + "grantManageCollectionWarningTitle": { + "message": "Липсват правомощия за управление на колекции" }, - "grantAddAccessCollectionWarning": { - "message": "Дайте правомощия за управление, за да позволите пълното управление на колекции, включително изтриването им." + "grantManageCollectionWarning": { + "message": "Дайте правомощия за управление на колекциите, за да позволите пълното управление на колекции, включително изтриването им." }, "grantCollectionAccess": { "message": "Дайте права на групи и членове до тази колекция." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Настройте управлението на устройства в Битуорден, като използвате ръководството за внедряване за платформата си." }, + "deviceIdMissing": { + "message": "Липсва идентификатор на устройството" + }, + "deviceTypeMissing": { + "message": "Липсва тип на устройството" + }, + "deviceCreationDateMissing": { + "message": "Липсва дата на създаване на устройството" + }, + "desktopRequired": { + "message": "Трябва да сте на компютър" + }, + "reopenLinkOnDesktop": { + "message": "Отворете тази връзка от е-пощата си на компютър." + }, "integrationCardTooltip": { "message": "Стартиране на ръководството за внедряване за $INTEGRATION$.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "на месец за член" }, + "monthPerMemberBilledAnnually": { + "message": "месец, за всеки потребител, таксувано на годишна база" + }, "seats": { "message": "Места" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Научете повече относно ППИ на Биуорден" }, + "fileSend": { + "message": "Файлово изпращане" + }, "fileSends": { "message": "Файлови изпращания" }, + "textSend": { + "message": "Текстово изпращане" + }, "textSends": { "message": "Текстови изпращания" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Алгоритъм на ключа" }, + "sshPrivateKey": { + "message": "Частен ключ" + }, + "sshPublicKey": { + "message": "Публичен ключ" + }, + "sshFingerprint": { + "message": "Отпечатък" + }, "sshKeyFingerprint": { "message": "Отпечатък" }, @@ -9774,10 +10088,6 @@ "message": "Включване на специални знаци", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Добавяне на прикачен файл" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Код от описанието" }, + "cannotRemoveViewOnlyCollections": { + "message": "Не можете да премахвате колекции с права „Само за преглед“: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Важно съобщение" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Премахване на членовете" }, + "devices": { + "message": "Устройства" + }, + "deviceListDescription": { + "message": "Вие сте се вписали във всяко от устройствата по-долу. Ако не разпознавате някое от тях, премахнете го сега." + }, + "deviceListDescriptionTemp": { + "message": "Вашият акаунт е вписан във всяко от устройствата по-долу." + }, "claimedDomains": { "message": "Присвоени домейни" }, @@ -10020,7 +10348,69 @@ "organizationNameMaxLength": { "message": "Името на организацията не може да бъде по-дълго от 50 знака." }, - "resellerRenewalWarning": { + "sshKeyWrongPassword": { + "message": "Въведената парола е неправилна." + }, + "importSshKey": { + "message": "Внасяне" + }, + "confirmSshKeyPassword": { + "message": "Потвърждаване на паролата" + }, + "enterSshKeyPasswordDesc": { + "message": "Въведете паролата за SSH-ключа." + }, + "enterSshKeyPassword": { + "message": "Въведете паролата" + }, + "invalidSshKey": { + "message": "SSH ключът е неправилен" + }, + "sshKeyTypeUnsupported": { + "message": "Типът на SSH ключа не се поддържа" + }, + "importSshKeyFromClipboard": { + "message": "Внасяне на ключ от буфера за обмен" + }, + "sshKeyImported": { + "message": "SSH ключът е внесен успешно" + }, + "copySSHPrivateKey": { + "message": "Копиране на частния ключ" + }, + "openingExtension": { + "message": "Отваряне на добавката за браузър на Битуорден" + }, + "somethingWentWrong": { + "message": "Нещо се обърка…" + }, + "openingExtensionError": { + "message": "Добавката за браузър на Биуторден не успя да се отвори. Натиснете бутона, за да я отворите сега." + }, + "openExtension": { + "message": "Отваряне на добавката" + }, + "doNotHaveExtension": { + "message": "Нямате ли добавката за браузър на Битуорден?" + }, + "installExtension": { + "message": "Инсталиране на добавката" + }, + "openedExtension": { + "message": "Добавката за браузъра е отворена" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Добавката за браузър на Биуторден се отвори. Сега може да прегледате паролите си в риск." + }, + "openExtensionManuallyPart1": { + "message": "Добавката за браузър на Биуторден не успя да се отвори. Отворете я с иконката на Битуорден", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "от лентата с инструменти.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { "message": "Вашият абонамент ще бъде подновен скоро. За да подсигурите, че услугата няма да има прекъсвания, свържете се с $RESELLER$ и потвърдете подновяването преди $RENEWAL_DATE$.", "placeholders": { "reseller": { @@ -10033,7 +10423,7 @@ } } }, - "resellerOpenInvoiceWarning": { + "resellerOpenInvoiceWarningMgs": { "message": "Фактура за абонамента Ви беше издадена на $ISSUED_DATE$. За да подсигурите, че услугата няма да има прекъсвания, свържете се с $RESELLER$ и потвърдете подновяването преди $DUE_DATE$.", "placeholders": { "reseller": { @@ -10050,7 +10440,7 @@ } } }, - "resellerPastDueWarning": { + "resellerPastDueWarningMsg": { "message": "Фактурата за абонамента Ви не е била платена. За да подсигурите, че услугата няма да има прекъсвания, свържете се с $RESELLER$ и потвърдете подновяването преди $GRACE_PERIOD_END$.", "placeholders": { "reseller": { @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Абонаментът на организацията е рестартиран" + }, + "restartSubscription": { + "message": "Рестартирайте абонамента си" + }, + "suspendedManagedOrgMessage": { + "message": "Свържете се с $PROVIDER$ за помощ.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Администраторите вече имат възможността да изтриват акаунтите на членовете, които принадлежат към присвоен домейн." + }, + "deleteManagedUserWarningDesc": { + "message": "Това действие ще изтрие акаунта на члена, включително всички елементи в неговия трезор. Това заменя предишното действие за Премахване." + }, + "deleteManagedUserWarning": { + "message": "Изтриването е ново действие!" + }, + "seatsRemaining": { + "message": "Остават Ви $REMAINING$ от общо $TOTAL$ места в тази организация. Свържете се с доставчика си, ако искате да управлявате абонамента си.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Съществуваща организация" + }, + "selectOrganizationProviderPortal": { + "message": "Изберете организацията, която искате да добавите към своя Портал за доставчици." + }, + "noOrganizations": { + "message": "Няма организации за показване" + }, + "yourProviderSubscriptionCredit": { + "message": "Вашият абонамент за доставчик ще получи кредит за оставащото време в абонамента на организацията, ако има такова." + }, + "doYouWantToAddThisOrg": { + "message": "Искате ли да добавите тази организация към $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Добавена е съществуваща организация" + }, + "assignedExceedsAvailable": { + "message": "Назначените места превишават наличния брой." + }, + "changeAtRiskPassword": { + "message": "Промяна на парола в риск" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Премахване на отключването чрез ПИН" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Забраняване на членовете да отключват акаунтите си с ПИН." + }, + "limitedEventLogs": { + "message": "Плановете от тип „$PRODUCT_TYPE$“ нямат достъп до истинските журнали на събитията", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Получете пълен достъп до журналите на събитията за организациите като надградите до Екипния план или този за Големи организации." + }, + "upgradeEventLogTitle": { + "message": "Надградете за достъп до истинските журнали на събитията" + }, + "upgradeEventLogMessage": { + "message": "Тези събития са само за пример и не отразяват истинските събития във Вашата организация." + }, + "cannotCreateCollection": { + "message": "Безплатните организации могат да имат не повече от 2 колекции. Надградете до платен план, ако искате да имате повече колекции." } } diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index 56a04f3d870..8c082c57e30 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Critical applications" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "এটি কোন ধরণের বস্তু?" }, @@ -159,6 +201,9 @@ "notes": { "message": "মন্তব্য" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Note" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "বাছাই করতে টানুন" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "পাঠ্য" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "ফোল্ডার সম্পাদনা" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "ভিত্তি ডোমেইন", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "উদাহরণ", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filter" }, - "moveSelectedToOrg": { - "message": "Move selected to organization" - }, "deleteSelected": { "message": "Delete selected" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "আপনার সুরক্ষিত ভল্টে প্রবেশ করতে লগ ইন করুন অথবা একটি নতুন অ্যাকাউন্ট তৈরি করুন।" }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Submit" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "পাসওয়ার্ড ইঙ্গিত" - }, - "enterEmailToGetHint": { - "message": "আপনার মূল পাসওয়ার্ডের ইঙ্গিতটি পেতে আপনার অ্যাকাউন্টের ইমেল ঠিকানা প্রবেশ করুন।" - }, "getMasterPasswordHint": { "message": "মূল পাসওয়ার্ডের ইঙ্গিত পান" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "সংস্করণ $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "আমাকে মনে রাখবেন" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "আবার যাচাইকরণ কোড ইমেইলে প্রেরণ করুন" }, "useAnotherTwoStepMethod": { "message": "অন্য দ্বি-পদক্ষেপ প্রবেশ পদ্ধতি ব্যবহার করুন" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "আপনার কম্পিউটারের ইউএসবি পোর্টে আপনার YubiKey ঢোকান, তারপরে তার বোতামটি স্পর্শ করুন।" }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "দ্বি-পদক্ষেপ লগইন বিকল্প" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "আপনার সমস্ত দ্বি-গুণক সরবরাহকারীদের অ্যাক্সেস হারিয়েছেন? আপনার অ্যাকাউন্ট থেকে সমস্ত দ্বি-গুণক সরবরাহকারীদের অক্ষম করতে আপনার পুনরুদ্ধার কোডটি ব্যবহার করুন।" }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Migrated from FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "Email" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." }, - "moveManyToOrgDesc": { - "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved." - }, "collectionsDesc": { "message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Verification code (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Regenerate password" - }, "length": { "message": "Length" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Danger zone" }, - "dangerZoneDesc": { - "message": "Careful, these actions are not reversible!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Deauthorize sessions" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Proceeding will also log you out of your current session, requiring you to log back in. You will also be prompted for two-step login again, if set up. Active sessions on other devices may continue to remain active for up to one hour." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "All sessions deauthorized" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Setting up two-step login can permanently lock you out of your Bitwarden account. A recovery code allows you to access your account in the event that you can no longer use your normal two-step login provider (example: you lose your device). Bitwarden support will not be able to assist you if you lose access to your account. We recommend you write down or print the recovery code and keep it in a safe place." }, + "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." + }, "viewRecoveryCode": { "message": "পুনরুদ্ধার কোড দেখুন" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Manage" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Turn off" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Revoke access" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "This two-step login provider is active on your account." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "There was a problem reading the security key. Try again." }, - "twoFactorWebAuthnWarning": { - "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used. Supported platforms:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Web vault and browser extensions on a desktop/laptop with a WebAuthn supported browser (Chrome, Opera, Vivaldi, or Firefox with FIDO U2F turned on)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Your Bitwarden two-step login recovery code" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "If you cannot access your account through your normal two-step login methods, you can use your two-step login recovery code to turn off all two-step providers on your account." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Recover account two-step login" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Set requirements for master password strength." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Require two-step login" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "পাসওয়ার্ড উৎপাদকের কনফিগারেশনের জন্য ন্যূনতম প্রয়োজনীয়তা সেট করুন।" }, - "passwordGeneratorPolicyInEffect": { - "message": "এক বা একাধিক সংস্থার নীতিগুলি আপনার উৎপাদকের সেটিংসকে প্রভাবিত করছে।" - }, "masterPasswordPolicyInEffect": { "message": "One or more organization policies require your master password to meet the following requirements:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Organization owners and admins are exempt from this policy's enforcement." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "ফাইল" }, "sendTypeText": { "message": "পাঠ্য" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Delete Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Are you sure you want to delete this Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "What type of Send is this?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maximum access count" }, - "maxAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Disabled" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Are you sure you want to remove the password?" }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, - "disableThisSend": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "All Sends" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Pending deletion" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Expired" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "The following organization policies are currently in effect:" - }, - "sendDisableHideEmailInEffect": { - "message": "Users are not allowed 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." - }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Remove individual ownership for organization users" }, - "textHiddenByDefault": { - "message": "When accessing the Send, hide the text by default", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to Send." - }, - "sendFileDesc": { - "message": "The file you want to Send." - }, - "copySendLinkOnSave": { - "message": "Copy the link to share this Send to my clipboard upon save." - }, - "sendLinkLabel": { - "message": "Send link", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "To verify your 2FA please click the button below." }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn is not supported in this browser." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, - "regenerateUsername": { - "message": "Regenerate username" - }, "generateUsername": { "message": "Generate username" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Username type" - }, "plusAddressedEmail": { "message": "Plus addressed email", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Hostname", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API access token" - }, "deviceVerification": { "message": "Device verification" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Number of users" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "pickAnAvatarColor": { "message": "Pick an avatar color" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "No collection" }, - "canView": { - "message": "Can view" - }, - "canViewExceptPass": { - "message": "Can view, except passwords" - }, - "canEdit": { - "message": "Can edit" - }, - "canEditExceptPass": { - "message": "Can edit, except passwords" - }, "noCollectionsAdded": { "message": "No collections added" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domain" - }, "alreadyHaveAccount": { "message": "Already have an account?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index 3e9c4525b04..7efb47bd86c 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Critical applications" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "Koja je ovo vrsta stavke?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Bilješke" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Note" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Povuci za sortiranje" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Tekst" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Uredite folder" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "Osnovni domen", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "npr.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filter" }, - "moveSelectedToOrg": { - "message": "Premjestite Odabrano u Organizaciju" - }, "deleteSelected": { "message": "Obrišite Odabrano" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Odabrane stavke premještene u $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "Ne" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "Log in or create a new account to access your secure vault." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Submit" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Nagovještaj lozinke" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "Verzija $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Zapamti me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Ponovno slanje kontrolnog koda imejlom" }, "useAnotherTwoStepMethod": { "message": "Koristiti drugi način prijave u dva koraka" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Povežite Vaš YubiKey preko USB porta na vašem računaru, pa pritisnite dugme na njemu." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Mogućnosti prijave u dva koraka" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Izgubljen je pristup uređaju za dvostruku autentifikaciju? Koristite svoj kôd za oporavak za onemogućavanje svih pružatelja usluga dvostruke autentifikacije na tvojem računu." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Migrated from FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "Email" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." }, - "moveManyToOrgDesc": { - "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved." - }, "collectionsDesc": { "message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Verification code (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Regenerate password" - }, "length": { "message": "Length" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Danger zone" }, - "dangerZoneDesc": { - "message": "Careful, these actions are not reversible!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Deauthorize sessions" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Proceeding will also log you out of your current session, requiring you to log back in. You will also be prompted for two-step login again, if set up. Active sessions on other devices may continue to remain active for up to one hour." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "All sessions deauthorized" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Setting up two-step login can permanently lock you out of your Bitwarden account. A recovery code allows you to access your account in the event that you can no longer use your normal two-step login provider (example: you lose your device). Bitwarden support will not be able to assist you if you lose access to your account. We recommend you write down or print the recovery code and keep it in a safe place." }, + "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." + }, "viewRecoveryCode": { "message": "View recovery code" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Manage" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Turn off" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Revoke access" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "This two-step login provider is active on your account." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "There was a problem reading the security key. Try again." }, - "twoFactorWebAuthnWarning": { - "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used. Supported platforms:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Web vault and browser extensions on a desktop/laptop with a WebAuthn supported browser (Chrome, Opera, Vivaldi, or Firefox with FIDO U2F turned on)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Your Bitwarden two-step login recovery code" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "If you cannot access your account through your normal two-step login methods, you can use your two-step login recovery code to turn off all two-step providers on your account." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Recover account two-step login" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Set requirements for master password strength." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Require two-step login" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Set requirements for password generator." }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." - }, "masterPasswordPolicyInEffect": { "message": "One or more organization policies require your master password to meet the following requirements:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Organization owners and admins are exempt from this policy's enforcement." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "File" }, "sendTypeText": { "message": "Text" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Delete Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Are you sure you want to delete this Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "What type of Send is this?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maximum access count" }, - "maxAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Disabled" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Are you sure you want to remove the password?" }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, - "disableThisSend": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "All Sends" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Pending deletion" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Expired" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "The following organization policies are currently in effect:" - }, - "sendDisableHideEmailInEffect": { - "message": "Users are not allowed 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." - }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Remove individual ownership for organization users" }, - "textHiddenByDefault": { - "message": "When accessing the Send, hide the text by default", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to Send." - }, - "sendFileDesc": { - "message": "The file you want to Send." - }, - "copySendLinkOnSave": { - "message": "Copy the link to share this Send to my clipboard upon save." - }, - "sendLinkLabel": { - "message": "Send link", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "To verify your 2FA please click the button below." }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn is not supported in this browser." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, - "regenerateUsername": { - "message": "Regenerate username" - }, "generateUsername": { "message": "Generate username" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Username type" - }, "plusAddressedEmail": { "message": "Plus addressed email", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Hostname", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API access token" - }, "deviceVerification": { "message": "Device verification" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Broj korisnika" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "pickAnAvatarColor": { "message": "Pick an avatar color" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "No collection" }, - "canView": { - "message": "Can view" - }, - "canViewExceptPass": { - "message": "Can view, except passwords" - }, - "canEdit": { - "message": "Can edit" - }, - "canEditExceptPass": { - "message": "Can edit, except passwords" - }, "noCollectionsAdded": { "message": "No collections added" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domain" - }, "alreadyHaveAccount": { "message": "Already have an account?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index 1cf16dcc0fd..a4f2aeb7f55 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -1,24 +1,27 @@ { "allApplications": { - "message": "All applications" + "message": "Totes les aplicacions" }, "criticalApplications": { - "message": "Critical applications" + "message": "Aplicacions crítiques" + }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" }, "accessIntelligence": { - "message": "Access Intelligence" + "message": "Intel·ligència d'accés" }, "riskInsights": { - "message": "Risk Insights" + "message": "Coneixements de risc" }, "passwordRisk": { - "message": "Password Risk" + "message": "Risc de contrasenya" }, "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." + "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." }, "dataLastUpdated": { - "message": "Data last updated: $DATE$", + "message": "Última actualització de les dades: $DATE$", "placeholders": { "date": { "content": "$1", @@ -27,19 +30,19 @@ } }, "notifiedMembers": { - "message": "Notified members" + "message": "Membres notificats" }, "revokeMembers": { - "message": "Revoke members" + "message": "Revoca membres" }, "restoreMembers": { - "message": "Restore members" + "message": "Restaura membres" }, "cannotRestoreAccessError": { - "message": "Cannot restore organization access" + "message": "No es pot restaurar l'accés a l'organització" }, "allApplicationsWithCount": { - "message": "All applications ($COUNT$)", + "message": "Totes les aplicacions ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -48,10 +51,10 @@ } }, "createNewLoginItem": { - "message": "Create new login item" + "message": "Crea un nou element d'inici de sessió" }, "criticalApplicationsWithCount": { - "message": "Critical applications ($COUNT$)", + "message": "Aplicacions crítiques ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -60,7 +63,7 @@ } }, "notifiedMembersWithCount": { - "message": "Notified members ($COUNT$)", + "message": "Membres notificats ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -96,25 +99,58 @@ "message": "Apps marked as critical" }, "application": { - "message": "Application" + "message": "Aplicació" }, "atRiskPasswords": { "message": "At-risk passwords" }, "requestPasswordChange": { - "message": "Request password change" + "message": "Sol·licita canvi de contrasenya" }, "totalPasswords": { - "message": "Total passwords" + "message": "Contrasenyes totals" }, "searchApps": { - "message": "Search applications" + "message": "Cerca d'aplicacions" }, "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { - "message": "Total members" + "message": "Nombre total de membres" }, "atRiskApplications": { "message": "At-risk applications" @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "Quin tipus d'element és aquest?" }, @@ -159,8 +201,11 @@ "notes": { "message": "Notes" }, + "privateNote": { + "message": "Private note" + }, "note": { - "message": "Note" + "message": "Nota" }, "customFields": { "message": "Camps personalitzats" @@ -172,16 +217,16 @@ "message": "Credencials d'inici de sessió" }, "personalDetails": { - "message": "Personal details" + "message": "Detalls personals" }, "identification": { - "message": "Identification" + "message": "Identificació" }, "contactInfo": { - "message": "Contact info" + "message": "Informació de contacte" }, "cardDetails": { - "message": "Card details" + "message": "Dades de la targeta" }, "cardBrandDetails": { "message": "$BRAND$ details", @@ -193,7 +238,7 @@ } }, "itemHistory": { - "message": "Item history" + "message": "Historial d'elements" }, "authenticatorKey": { "message": "Clau autenticadora" @@ -345,7 +390,7 @@ "message": "Dr." }, "cardExpiredTitle": { - "message": "Expired card" + "message": "Targeta de crèdit caducada" }, "cardExpiredMessage": { "message": "If you've renewed it, update the card's information" @@ -383,6 +428,9 @@ "dragToSort": { "message": "Arrossega per ordenar" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Text" }, @@ -393,17 +441,17 @@ "message": "Booleà" }, "cfTypeCheckbox": { - "message": "Checkbox" + "message": "Casella de selecció" }, "cfTypeLinked": { "message": "Enllaçat", "description": "This describes a field that is 'linked' (related) to another field." }, "fieldType": { - "message": "Field type" + "message": "Tipus de camp" }, "fieldLabel": { - "message": "Field label" + "message": "Etiqueta del camp" }, "remove": { "message": "Suprimeix" @@ -425,6 +473,31 @@ "editFolder": { "message": "Edita la carpeta" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "Carpeta nova" + }, + "folderName": { + "message": "Nom de la carpeta" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "Domini base", "description": "Domain name. Example: website.com" @@ -469,7 +542,7 @@ "message": "Genera contrasenya" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "Genera frase de pas" }, "checkPassword": { "message": "Comprova si la contrasenya ha estat exposada." @@ -572,7 +645,7 @@ "message": "Nota segura" }, "typeSshKey": { - "message": "SSH key" + "message": "Clau SSH" }, "typeLoginPlural": { "message": "Inicis de sessió" @@ -605,7 +678,7 @@ "message": "Nom complet" }, "address": { - "message": "Address" + "message": "Adreça" }, "address1": { "message": "Adreça 1" @@ -650,7 +723,7 @@ "message": "Visualitza l'element" }, "newItemHeader": { - "message": "New $TYPE$", + "message": "Nou $TYPE$", "placeholders": { "type": { "content": "$1", @@ -659,7 +732,7 @@ } }, "editItemHeader": { - "message": "Edit $TYPE$", + "message": "Edita $TYPE$", "placeholders": { "type": { "content": "$1", @@ -668,7 +741,7 @@ } }, "viewItemType": { - "message": "View $ITEMTYPE$", + "message": "Mostra $ITEMTYPE$", "placeholders": { "itemtype": { "content": "$1", @@ -689,15 +762,6 @@ "itemName": { "message": "Nom d'element" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "ex.", "description": "Short abbreviation for 'example'." @@ -722,7 +786,7 @@ } }, "copySuccessful": { - "message": "Copy Successful" + "message": "Còpia correcta" }, "copyValue": { "message": "Copia el valor", @@ -733,11 +797,11 @@ "description": "Copy password to clipboard" }, "copyPassphrase": { - "message": "Copy passphrase", + "message": "Copia frase de pas", "description": "Copy passphrase to clipboard" }, "passwordCopied": { - "message": "Password copied" + "message": "S'ha copiat la contrasenya" }, "copyUsername": { "message": "Copia el nom d'usuari", @@ -756,7 +820,7 @@ "description": "Copy URI to clipboard" }, "copyCustomField": { - "message": "Copy $FIELD$", + "message": "Copia $FIELD$", "placeholders": { "field": { "content": "$1", @@ -765,34 +829,34 @@ } }, "copyWebsite": { - "message": "Copy website" + "message": "Copia el lloc web" }, "copyNotes": { - "message": "Copy notes" + "message": "Copia notes" }, "copyAddress": { - "message": "Copy address" + "message": "Copia l'adreça" }, "copyPhone": { - "message": "Copy phone" + "message": "Copia telèfon" }, "copyEmail": { - "message": "Copy email" + "message": "Copia el correu electrònic" }, "copyCompany": { - "message": "Copy company" + "message": "Copia empresa" }, "copySSN": { - "message": "Copy Social Security number" + "message": "Copia número de la Seguretat Social" }, "copyPassportNumber": { - "message": "Copy passport number" + "message": "Copia el número de passaport" }, "copyLicenseNumber": { - "message": "Copy license number" + "message": "Copia el número de llicència" }, "copyName": { - "message": "Copy name" + "message": "Copia el nom" }, "me": { "message": "Jo" @@ -815,9 +879,6 @@ "filter": { "message": "Filtre" }, - "moveSelectedToOrg": { - "message": "Desplaça la selecció a l'organització" - }, "deleteSelected": { "message": "Suprimeix selecció" }, @@ -873,17 +934,8 @@ } } }, - "movedItemsToOrg": { - "message": "Els elements seleccionats s'han desplaçat a $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { - "message": "Items moved to $ORGNAME$", + "message": "S'han desplaçat elements a $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -892,7 +944,7 @@ } }, "itemMovedToOrg": { - "message": "Item moved to $ORGNAME$", + "message": "S'ha desplaçat un element a $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -946,25 +998,25 @@ "message": "Nivell d'accés" }, "accessing": { - "message": "Accessing" + "message": "Accedint a" }, "loggedOut": { "message": "Sessió tancada" }, "loggedOutDesc": { - "message": "You have been logged out of your account." + "message": "Heu tancat la sessió del compte." }, "loginExpired": { "message": "La vostra sessió ha caducat." }, "restartRegistration": { - "message": "Restart registration" + "message": "Reinicia el registre" }, "expiredLink": { "message": "Enllaç caducat" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "Reinicieu el registre o proveu d'iniciar sessió." }, "youMayAlreadyHaveAnAccount": { "message": "És possible que ja tingueu un compte" @@ -984,6 +1036,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "Inicieu sessió o creeu un compte nou per accedir a la caixa forta." }, @@ -994,7 +1049,7 @@ "message": "L'inici de sessió amb el dispositiu ha d'estar activat a la configuració de l'aplicació Bitwarden. Necessiteu una altra opció?" }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "Necessiteu una altra opció?" }, "loginWithMasterPassword": { "message": "Inici de sessió amb contrasenya mestra" @@ -1009,13 +1064,13 @@ "message": "Utilitzeu un mètode d'inici de sessió diferent" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Inicieu sessió amb la clau de pas" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "Usa inici de sessió únic" }, "welcomeBack": { - "message": "Welcome back" + "message": "Benvingut/da de nou" }, "invalidPasskeyPleaseTryAgain": { "message": "Clau d'accés no vàlida. Torneu-ho a provar." @@ -1099,7 +1154,7 @@ "message": "Crea un compte" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Nou a Bitwarden?" }, "setAStrongPassword": { "message": "Estableix una contrasenya segura" @@ -1117,20 +1172,44 @@ "message": "Inicia sessió" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Inicia sessió a Bitwarden" + }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "Temps d'espera d'autenticació" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "La sessió d'autenticació s'ha esgotat. Reinicieu el procés d'inici de sessió." }, - "verifyIdentity": { - "message": "Verificació de la vostra identitat" + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "No reconeixem aquest dispositiu. Introduïu el codi que us hem enviat al correu electrònic per verificar la identitat." + }, + "continueLoggingIn": { + "message": "Continua l'inici de sessió" + }, + "whatIsADevice": { + "message": "Què és un dispositiu?" + }, + "aDeviceIs": { + "message": "Un dispositiu és una instal·lació única de l'aplicació Bitwarden on heu iniciat la sessió. Si torneu a instal·lar, suprimir les dades de l'aplicació o suprimir les galetes, pot ser que un dispositiu aparega diverses vegades." }, "logInInitiated": { "message": "S'ha iniciat la sessió" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Envia" }, @@ -1162,7 +1241,7 @@ "message": "Pista de la contrasenya mestra (opcional)" }, "newMasterPassHint": { - "message": "New master password hint (optional)" + "message": "Pista de la contrasenya mestra (opcional)" }, "masterPassHintLabel": { "message": "Pista de la contrasenya mestra" @@ -1184,22 +1263,16 @@ "message": "Configuració" }, "accountEmail": { - "message": "Account email" + "message": "Correu electrònic del compte" }, "requestHint": { - "message": "Request hint" + "message": "Sol·licita pista" }, "requestPasswordHint": { - "message": "Request password hint" + "message": "Sol·licita pista de la contrasenya" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { - "message": "Enter your account email address and your password hint will be sent to you" - }, - "passwordHint": { - "message": "Pista de la contrasenya" - }, - "enterEmailToGetHint": { - "message": "Introduïu l'adreça electrònica del vostre compte per rebre la contrasenya mestra." + "message": "Introduïu l'adreça de correu electrònic del compte i se us enviarà la pista de contrasenya" }, "getMasterPasswordHint": { "message": "Obteniu la pista de la contrasenya mestra" @@ -1233,10 +1306,10 @@ "message": "El vostre compte s'ha creat correctament. Ara ja podeu entrar." }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "S'ha creat el vostre compte nou!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "Heu iniciat sessió!" }, "trialAccountCreated": { "message": "Compte creat amb èxit." @@ -1257,7 +1330,7 @@ "message": "La caixa forta està bloquejada" }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "El compte està bloquejat" }, "uuid": { "message": "UUID" @@ -1320,11 +1393,38 @@ "notificationSentDevice": { "message": "S'ha enviat una notificació al vostre dispositiu." }, - "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirmeu l'accés" + }, + "denyAccess": { + "message": "Denega l'accés" + }, + "notificationSentDeviceAnchor": { + "message": "aplicació web" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, + "aNotificationWasSentToYourDevice": { + "message": "S'ha enviat una notificació al vostre dispositiu" }, "versionNumber": { "message": "Versió $VERSION_NUMBER$", @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Recorda'm" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Envia el codi de verificació altra vegada" }, "useAnotherTwoStepMethod": { "message": "Utilitzeu un altre mètode d'inici de sessió en dues passes" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Introduïu el vostre YubiKey al port USB de l'ordinador i, a continuació, premeu el seu botó." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Opcions d'inici de sessió en dos passos" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Heu perdut l'accés a tots els vostres proveïdors de dos factors? Utilitzeu el vostre codi de recuperació per desactivar tots els proveïdors de dos factors del vostre compte." }, @@ -1400,7 +1513,7 @@ "message": "Clau de seguretat OTP de Yubico" }, "yubiKeyDesc": { - "message": "Utilitzeu una YubiKey per accedir al vostre compte. Funciona amb els dispositius YubiKey 4, 4 Nano, 4C i NEO." + "message": "Utilitzeu un dispositiu YubiKey 4, 5 o NEO." }, "duoDescV2": { "message": "Enter a code generated by Duo Security.", @@ -1417,19 +1530,22 @@ "message": "Clau de seguretat FIDO U2F" }, "webAuthnTitle": { - "message": "FIDO2 WebAuthn" + "message": "Clau d'accés" }, "webAuthnDesc": { - "message": "Utilitzeu qualsevol clau de seguretat habilitada per WebAuthn per accedir al vostre compte." + "message": "Utilitzeu la biometria del vostre dispositiu o una clau de seguretat compatible amb FIDO2." }, "webAuthnMigrated": { "message": "(Migrat de FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "Correu electrònic" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "Introduïu el codi que us hem enviat al correu electrònic." }, "continue": { "message": "Continua" @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Trieu una organització a la qual vulgueu desplaçar aquest element. El trasllat a una organització transfereix la propietat de l'element a aquesta organització. Ja no sereu el propietari directe d'aquest element una vegada s'haja mogut." }, - "moveManyToOrgDesc": { - "message": "Trieu una organització a la qual vulgueu desplaçar aquests elements. El trasllat a una organització transfereix la propietat dels elements a aquesta organització. Ja no sereu el propietari directe d'aquests elements una vegada s'hagen mogut." - }, "collectionsDesc": { "message": "Editeu les col·leccions amb les què es comparteix aquest element. Només els usuaris de l'organització que tinguen accés a aquestes col·leccions podran veure'l." }, @@ -1471,7 +1584,7 @@ "message": "Esteu segur que voleu continuar?" }, "moveSelectedItemsDesc": { - "message": "Trieu una carpeta a la que vulgueu afegir els $COUNT$ elements seleccionats.", + "message": "Trieu una carpeta a la qual vulgueu afegir els $COUNT$ elements seleccionats.", "placeholders": { "count": { "content": "$1", @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "Heu seleccionat $COUNT$ elements. $MOVEABLE_COUNT$ elements es poden desplaçar a una organització, $NONMOVEABLE_COUNT$ no.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Codi de verificació (TOTP)" }, @@ -1610,12 +1706,9 @@ "description": "deprecated. Use avoidAmbiguous instead." }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "Eviteu caràcters ambigus", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Regenera contrasenya" - }, "length": { "message": "Longitud" }, @@ -1651,29 +1744,29 @@ "message": "Inclou número" }, "generatorPolicyInEffect": { - "message": "Enterprise policy requirements have been applied to your generator options.", + "message": "Els requisits de la política empresarial s'han aplicat a les opcions del generador.", "description": "Indicates that a policy limits the credential generator screen." }, "passwordHistory": { "message": "Historial de les contrasenyes" }, "generatorHistory": { - "message": "Generator history" + "message": "Historial del generador" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "Neteja l'historial del generador" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "Si continueu, totes les entrades se suprimiran permanentment de l'historial del generador. Esteu segur que voleu continuar?" }, "noPasswordsInList": { "message": "No hi ha cap contrasenya a llistar." }, "clearHistory": { - "message": "Clear history" + "message": "Neteja l'historial" }, "nothingToShow": { - "message": "Nothing to show" + "message": "Res a mostrar" }, "nothingGeneratedRecently": { "message": "You haven't generated anything recently" @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Torneu a iniciar sessió." }, + "currentSession": { + "message": "Sessió actual" + }, + "requestPending": { + "message": "Sol·licitud pendent" + }, "logBackInOthersToo": { "message": "Torneu a iniciar la sessió. Si esteu utilitzant altres aplicacions Bitwarden, tanqueu-les i torneu-les a obrir també." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Zona perillosa" }, - "dangerZoneDesc": { - "message": "Aneu amb compte, aquestes accions no són reversibles!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Desautoritza sessions" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "El procediment també tancarà la sessió actual, i l'heu de tornar a iniciar. També demanarà iniciar la sessió en dues passes, si està habilitada. Les sessions actives d'altres dispositius poden mantenir-se actives fins a una hora." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "Totes les sessions estan desautoritzades" }, @@ -1866,7 +1980,7 @@ "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLoginLink": { - "message": "new login", + "message": "inici de sessió nou", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new login instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsPartTwoNoOrgs": { @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Si habiliteu l'inici de sessió en dues passes, pot bloquejar-vos de manera definitiva el compte de Bitwarden. Un codi de recuperació us permet accedir al vostre compte en cas que no pugueu utilitzar el proveïdor d'inici de sessió en dues passes (p. Ex. Perdre el dispositiu). El suport de Bitwarden no podrà ajudar-vos si perdeu l'accés al vostre compte. Us recomanem que escriviu o imprimiu el codi de recuperació i el mantingueu en un lloc segur." }, + "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." + }, "viewRecoveryCode": { "message": "Mostra el codi de recuperació" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Administra" }, - "canManage": { - "message": "Pot gestionar" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edita elements" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Inhabilita" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Revoca l'accés" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "Aquest proveïdor d'inici de sessió en dos passos està habilitat al vostre compte." }, @@ -2120,7 +2252,7 @@ "message": "," }, "twoStepAuthenticatorInstructionInfix2": { - "message": "or" + "message": "o" }, "twoStepAuthenticatorInstructionSuffix": { "message": "." @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "Hi ha hagut un problema en llegir la clau de seguretat. Torneu-ho a provar." }, - "twoFactorWebAuthnWarning": { - "message": "A causa de les limitacions de la plataforma, WebAuthn no es pot utilitzar en totes les aplicacions Bitwarden. Heu d’habilitar un altre proveïdor d’inici de sessió en dos passos perquè pugueu accedir al vostre compte quan no es puga utilitzar WebAuthn. Plataformes compatibles:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Caixa forta web i extensions de navegador en un escriptori/portàtil amb un navegador compatible amb WebAuthn (Chrome, Opera, Vivaldi, o Firefox amb FIDO U2F activat)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "El codi de recuperació d'inici de sessió en dues passes de Bitwarden" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "Aquest usuari fa servir l'inici de sessió en dues passes per protegir el seu compte." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "SSO sense enllaçar per a l'usuari $ID$.", "placeholders": { @@ -3765,11 +3903,81 @@ "device": { "message": "Dispositiu" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Ara mateix" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, "checkYourEmail": { - "message": "Check your email" + "message": "Comprova el correu" }, "followTheLinkInTheEmailSentTo": { "message": "Follow the link in the email sent to" @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "Esteu utilitzant un navegador web no compatible. La caixa forta web pot no funcionar correctament." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "Si no podeu accedir al vostre compte a través dels vostres mètodes d'inici de sessió de dues passes, podeu utilitzar el codi de recuperació de l'inici de sessió en dues passes per desactivar tots els proveïdors del vostre compte." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Recupera l'inici de sessió en dues passes del compte" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4331,7 +4600,7 @@ "message": "By continuing, you agree to the" }, "and": { - "message": "and" + "message": "i" }, "acceptPolicies": { "message": "Si activeu aquesta casella, indiqueu que esteu d’acord amb el següent:" @@ -4352,7 +4621,7 @@ "message": "Temps d'espera de la caixa forta" }, "vaultTimeout1": { - "message": "Timeout" + "message": "Temps d'espera" }, "vaultTimeoutDesc": { "message": "Trieu quan es tancarà la vostra caixa forta i feu l'acció seleccionada." @@ -4421,7 +4690,7 @@ "message": "Seleccionat" }, "recommended": { - "message": "Recommended" + "message": "Recomanat" }, "ownership": { "message": "Propietat" @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Estableix els requisits mínims per al nivell de seguretat de la contrasenya principal." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Cal iniciar sessió en dos passos" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Estableix els requisits mínims per a la configuració del generador de contrasenyes." }, - "passwordGeneratorPolicyInEffect": { - "message": "Una o més polítiques d’organització afecten la configuració del generador." - }, "masterPasswordPolicyInEffect": { "message": "Una o més polítiques d’organització requereixen que la vostra contrasenya principal complisca els requisits següents:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Els propietaris i administradors d’organitzacions estan exempts de fer complir aquesta política." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "Fitxer" }, "sendTypeText": { "message": "Text" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Nou Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Suprimeix el Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Esteu segur que voleu suprimir aquest Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "Quin tipus de Send és aquest?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Data de supressió" }, - "deletionDateDesc": { - "message": "L'enviament se suprimirà permanentment a la data i hora especificades.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Recompte màxim d'accés" }, - "maxAccessCountDesc": { - "message": "Si s’estableix, els usuaris ja no podran accedir a aquest Send una vegada s’assolisca el nombre màxim d’accessos.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Recompte d’accés actual" - }, - "sendPasswordDesc": { - "message": "Opcionalment, necessiteu una contrasenya perquè els usuaris accedisquen a aquest enviament.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Notes privades sobre aquest enviament.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Inhabilitat" }, @@ -4908,7 +5192,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "copyLink": { - "message": "Copy link" + "message": "Copia l'enllaç" }, "copySendLink": { "message": "Copia l'enllaç Send", @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Esteu segur que voleu suprimir la contrasenya?" }, - "hideEmail": { - "message": "Amagueu la meua adreça de correu electrònic als destinataris." - }, - "disableThisSend": { - "message": "Desactiveu aquest enviament perquè ningú no hi puga accedir.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Tots els Send" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Pendent de supressió" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Caducat" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "Actualment estan en vigor les polítiques organitzatives següents:" - }, - "sendDisableHideEmailInEffect": { - "message": "No es permet als usuaris amagar la seua adreça de correu electrònic dels destinataris en crear o editar un Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "modifiedPolicyId": { "message": "Política modificada $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Desactiva la propietat personal per als usuaris de l'organització" }, - "textHiddenByDefault": { - "message": "Quan accediu a Enviar, amaga el text per defecte", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Un nom apropiat per descriure aquest Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "El text que voleu pel Send." - }, - "sendFileDesc": { - "message": "El fitxer que voleu pel Send." - }, - "copySendLinkOnSave": { - "message": "Copie l'enllaç per compartir aquest Send al meu porta-retalls després de guardar-lo." - }, - "sendLinkLabel": { - "message": "Enllaç Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5414,7 +5666,7 @@ } }, "viewSend": { - "message": "View Send", + "message": "Veure Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "viewSendHiddenEmailWarning": { @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "S'ha produït un error en guardar les dates de supressió i caducitat." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "Per verificar el vostre 2FA, feu clic al botó següent." }, "webAuthnAuthenticate": { "message": "Autenticar WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn no és compatible amb aquest navegador." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "L'administració d'usuaris també ha d'estar habilitada amb el permís de recuperació del compte de gestió" }, @@ -6516,15 +6791,6 @@ "message": "Generador", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "Què voleu generar?" - }, - "passwordType": { - "message": "Tipus de contrasenya" - }, - "regenerateUsername": { - "message": "Regenera el nom d'usuari" - }, "generateUsername": { "message": "Genera un nom d'usuari" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Tipus de nom d'usuari" - }, "plusAddressedEmail": { "message": "Adreça amb sufix", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Utilitzeu la safata d'entrada global configurada del vostre domini." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Aleatori", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Nom de l'amfitrió", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "Token d'accés a l'API" - }, "deviceVerification": { "message": "Verificació del dispositiu" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Es requereix l'inici de sessió en dos passos de DUO al vostre compte." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Inicia DUO" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Nombre d'usuaris" }, - "loggingInAs": { - "message": "Has iniciat sessió com" - }, - "notYou": { - "message": "No sou vosaltres?" - }, "pickAnAvatarColor": { "message": "Tria un color d'avatar" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "Cap col·lecció" }, - "canView": { - "message": "Pot veure" - }, - "canViewExceptPass": { - "message": "Pot veure, excepte les contrasenyes" - }, - "canEdit": { - "message": "Pot editar" - }, - "canEditExceptPass": { - "message": "Pot editar, excepte les contrasenyes" - }, "noCollectionsAdded": { "message": "No s'han afegit col·leccions" }, @@ -7802,7 +8077,7 @@ "message": "Càrrega de fitxers" }, "upload": { - "message": "Upload" + "message": "Puja" }, "acceptedFormats": { "message": "Formats acceptats:" @@ -7814,10 +8089,10 @@ "message": "o" }, "unlockWithBiometrics": { - "message": "Unlock with biometrics" + "message": "Desbloqueja amb dades biomètriques" }, "unlockWithPin": { - "message": "Unlock with PIN" + "message": "Desbloqueja amb codi PIN" }, "unlockWithMasterPassword": { "message": "Unlock with master password" @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Sol·liciteu l'aprovació de l'administrador" }, - "approveWithMasterPassword": { - "message": "Aprova amb contrasenya mestra" - }, "trustedDeviceEncryption": { "message": "Encriptació de dispositius de confiança" }, "trustedDevices": { "message": "Dispositius de confiança" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Una vegada autenticats, els membres desxifraran les dades de la caixa forta mitjançant una clau emmagatzemada al seu dispositiu. La", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "política d'organització", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "single organization", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": "única,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "policy,", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "la política de", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO required", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "requerida y la política", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "policy, and", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "d'administració de recuperació del compte", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "account recovery administration", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "amb inscripció automàtica s'activaran quan s'utilitze aquesta opció.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Els permisos de la vostra organització s'han actualitzat, cal que establiu una contrasenya mestra.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Aprova la sol·licitud" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No hi ha sol·licituds de dispositiu" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "La vostra sol·licitud s'ha enviat a l'administrador." }, - "youWillBeNotifiedOnceApproved": { - "message": "Se us notificarà una vegada aprovat." - }, "troubleLoggingIn": { "message": "Teniu problemes per iniciar la sessió?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Els propietaris i els administradors poden gestionar totes les col·leccions i articles" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alies de domini" - }, "alreadyHaveAccount": { "message": "Ja tens un compte?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "No teniu accés per gestionar aquesta col·lecció." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Concedeix als grups o membres l'accés a aquesta col·lecció." @@ -8730,7 +9011,7 @@ "message": "Els ajustos dels seients es reflectiran en el pròxim cicle de facturació." }, "unassignedSeatsDescription": { - "message": "Seients de subscripció no assignats" + "message": "Places de subscripció no assignades" }, "purchaseSeatDescription": { "message": "Seients addicionals adquirits" @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "mes per membre" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seients" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index 64ce764de82..46b510a6459 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Kritické aplikace" }, + "noCriticalAppsAtRisk": { + "message": "Žádné ohrožené kritické aplikace" + }, "accessIntelligence": { "message": "Přístup k inteligenci" }, @@ -113,15 +116,54 @@ "atRiskMembers": { "message": "Ohrožení členové" }, + "atRiskMembersWithCount": { + "message": "Rizikoví členové ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Rizikové aplikace ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "Tito členové se přihlašují do aplikací se slabými, odhalenými nebo znovu použitými hesly." + }, + "atRiskApplicationsDescription": { + "message": "Tyto aplikace mají slabá, odhalená nebo opakovaně používaná hesla." + }, + "atRiskMembersDescriptionWithApp": { + "message": "Tito členové se přihlašují do $APPNAME$ se slabými, odhalenými nebo znovu použitými hesly.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Celkem členů" }, "atRiskApplications": { - "message": "Ohrožené aplikace" + "message": "Rizikové aplikace" }, "totalApplications": { "message": "Celkem aplikací" }, + "unmarkAsCriticalApp": { + "message": "Zrušit označení jako kritické aplikace" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Kritická aplikace úspěšně odoznačena" + }, "whatTypeOfItem": { "message": "O jaký typ položky se jedná?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Poznámka" }, + "privateNote": { + "message": "Soukromá poznámka" + }, "note": { "message": "Poznámka" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Přetáhnutím seřadíte" }, + "dragToReorder": { + "message": "Přesuňte tažením" + }, "cfTypeText": { "message": "Text" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Upravit složku" }, + "editWithName": { + "message": "Upravit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "Nová složka" + }, + "folderName": { + "message": "Název složky" + }, + "folderHintText": { + "message": "Vnořte složku přidáním názvu nadřazené složky následovaného znakem \"/\". Příklad: Sociální/Fóra" + }, + "deleteFolderPermanently": { + "message": "Opravdu chcete trvale smazat tuto složku?" + }, "baseDomain": { "message": "Základní doména", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Název položky" }, - "cannotRemoveViewOnlyCollections": { - "message": "Nemůžete odebrat kolekce s oprávněními jen pro zobrazení: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "např.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filtr" }, - "moveSelectedToOrg": { - "message": "Přesunout vybrané do organizace" - }, "deleteSelected": { "message": "Smazat vybrané" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Vybrané položky přesunuty do $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Položky přesunuty do $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "Ne" }, + "location": { + "message": "Umístění" + }, "loginOrCreateNewAccount": { "message": "Pro přístup do Vašeho bezpečného trezoru se přihlaste nebo si vytvořte nový účet." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Přihlásit se do Bitwardenu" }, + "enterTheCodeSentToYourEmail": { + "message": "Zadejte kód odeslaný na Váš e-mail" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Zadejte kód z Vaší ověřovací aplikace" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Stiskněte svůj YubiKey pro ověření" + }, "authenticationTimeout": { "message": "Časový limit ověření" }, "authenticationSessionTimedOut": { "message": "Vypršel časový limit relace ověřování. Restartujte proces přihlášení." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Ověřte svou totožnost" }, + "weDontRecognizeThisDevice": { + "message": "Toto zařízení nepoznáváme. Zadejte kód zaslaný na Váš e-mail pro ověření Vaší totožnosti." + }, + "continueLoggingIn": { + "message": "Pokračovat v přihlášení" + }, + "whatIsADevice": { + "message": "Co je to zařízení?" + }, + "aDeviceIs": { + "message": "Zařízení je jedinečná instalace aplikace Bitwarden, ve které jste se přihlásili. Přeinstalování, vymazání dat aplikace nebo vymazání souborů cookie může vést k tomu, že se zařízení objeví vícekrát." + }, "logInInitiated": { "message": "Bylo zahájeno přihlášení" }, + "logInRequestSent": { + "message": "Požadavek odeslán" + }, "submit": { "message": "Odeslat" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Zadejte svou e-mailovou adresu, na kterou Vám zašleme nápovědu k heslu" }, - "passwordHint": { - "message": "Nápověda pro heslo" - }, - "enterEmailToGetHint": { - "message": "Zadejte e-mailovou adresu pro zaslání nápovědy k hlavnímu heslu." - }, "getMasterPasswordHint": { "message": "Zaslat nápovědu k hlavnímu heslu" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "Na Vaše zařízení bylo odesláno oznámení." }, + "notificationSentDevicePart1": { + "message": "Odemknout Bitwarden na Vašem zařízení nebo na " + }, + "areYouTryingToAccessYourAccount": { + "message": "Pokoušíte se získat přístup k Vašemu účtu?" + }, + "accessAttemptBy": { + "message": "Pokus o přístup z $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Potvrdit přístup" + }, + "denyAccess": { + "message": "Zamítnout přístup" + }, + "notificationSentDeviceAnchor": { + "message": "webová aplikace" + }, + "notificationSentDevicePart2": { + "message": "Před schválením se ujistěte, že fráze otisku prstu odpovídá frázi níže." + }, + "notificationSentDeviceComplete": { + "message": "Odemkněte Bitwarden na Vašem zařízení. Před schválením se ujistěte, že fráze otisku prstu odpovídá frázi níže." + }, "aNotificationWasSentToYourDevice": { "message": "Na Vaše zařízení bylo odesláno oznámení" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Ujistěte se, že je Váš trezor odemčen a fráze otisku prstu se shodují s druhým zařízením" - }, "versionNumber": { "message": "Verze $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Zapamatovat mě" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Neptat se na tomto zařízení 30 dnů" + }, "sendVerificationCodeEmailAgain": { "message": "Znovu zaslat ověřovací kód na e-mail" }, "useAnotherTwoStepMethod": { "message": "Použít jinou metodu dvoufázového přihlášení" }, + "selectAnotherMethod": { + "message": "Vybrat jinou metodu", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Použít obnovovací kód" + }, "insertYubiKey": { "message": "Vložte YubiKey do USB portu Vašeho počítače a stiskněte jeho tlačítko." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Volby dvoufázového přihlášení" }, + "selectTwoStepLoginMethod": { + "message": "Vyberte metodu dvoufázového přihlášení" + }, "recoveryCodeDesc": { "message": "Ztratili jste přístup ke všem nastaveným poskytovatelům dvoufázového přihlášení? Použijte obnovovací kód pro vypnutí dvoufázového přihlášení." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Migrováno z FIDO)" }, + "openInNewTab": { + "message": "Otevřít v nové kartě" + }, "emailTitle": { "message": "E-mail" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Vyberte organizaci, do které chcete tuto položku přesunout. Přesun do organizace převede vlastnictví položky této organizaci. Po přesunutí této položky již nebudete přímým vlastníkem této položky." }, - "moveManyToOrgDesc": { - "message": "Vyberte organizaci, do které chcete tyto položky přesunout. Přesun do organizace převede vlastnictví položek této organizaci. Po přesunutí již nebudete přímým vlastníkem těchto položek." - }, "collectionsDesc": { "message": "Upravte kolekce, ve kterých je tato položka sdílená. Pouze uživatelé organizace, kteří mají přístup k těmto kolekcím, budou moci tuto položku vidět." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "Vybrali jste $COUNT$ položek. $MOVEABLE_COUNT$ položek lze přesunout do organizace, $NONMOVEABLE_COUNT$ nelze.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Ověřovací kód (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Nepoužívat zaměnitelné znaky", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Vygenerovat jiné heslo" - }, "length": { "message": "Délka" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Přihlaste se znovu." }, + "currentSession": { + "message": "Aktuální relace" + }, + "requestPending": { + "message": "Čekající požadavek" + }, "logBackInOthersToo": { "message": "Přihlaste se znovu. Používáte-li jiné aplikace Bitwardenu, přihlaste se znovu i v nich." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Nebezpečná zóna" }, - "dangerZoneDesc": { - "message": "Opatrně. Tyto akce se nedají vrátit!" - }, - "dangerZoneDescSingular": { - "message": "Opatrně, tato akce je nevratná!" - }, "deauthorizeSessions": { "message": "Zrušit autorizaci relací" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Pokud chcete pokračovat, budete také odhlášeni z aktuální relace a bude nutné se znovu přihlásit. Pokud používáte dvoufázové přihlášení, bude také vyžadováno. Aktivní relace na jiných zařízeních mohou nadále zůstat aktivní po dobu až jedné hodiny." }, + "newDeviceLoginProtection": { + "message": "Nové přihlášení do zařízení" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Vypnout ochranu pro přihlášení z nového zařízení" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Zapnout ochranu pro přihlášení z nového zařízení" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Pokračujte níže a vypněte ověřovací e-maily od Bitwardenu, když se přihlásíte z nového zařízení." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Níže postupujte tak, aby Vám Bitwarden zasílal ověřovací e-maily při přihlášení z nového zařízení." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "Pokud je ochrana přihlášení z nového zařízení vypnutá, může kdokoli s Vaším hlavním heslem přistupovat k Vašemu účtu z libovolného zařízení. Chcete-li svůj účet chránit bez ověřovacích e-mailů, nastavte dvoufázové přihlašování." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "Změny ochrany přihlášení z nového zařízení byly uloženy" + }, "sessionsDeauthorized": { "message": "Všechny relace byly zrušeny" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Nastavením dvoufázového přihlášení můžete sami sobě znemožnit přihlášení k Vašemu účtu. Obnovovací kód umožňuje přístup do Vašeho účtu i v případě, pokud již nemůžete použít svůj normální způsob dvoufázového přihlášení (např. ztráta zařízení). Pokud ztratíte přístup ke svému účtu, nebude Vám schopna pomoci ani zákaznická podpora Bitwardenu. Doporučujeme si proto kód pro obnovení zapsat nebo vytisknout a uložit jej na bezpečném místě." }, + "yourSingleUseRecoveryCode": { + "message": "Jednorázový kód pro obnovení lze použít k vypnutí dvoufázového přihlašování v případě, že ztratíte přístup ke svému poskytovateli dvoufázového přihlašování. Bitwarden doporučuje, abyste si kód pro obnovení zapsali a uložili na bezpečném místě." + }, "viewRecoveryCode": { "message": "Zobrazit kód pro obnovení" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Spravovat" }, - "canManage": { - "message": "Může spravovat" + "manageCollection": { + "message": "Spravovat kolekci" + }, + "viewItems": { + "message": "Zobrazit položky" + }, + "viewItemsHidePass": { + "message": "Zobrazit položky, skrytá hesla" + }, + "editItems": { + "message": "Upravit položky" + }, + "editItemsHidePass": { + "message": "Upravit položky, skrytá hesla" }, "disable": { "message": "Vypnout" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Zrušit přístup" }, + "revoke": { + "message": "Odvolat" + }, "twoStepLoginProviderEnabled": { "message": "Tento poskytovatel dvoufázového přihlášení je ve Vašem účtu aktivní." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "Při čtení bezpečnostního klíče došlo k potížím. Zkuste to znovu." }, - "twoFactorWebAuthnWarning": { - "message": "Z důvodu omezení platformy nelze WebAuthn použít ve všech aplikacích Bitwarden. Měli byste využít i jiného poskytovatele dvoufaktorového přihlášení, abyste měli přístup ke svému účtu, když nelze použít WebAuthn. Podporované platformy:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Webový trezor a rozšíření pro prohlížeče na počítači/notebooku s podporou WebAuthn (Chrome, Opera, Vivaldi nebo Firefox se zapnutým FIDO U2F)." + "twoFactorWebAuthnWarning1": { + "message": "Z důvodu omezení platformy nelze WebAuthn použít ve všech aplikacích Bitwarden. Měli byste využít i jiného poskytovatele dvoufaktorového přihlášení, abyste měli přístup ke svému účtu, když nelze použít WebAuthn." }, "twoFactorRecoveryYourCode": { "message": "Váš kód pro obnovení dvoufázového přihlášení" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Zbývá Vám 1 pozvánka." + }, + "inviteZeroEmailDesc": { + "message": "Zbývá Vám 0 pozvánek." + }, "userUsingTwoStep": { "message": "Tento uživatel používá pro ochranu svého účtu dvoufázové přihlášení." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Nepropojené SSO." + }, "unlinkedSsoUser": { "message": "Bylo zrušeno propojení SSO pro uživatele $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Zařízení" }, + "loginStatus": { + "message": "Stav přihlášení" + }, + "firstLogin": { + "message": "První přihlášení" + }, + "trusted": { + "message": "Důvěryhodný" + }, + "needsApproval": { + "message": "Vyžaduje schválení" + }, + "areYouTryingtoLogin": { + "message": "Pokoušíte se přihlásit?" + }, + "logInAttemptBy": { + "message": "Pokus o přihlášení z $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Typ zařízení" + }, + "ipAddress": { + "message": "IP adresa" + }, + "confirmLogIn": { + "message": "Potvrdit přihlášení" + }, + "denyLogIn": { + "message": "Zamítnout přihlášení" + }, + "thisRequestIsNoLongerValid": { + "message": "Tento požadavek již není platný." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Přihlášení bylo potvrzeno z $EMAIL$ pro $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "Pokus o přihlášení byl zamítnut z jiného zařízení. Pokud jste to opravdu Vy, zkuste se znovu přihlásit do zařízení." + }, + "loginRequestHasAlreadyExpired": { + "message": "Požadavek na přihlášení již vypršel." + }, + "justNow": { + "message": "Právě teď" + }, + "requestedXMinutesAgo": { + "message": "Požadováno před $MINUTES$ minutami", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Vytváření účtu na" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "Používáte nepodporovaný webový prohlížeč. Webový trezor nemusí pracovat správně." }, + "youHaveAPendingLoginRequest": { + "message": "Máte čekající žádost o přihlášení z jiného zařízení." + }, + "reviewLoginRequest": { + "message": "Podívat se na žádost o přihlášení" + }, "freeTrialEndPromptCount": { "message": "Vaše zkušební doba končí za $COUNT$ dnů.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "Nemůžete-li přistoupit ke svému účtu pomocí běžné metody dvoufázového přihlášení, můžete pro vypnutí všech dvoufázových ověření ve Vašem účtu použít kód pro obnovení dvoufázového přihlášení." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Přihlaste se níže pomocí jednorázového kódu pro obnovení. Tím vypnete všechny dvoufázové poskytovatele na svém účtu." + }, "recoverAccountTwoStep": { "message": "Obnovit dvoufázové přihlášení k účtu" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Aktualizace šifrovacího klíče nemůže pokračovat" }, + "editFieldLabel": { + "message": "Upravit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Změnit pořadí $LABEL$. Použijte šipky pro posunutí položky nahoru nebo dolů.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ - přesunuto nahoru, pozice $INDEX$ z $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ - přesunuto dolů, pozice $INDEX$ z $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "Při aktualizaci šifrovacího klíče se nepodařilo dešifrovat Vaše složky. Chcete-li pokračovat v aktualizaci, musí být Vaše složky smazány. Pokud budete pokračovat, nebudou smazány žádné položky trezoru." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Nastavte minimální požadavky pro sílu hlavního hesla." }, + "passwordStrengthScore": { + "message": "Skóre síly hesla: $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Požadovat dvoufázové přihlášení" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Nastaví požadavky pro generátor hesel." }, - "passwordGeneratorPolicyInEffect": { - "message": "Jedna nebo více zásad organizace ovlivňují nastavení generátoru." - }, "masterPasswordPolicyInEffect": { "message": "Jedna nebo více zásad organizace vyžaduje, aby hlavní heslo splňovalo následující požadavky:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Majitelé a administrátoři organizací jsou od prosazování těchto zásad osvobozeni." }, + "limitSendViews": { + "message": "Omezit zobrazení" + }, + "limitSendViewsHint": { + "message": "Po dosažení limitu nebude nikdo moci zobrazit tento Send.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "Zbývá $ACCESSCOUNT$ zobrazení", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Podrobnosti Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text ke sdílení" + }, "sendTypeFile": { "message": "Soubor" }, "sendTypeText": { "message": "Text" }, + "sendPasswordDescV3": { + "message": "Přidá volitelné heslo pro příjemce pro přístup k tomuto Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Nový Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Smazat Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Opravdu chcete smazat tento Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "Jakého typu je tento Send?", + "deleteSendPermanentConfirmation": { + "message": "Opravdu chcete tento Send trvale smazat?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Datum smazání" }, - "deletionDateDesc": { - "message": "Tento Send bude trvale smazán v určený datum a čas.", + "deletionDateDescV2": { + "message": "Tento Send bude trvale smazán v určené datum.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maximální počet přístupů" }, - "maxAccessCountDesc": { - "message": "Je-li nastaveno, uživatelé již nebudou mít přístup k tomuto Send, jakmile bude dosaženo maximálního počtu přístupů.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Aktuální počet přístupů" - }, - "sendPasswordDesc": { - "message": "Volitelně vyžadovat heslo pro přístup k tomuto Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Soukromé poznámky o tomto Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Zakázáno" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Opravdu chcete odebrat heslo?" }, - "hideEmail": { - "message": "Skrýt moji e-mailovou adresu před příjemci" - }, - "disableThisSend": { - "message": "Deaktivovat tento Send, takže k němu nebude moci nikdo přistoupit", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Všechny Sends" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Čekání na smazání" }, + "hideTextByDefault": { + "message": "Ve výchozím nastavení skrýt text" + }, "expired": { "message": "Vypršela platnost" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "Aktuálně platí následující zásady organizace:" - }, - "sendDisableHideEmailInEffect": { - "message": "Uživatelé nesmí při vytváření nebo úpravách Send skrýt svou e-mailovou adresu před příjemci.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "modifiedPolicyId": { "message": "Byly změněny zásady $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Odebere osobní vlastnictví pro uživatele organizace" }, - "textHiddenByDefault": { - "message": "Při přístupu k Send skrýt text ve výchozím nastavení", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Přátelský název pro popis tohoto Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Text, který chcete odeslat." - }, - "sendFileDesc": { - "message": "Soubor, který chcete odeslat." - }, - "copySendLinkOnSave": { - "message": "Kopírovat odkaz pro sdílení tohoto Send do mé schránky při uložení" - }, - "sendLinkLabel": { - "message": "Odkaz tohoto Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "Došlo k chybě při ukládání datumů smazání a vypršení platnosti." }, + "hideYourEmail": { + "message": "Skryje Vaši e-mailovou adresu před zobrazením." + }, "webAuthnFallbackMsg": { "message": "Pro ověření dvoufaktorového ověření klepněte na tlačítko níže." }, "webAuthnAuthenticate": { "message": "Ověřit WebAuthn" }, + "readSecurityKey": { + "message": "Přečíst bezpečnostní klíč" + }, + "awaitingSecurityKeyInteraction": { + "message": "Čeká se na interakci s bezpečnostním klíčem..." + }, "webAuthnNotSupported": { "message": "WebAuthn není v tomto prohlížeči podporován." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Chyba" }, + "decryptionError": { + "message": "Chyba dešifrování" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nemohl dešifrovat níže uvedené položky v trezoru." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktujte zákaznickou podporu,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "abyste zabránili ztrátě dat.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "S oprávněním spravovat obnovení hesla musí být povolena také správa uživatelů" }, @@ -6516,15 +6791,6 @@ "message": "Generátor", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "Co chcete vygenerovat?" - }, - "passwordType": { - "message": "Typ hesla" - }, - "regenerateUsername": { - "message": "Znovu vygenerovat uživatelské jméno" - }, "generateUsername": { "message": "Vygenerovat uživatelské jméno" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Typ uživatelského jména" - }, "plusAddressedEmail": { "message": "E-mailová adresa s plusem", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Použijte nakonfigurovanou univerzální schránku své domény." }, + "useThisEmail": { + "message": "Použít tento e-mail" + }, "random": { "message": "Náhodně", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ odmítl Váš požadavek. Kontaktujte svého poskytovatele služeb o pomoc.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ odmítl Váš požadavek: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Nelze získat maskované ID e-mailového účtu $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Název hostitele", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "Přístupový token API" - }, "deviceVerification": { "message": "Ověřování zařízení" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Pro Váš účet je vyžadováno dvoufázové přihlášení Duo." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Pro Váš účet je nutné dvoufázové přihlášení. Pro dokončení přihlášení postupujte podle následujících kroků." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Postupujte podle kroků níže pro dokončení přihlášení." + }, "launchDuo": { "message": "Spustit Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Počet uživatelů" }, - "loggingInAs": { - "message": "Přihlašování jako" - }, - "notYou": { - "message": "Nejste to Vy?" - }, "pickAnAvatarColor": { "message": "Zvolte barvu avatara" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "Žádná kolekce" }, - "canView": { - "message": "Může zobrazit" - }, - "canViewExceptPass": { - "message": "Může zobrazit kromě hesel" - }, - "canEdit": { - "message": "Může upravovat" - }, - "canEditExceptPass": { - "message": "Může upravovat kromě hesel" - }, "noCollectionsAdded": { "message": "Nebyly přidány žádné kolekce" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Žádost o schválení správcem" }, - "approveWithMasterPassword": { - "message": "Schválit hlavním heslem" - }, "trustedDeviceEncryption": { "message": "Šifrování důvěryhodného zařízení" }, "trustedDevices": { "message": "Důvěryhodná zařízení" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Po ověření budou členové dešifrovat data v trezoru pomocí klíče uloženého na jejich zařízení. ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "Při použití této možnosti ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "single organization", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": "se zapnou zásady jednotné organizace, ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "policy,", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "zásady vyžadované SSO ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO required", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "a zásady správy obnovení účtu ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "policy, and", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "s ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "account recovery administration", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "s automatickým zápisem.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Oprávnění Vaší organizace byla aktualizována. To vyžaduje nastavení hlavního hesla.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Schválit žádost" }, + "deviceApproved": { + "message": "Zařízení schváleno" + }, + "deviceRemoved": { + "message": "Zařízení odebráno" + }, + "removeDevice": { + "message": "Odebrat zařízení" + }, + "removeDeviceConfirmation": { + "message": "Opravdu chcete odebrat toto zařízení?" + }, "noDeviceRequests": { "message": "Žádné žádosti ze zařízení" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Váš požadavek byl odeslán Vašemu správci." }, - "youWillBeNotifiedOnceApproved": { - "message": "Po schválení budete upozorněni." - }, "troubleLoggingIn": { "message": "Potíže s přihlášením?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Omezí mazání kolekce na vlastníky a správce" }, + "limitItemDeletionDesc": { + "message": "Omezit smazání položky na členy s oprávněním ke správě" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Vlastníci a správci mohou spravovat všechny kolekce a předměty" }, @@ -8494,9 +8778,6 @@ "message": "Adresa URL serveru vlastního hostování", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Doména aliasu" - }, "alreadyHaveAccount": { "message": "Už máte účet?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "Nemáte přístup ke správě této kolekce." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Chybějící oprávnění \"Může spravovat\"" + "grantManageCollectionWarningTitle": { + "message": "Chybějící oprávnění pro správu kolekcí" }, - "grantAddAccessCollectionWarning": { - "message": "Udělte oprávnění \"Může spravovat\" pro úplnou správu kolekce, včetně jejího smazání." + "grantManageCollectionWarning": { + "message": "Udělte oprávnění \"Spravovat kolekci\" pro úplnou správu kolekce, včetně jejího smazání." }, "grantCollectionAccess": { "message": "Udělí skupinám nebo členům přístup k této kolekci." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Nastaví správu zařízení pro Bitwarden pomocí implementačního průvodce pro Vaši platformu." }, + "deviceIdMissing": { + "message": "Chybí ID zařízení" + }, + "deviceTypeMissing": { + "message": "Chybí typ zařízení" + }, + "deviceCreationDateMissing": { + "message": "Chybí datum vytvoření zařízení" + }, + "desktopRequired": { + "message": "Je vyžadována desktopová aplikace" + }, + "reopenLinkOnDesktop": { + "message": "Znovu otevřete tento odkaz z Vašeho e-mailu na počítači." + }, "integrationCardTooltip": { "message": "Spustí průvodce implementací $INTEGRATION$.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "měsíčně za člena" }, + "monthPerMemberBilledAnnually": { + "message": "měsíčně za člena a účtováno ročně" + }, "seats": { "message": "Počet" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Další informace o API Bitwardenu" }, + "fileSend": { + "message": "Send souboru" + }, "fileSends": { "message": "Sends se soubory" }, + "textSend": { + "message": "Send textu" + }, "textSends": { "message": "Sends s texty" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Algoritmus klíče" }, + "sshPrivateKey": { + "message": "Soukromý klíč" + }, + "sshPublicKey": { + "message": "Veřejný klíč" + }, + "sshFingerprint": { + "message": "Otisk prstu" + }, "sshKeyFingerprint": { "message": "Otisk prstu" }, @@ -9774,10 +10088,6 @@ "message": "Zahrnout speciální znaky", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Přidat přílohu" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Kód z popisu" }, + "cannotRemoveViewOnlyCollections": { + "message": "Nemůžete odebrat kolekce s oprávněními jen pro zobrazení: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Důležité upozornění" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Odebrat členy" }, + "devices": { + "message": "Zařízení" + }, + "deviceListDescription": { + "message": "Váš účet byl přihlášen do každého z níže uvedených zařízení. Pokud zařízení nepoznáváte, odeberte jej nyní." + }, + "deviceListDescriptionTemp": { + "message": "Váš účet byl přihlášen do každého z níže uvedených zařízení." + }, "claimedDomains": { "message": "Uplatněné domény" }, @@ -10020,7 +10348,69 @@ "organizationNameMaxLength": { "message": "Název organizace nesmí přesáhnout 50 znaků." }, - "resellerRenewalWarning": { + "sshKeyWrongPassword": { + "message": "Zadané heslo není správné." + }, + "importSshKey": { + "message": "Importovat" + }, + "confirmSshKeyPassword": { + "message": "Potvrdit heslo" + }, + "enterSshKeyPasswordDesc": { + "message": "Zadejte heslo pro SSH klíč." + }, + "enterSshKeyPassword": { + "message": "Zadejte heslo" + }, + "invalidSshKey": { + "message": "SSH klíč je neplatný" + }, + "sshKeyTypeUnsupported": { + "message": "Typ SSH klíče není podporován" + }, + "importSshKeyFromClipboard": { + "message": "Importovat klíč ze schránky" + }, + "sshKeyImported": { + "message": "SSH klíč byl úspěšně importován" + }, + "copySSHPrivateKey": { + "message": "Kopírovat soukromý klíč" + }, + "openingExtension": { + "message": "Otevírání rozšíření Bitwarden pro prohlížeč" + }, + "somethingWentWrong": { + "message": "Něco se pokazilo..." + }, + "openingExtensionError": { + "message": "Měli jsme potíže s otevřením rozšíření Bitwarden pro pohlížeč. Klepněte na tlačítko pro jeho otevření." + }, + "openExtension": { + "message": "Otevřít rozšíření" + }, + "doNotHaveExtension": { + "message": "Nemáte rozšíření Bitwarden pro prohlížeč?" + }, + "installExtension": { + "message": "Nainstalovat rozšíření" + }, + "openedExtension": { + "message": "Otevřeno rozšíření prohlížeče" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Rozšíření Bitwarden pro prohlížeč bylo úspěšně otevřeno. Nyní můžete zkontrolovat Vaše riziková hesla." + }, + "openExtensionManuallyPart1": { + "message": "Měli jsme potíže s otevřením rozšíření Bitwarden pro pohlížeč. Klepněte na ikonu Bitwarden", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "na panelu nástrojů.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { "message": "Vaše předplatné se brzy obnoví. Chcete-li zajistit nepřerušenou službu, kontaktujte $RESELLER$ pro potvrzení obnovení před $RENEWAL_DATE$.", "placeholders": { "reseller": { @@ -10033,7 +10423,7 @@ } } }, - "resellerOpenInvoiceWarning": { + "resellerOpenInvoiceWarningMgs": { "message": "Faktura pro Vaše předplatné byla vystavena dne $ISSUED_DATE$. Chcete-li zajistit nepřerušovanou službu, kontaktujte $RESELLER$ pro potvrzení obnovení před $DUE_DATE$.", "placeholders": { "reseller": { @@ -10050,7 +10440,7 @@ } } }, - "resellerPastDueWarning": { + "resellerPastDueWarningMsg": { "message": "Faktura za Vaše předplatné nebyla zaplacena. Chcete-li zajistit nepřerušovanou službu, kontaktujte $RESELLER$ pro potvrzení obnovení před $GRACE_PERIOD_END$.", "placeholders": { "reseller": { @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Předplatné organizace bylo restartováno" + }, + "restartSubscription": { + "message": "Restartovat Vaše předplatné" + }, + "suspendedManagedOrgMessage": { + "message": "Kontaktujte $PROVIDER$ pro pomoc.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Správci mají nyní možnost smazat účty členů, které patří k nárokované doméně." + }, + "deleteManagedUserWarningDesc": { + "message": "Tato akce smaže účet uživatele včetně všech jeho položek v trezoru. Tím se nahradí předchozí akce odebrání." + }, + "deleteManagedUserWarning": { + "message": "Smazat je nová akce!" + }, + "seatsRemaining": { + "message": "Máte zbývajících $REMAINING$ uživatelů z $TOTAL$ uživatelů přidělených této organizaci. Kontaktujte svého poskytovatele pro správu předplatného.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existující organizace" + }, + "selectOrganizationProviderPortal": { + "message": "Vyberte organizaci pro přidání do portálu poskytovatele." + }, + "noOrganizations": { + "message": "Žádné organizace k zobrazení" + }, + "yourProviderSubscriptionCredit": { + "message": "Předplatné poskytovatele obdrží kredit za zbývající čas v předplatném organizace." + }, + "doYouWantToAddThisOrg": { + "message": "Chcete přidat tuto organizaci do $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Přidána existující organizace" + }, + "assignedExceedsAvailable": { + "message": "Přiřazení uživatelé překračují dostupné uživatele." + }, + "changeAtRiskPassword": { + "message": "Změnit ohrožené heslo" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Odebrat odemknutí pomocí PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Nepovolí členům odemknout svůj účet pomocí PIN." + }, + "limitedEventLogs": { + "message": "Plány $PRODUCT_TYPE$ nemají přístup k protokolům reálných událostí", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Získejte plný přístup k protokolům událostí organizace aktualizací do týmů nebo plánu Enterprise." + }, + "upgradeEventLogTitle": { + "message": "Aktualizovat pro reálná data protokolu událostí" + }, + "upgradeEventLogMessage": { + "message": "Tyto události jsou jen příklady a neodrážejí skutečné události v rámci Vaší organizace Bitwarden." + }, + "cannotCreateCollection": { + "message": "Bezplatné organizace mohou mít až 2 kolekce. Chcete-li přidat více kolekcí, přejděte na placený tarif." } } diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index c0c39c91d30..2fd61e01526 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Critical applications" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "What type of item is this?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Nodiadau" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Note" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Drag to sort" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Text" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Edit folder" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "Base domain", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "ex.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filter" }, - "moveSelectedToOrg": { - "message": "Move selected to organization" - }, "deleteSelected": { "message": "Delete selected" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "Log in or create a new account to access your secure vault." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Submit" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Lost access to all of your two-step login providers? Use your recovery code to turn off all two-step login providers from your account." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Migrated from FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "Email" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." }, - "moveManyToOrgDesc": { - "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved." - }, "collectionsDesc": { "message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Verification code (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Regenerate password" - }, "length": { "message": "Length" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Danger zone" }, - "dangerZoneDesc": { - "message": "Careful, these actions are not reversible!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Deauthorize sessions" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Proceeding will also log you out of your current session, requiring you to log back in. You will also be prompted for two-step login again, if set up. Active sessions on other devices may continue to remain active for up to one hour." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "All sessions deauthorized" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Setting up two-step login can permanently lock you out of your Bitwarden account. A recovery code allows you to access your account in the event that you can no longer use your normal two-step login provider (example: you lose your device). Bitwarden support will not be able to assist you if you lose access to your account. We recommend you write down or print the recovery code and keep it in a safe place." }, + "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." + }, "viewRecoveryCode": { "message": "View recovery code" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Manage" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Turn off" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Revoke access" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "This two-step login provider is active on your account." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "There was a problem reading the security key. Try again." }, - "twoFactorWebAuthnWarning": { - "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used. Supported platforms:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Web vault and browser extensions on a desktop/laptop with a WebAuthn supported browser (Chrome, Opera, Vivaldi, or Firefox with FIDO U2F turned on)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Your Bitwarden two-step login recovery code" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "If you cannot access your account through your normal two-step login methods, you can use your two-step login recovery code to turn off all two-step providers on your account." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Recover account two-step login" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Set requirements for master password strength." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Require two-step login" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Set requirements for password generator." }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." - }, "masterPasswordPolicyInEffect": { "message": "One or more organization policies require your master password to meet the following requirements:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Organization owners and admins are exempt from this policy's enforcement." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "File" }, "sendTypeText": { "message": "Text" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Delete Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Are you sure you want to delete this Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "What type of Send is this?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maximum access count" }, - "maxAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Disabled" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Are you sure you want to remove the password?" }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, - "disableThisSend": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "All Sends" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Pending deletion" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Expired" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "The following organization policies are currently in effect:" - }, - "sendDisableHideEmailInEffect": { - "message": "Users are not allowed 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." - }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Remove individual ownership for organization users" }, - "textHiddenByDefault": { - "message": "When accessing the Send, hide the text by default", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to Send." - }, - "sendFileDesc": { - "message": "The file you want to Send." - }, - "copySendLinkOnSave": { - "message": "Copy the link to share this Send to my clipboard upon save." - }, - "sendLinkLabel": { - "message": "Send link", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "To verify your 2FA please click the button below." }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn is not supported in this browser." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, - "regenerateUsername": { - "message": "Regenerate username" - }, "generateUsername": { "message": "Generate username" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Username type" - }, "plusAddressedEmail": { "message": "Plus addressed email", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Hostname", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API access token" - }, "deviceVerification": { "message": "Device verification" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Number of users" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "pickAnAvatarColor": { "message": "Pick an avatar color" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "No collection" }, - "canView": { - "message": "Can view" - }, - "canViewExceptPass": { - "message": "Can view, except passwords" - }, - "canEdit": { - "message": "Can edit" - }, - "canEditExceptPass": { - "message": "Can edit, except passwords" - }, "noCollectionsAdded": { "message": "No collections added" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domain" - }, "alreadyHaveAccount": { "message": "Already have an account?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index f71e1feeb3b..f8a335011e5 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Kritiske apps" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Adgangsefterretning" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "Udsatte medlemmer" }, + "atRiskMembersWithCount": { + "message": "Udsatte medlemmer ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Udsatte applikationer ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "Disse medlemmer logger ind på programmer med svage, udsatte eller genbrugte adgangskoder." + }, + "atRiskApplicationsDescription": { + "message": "Disse applikationer har svage, udsatte eller genbrugte adgangskoder." + }, + "atRiskMembersDescriptionWithApp": { + "message": "Disse medlemmer logger ind på $APPNAME$ med svage, udsatte eller genbrugte adgangskoder.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Medlemmer i alt" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Applikationer i alt" }, + "unmarkAsCriticalApp": { + "message": "Afmarkér som kritisk app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Kritisk app afmarkeret" + }, "whatTypeOfItem": { "message": "Hvilken emnetype er denne?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Notater" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Notat" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Træk for at sortere" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Tekst" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Redigér mappe" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "Basisdomæne", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Emnenavn" }, - "cannotRemoveViewOnlyCollections": { - "message": "Samlinger med kun tilladelsen Vis kan ikke fjernes: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "eks.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filtre" }, - "moveSelectedToOrg": { - "message": "Flyt valgte til organisation" - }, "deleteSelected": { "message": "Slet valgte" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Valgte emner flyttet til $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Emner flyttet til $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "Nej" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "Log ind eller opret en ny konto for at tilgå din sikre boks." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log ind på Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Godkendelsestimeout" }, "authenticationSessionTimedOut": { "message": "Godkendelsessessionen fik timeout. Genstart loginprocessen." }, - "verifyIdentity": { - "message": "Bekræft din identitet" + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "Denne enhed er ikke genkendt. Angiv koden i den tilsendte e-mail for at bekræfte identiteten." + }, + "continueLoggingIn": { + "message": "Fortsæt med at logge ind" + }, + "whatIsADevice": { + "message": "Hvad er en enhed?" + }, + "aDeviceIs": { + "message": "En enhed er en unik installation af Bitwarden-appen, hvor man har logget ind. Geninstallation, rydning af app-data eller rydning af cookies kan medføre, at en enhed vises op flere gange." }, "logInInitiated": { "message": "Indlogning påbegyndt" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Indsend" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Angiv kontoens e-mailadresse og få et adgangskodetip fremsendt" }, - "passwordHint": { - "message": "Adgangskodetip" - }, - "enterEmailToGetHint": { - "message": "Angiv din kontos e-mailadresse for at modtage dit hovedadgangskodetip." - }, "getMasterPasswordHint": { "message": "Få hovedadgangskodetip" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "En notifikation er sendt til din enhed." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "En notifikation er sendt til enheden" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Sørg for, at boksen er oplåst, samt at fingeraftrykssætningen matcher på den anden enhed" - }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Husk mig" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send bekræftelseskode-email igen" }, "useAnotherTwoStepMethod": { "message": "Brug en anden totrins-login metode" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Indsæt din YubiKey i computerens USB-port og tryk på dens knap." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Totrins-login indstillinger" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Mistet adgang til alle totrinsudbyderene? Brug din genoprettelseskode til at deaktivere dem alle på kontoen." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Migreret fra FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "E-mail" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Vælg den organisation, til hvilken dette emne ønskes flyttet. Flytning overfører ejerskab af emnet til organisationen, og du vil efter flytningen ikke længere være den direkte ejer af emnet." }, - "moveManyToOrgDesc": { - "message": "Vælg organisationen, til hvilken disse emner ønskes flyttet. Flytning til en organisation overfører ejerskabet af emnerne til denne organisation, og du vil efter flytningen ikke længere være den direkte ejer af disse emner." - }, "collectionsDesc": { "message": "Redigér de samlinger, med hvilke dette emne deles. Kun organisationsbrugere med adgang til disse samlinger vil kunne se dette emne." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "$COUNT$ emne(r) valgt, hvoraf $MOVEABLE_COUNT$ kan flyttes til en organisation, mens $NONMOVEABLE_COUNT$ ikke kan.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Bekræftelseskode (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Undgå tvetydige tegn", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Regenerér adgangskode" - }, "length": { "message": "Længde" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Log ind igen." }, + "currentSession": { + "message": "Aktuel session" + }, + "requestPending": { + "message": "Anmodning afventer" + }, "logBackInOthersToo": { "message": "Log ind igen. Bruger du andre Bitwarden-applikationer, så log også ud og ind igen på disse." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Farezone" }, - "dangerZoneDesc": { - "message": "Pas på, disse handlinger er irreversible!" - }, - "dangerZoneDescSingular": { - "message": "Forsigtig, denne handling er irreversibel!" - }, "deauthorizeSessions": { "message": "Fjern sessionsgodkendelser" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Fortsættes, logges man også ud af denne session og vil skulle logge ind igen. Der anmodes også om totrins-login igen, såfremt funktionen er opsat. Aktive sessioner på andre enheder kan forblive aktive i op til én time." }, + "newDeviceLoginProtection": { + "message": "Nyt enheds-login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Slå nyt enheds-login beskyttelse fra" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Slå nyt enheds-login beskyttelse til" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Fortsæt nedenfor for at deaktivere Bitwarden-bekræftelsesmails ved indlogning fra en ny enhed." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Fortsæt nedenfor for at få tilsendt Bitwarden-bekræftelsesmails ved indlogning fra en ny enhed." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "Med den nye enheds-login beskyttelse slået fra kan alle med hovedadgangskoden tilgå brugerkontoen fra enhver enhed. For at beskytte brugerkontoen uden bekræftelsesmails, opsæt totrins-login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "Ændringer i ny enheds-login beskyttelse er gemt" + }, "sessionsDeauthorized": { "message": "Godkendelser for alle sessioner fjernet" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Opsætning af totrins-login kan permanent låse en bruger ude af sin Bitwarden-konto. En gendannelseskode muliggør kontoadgang, såfremt den normale totrins-loginudbyder ikke længere kan bruges (f.eks. ved tab af en enhed). Bitwarden-supporten kan ikke hjælpe ved mistet kontoadgang. Det anbefales at nedskrive/udskrive gendannelseskoden samt gemme denne et sikkert sted." }, + "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." + }, "viewRecoveryCode": { "message": "Vis gendannelseskode" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Håndtér" }, - "canManage": { - "message": "Kan håndtere" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Deaktivér" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Ophæv adgang" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "Denne totrins-loginudbyder er aktiveret på kontoen." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "Problem med at læse sikkerhedsnøglen. Forsøg igen." }, - "twoFactorWebAuthnWarning": { - "message": "Grundet platformsbegrænsninger kan WebAuthn ikke bruges i alle Bitwarden-applikationer. Man bør opsætte en anden totrins-loginudbyder, så man kan tilgå sin konto, når WebAuthn ikke kan benyttes. Understøttede platforme:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Web-boks og browserudvidelser på en stationær/bærbar computer med en WebAuthn-aktiveret browser (Chrome, Opera, Vivaldi eller Firefox med FIDO U2F aktiveret)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Gendannelseskoden til Bitwarden totrins-login" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Der er 1 invitation tilbage." + }, + "inviteZeroEmailDesc": { + "message": "Der er 0 invitationer tilbage." + }, "userUsingTwoStep": { "message": "Denne bruger benytter totrins-login for at beskytte kontoen." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Fjern SSO-tilknytning." + }, "unlinkedSsoUser": { "message": "Af-linket SSO for bruger $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Enhed" }, + "loginStatus": { + "message": "Indlogningsstatus" + }, + "firstLogin": { + "message": "Første login" + }, + "trusted": { + "message": "Betroet" + }, + "needsApproval": { + "message": "Kræver godkendelse" + }, + "areYouTryingtoLogin": { + "message": "Forsøger at logge ind?" + }, + "logInAttemptBy": { + "message": "Loginforsøg fra $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Enhedstype" + }, + "ipAddress": { + "message": "IP-adresse" + }, + "confirmLogIn": { + "message": "Bekræft login" + }, + "denyLogIn": { + "message": "Afvis login" + }, + "thisRequestIsNoLongerValid": { + "message": "Anmodningen er ikke længere gyldig." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login bekræftet for $EMAIL$ på $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "Du nægtede et loginforsøg fra en anden enhed. Hvis dette virkelig var dig, prøv at logge ind med enheden igen." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login-anmodning er allerede udløbet." + }, + "justNow": { + "message": "Netop nu" + }, + "requestedXMinutesAgo": { + "message": "Anmodet for $MINUTES$ minutter siden", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Opretter konto på" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "Du bruger en ikke-understøttet webbrowser. Web-boksen fungerer muligvis ikke korrekt." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Den gratis prøveperiode slutter om $COUNT$ dage.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "Kan man ikke tilgå sin konto via de normale totrins-login metoder, kan man bruge sin totrins-login gendannelseskode til at deaktivere alle totrinsudbydere på sin konto." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Gendan totrins-login på konto" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Krypteringsnøgleopdatering kan ikke fortsætte" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "Under opdatering af krypteringsnøglen kunne de relevante mapper ikke dekrypteres. For at fortsætte opdateringen skal mapperne slettes. Ingen boks-emner slettes, hvis der fortsættes." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Angiv krav til styrken af hovedadgangskode." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Kræv totrins-login" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Angiv krav til adgangskodegenerator." }, - "passwordGeneratorPolicyInEffect": { - "message": "Én eller flere organisationspolitikker påvirker dine generatorindstillinger." - }, "masterPasswordPolicyInEffect": { "message": "Én eller flere organisationspolitikker kræver din hovedadgangskode opfylder følgende krav:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Denne politik håndhæves ikke for organisationsejere og -admins." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "Fil" }, "sendTypeText": { "message": "Tekst" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Ny Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Slet Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Sikker på, at denne Send skal slettes?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "Hvilken type Send er denne?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Sletningsdato" }, - "deletionDateDesc": { - "message": "Denne Send slettes permanent på den angivne dato og tidspunkt.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maksimalt adgangsantal" }, - "maxAccessCountDesc": { - "message": "Hvis opsat, vil brugere ikke længere kunne tilgå denne Send, når det maksimale adgangsantal er nået.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Aktuelt adgangsantal" - }, - "sendPasswordDesc": { - "message": "Valgfrit brugeradgangskodekrav for tilgang til denne Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Fortrolige notater om denne Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Deaktiveret" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Sikker på, at adgangskoden skal fjernes?" }, - "hideEmail": { - "message": "Skjul min e-mailadresse for modtagere." - }, - "disableThisSend": { - "message": "Deaktivér denne Send så ingen kan tilgå den.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Alle Send" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Afventer sletning" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Udløbet" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "Følgende organisationspolitikker er i øjeblikket gældende:" - }, - "sendDisableHideEmailInEffect": { - "message": "Brugere har ikke lov til at skjule deres e-mailadresser for modtagere, når en Send oprettes eller redigeres.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "modifiedPolicyId": { "message": "Redigerede politik $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Fjern personligt ejerskab for organisationsbrugere" }, - "textHiddenByDefault": { - "message": "Når Send tilgås, skjul som standard teksten", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Et logisk navn til at beskrive denne Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Teksten, du vil sende." - }, - "sendFileDesc": { - "message": "Filen, du vil sende." - }, - "copySendLinkOnSave": { - "message": "Kopiér linket for at dele denne Send til udklipsholden ved gem." - }, - "sendLinkLabel": { - "message": "Send link", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "En fejl opstod under forsøget på at gemme sletnings- og udløbsdatoer." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "Klik på knappen nedenfor for at bekræfte din 2FA." }, "webAuthnAuthenticate": { "message": "Godkend WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "Denne browser understøtter ikke WebAuthn." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Fejl" }, + "decryptionError": { + "message": "Dekrypteringsfejl" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden kunne ikke dekryptere boks-emne(r) anført nedenfor." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontakt kundeservice", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "for at undgå yderligere tab af data.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Håndtér brugere skal også tildeles med tilladelsen håndtér kontogendannelse" }, @@ -6516,15 +6791,6 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "Hvad ønskes genereret?" - }, - "passwordType": { - "message": "Adgangskodetype" - }, - "regenerateUsername": { - "message": "Regenerér brugernavn" - }, "generateUsername": { "message": "Generér brugernavn" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Brugernavnstype" - }, "plusAddressedEmail": { "message": "Plus adresseret e-mail", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Brug den for domænet opsatte Fang-alle indbakke." }, + "useThisEmail": { + "message": "Benyt denne e-mail" + }, "random": { "message": "Tilfældig", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Kan ikke få $SERVICENAME$ maskeret e-mailkonto-ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Værtsnavn", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API-adgangstoken" - }, "deviceVerification": { "message": "Enhedsbekræftelse" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo totrinsindlogning kræves for kontoen." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Start Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Antal brugere" }, - "loggingInAs": { - "message": "Logger ind som" - }, - "notYou": { - "message": "Ikke dig?" - }, "pickAnAvatarColor": { "message": "Vælg avatarfarve" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "Ingen samling" }, - "canView": { - "message": "Kan se" - }, - "canViewExceptPass": { - "message": "Kan se, undtagen adgangskoder" - }, - "canEdit": { - "message": "Kan redigere" - }, - "canEditExceptPass": { - "message": "Kan redigere, undtagen adgangskoder" - }, "noCollectionsAdded": { "message": "Ingen samlinger tilføjet" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Anmod om admin-godkendelse" }, - "approveWithMasterPassword": { - "message": "Godkend med hovedadgangskode" - }, "trustedDeviceEncryption": { "message": "Betroet enhedskryptering" }, "trustedDevices": { "message": "Betroede enheder" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Når godkendt, dekrypterer medlemmet boksdata vha. en nøgle gemt på deres enhed. ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Medlemmer behøver ikke en hovedadgangskode ved indlogning med SSO. Hovedadgangskoden erstattes af en krypteringsnøgle gemt på enheden, hvilket gør enheden betroet. Den første enhed, hvorfra et medlem opretter sin konto og logger ind, vil være betroet. Nye enheder skal godkendes af en eksisterende betroet enhed eller af en administrator. Den", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "Den enkelte organisations", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "enkelte organisations", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "politik,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "SSO-påkrævede", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO-krævet", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "politik samt", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "politik og", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "kontogendannelseshåndteringspolitik", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "kontogendannelseshåndterings-", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "med automatisk indrullering slås til ved brug af denne indstilling.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "politik træder i kraft, når denne indstilling bruges.", + "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": "Organisationstilladelserne er blevet opdateret, og der kræves nu oprettelse af en hovedadgangskode.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Godkend anmodning" }, + "deviceApproved": { + "message": "Enhed godkendt" + }, + "deviceRemoved": { + "message": "Enhed fjernet" + }, + "removeDevice": { + "message": "Fjern enhed" + }, + "removeDeviceConfirmation": { + "message": "Sikker på, at denne enhed skal fjernes?" + }, "noDeviceRequests": { "message": "Ingen enhedsanmodninger" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Anmodningen er sendt til din gruppe-admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "Du underrettes, når godkendelse foreligger." - }, "troubleLoggingIn": { "message": "Problemer med at logge ind?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Begræns samlingsslettelse til ejere og admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Ejere og admins kan håndtere alle samlinger og emner" }, @@ -8494,9 +8778,6 @@ "message": "URL til selv-hostet server", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Aliasdomæne" - }, "alreadyHaveAccount": { "message": "Har allerede en konto?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "Du har ikke adgang til at håndtere denne samling." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Manglende Kan håndtere tilladelser" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Tildel Kan håndtere tilladelser for at tillade fuld samlingshåndtering, herunder sletning af samling." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Tildel grupper eller medlemmer adgang til denne samling." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Opsæt enhedshåndtering for Bitwarden vha. implementeringsvejledningen for den aktuelle platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Start $INTEGRATION$-implementeringsguiden.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "måned pr. medlem" }, + "monthPerMemberBilledAnnually": { + "message": "måned pr. medlem faktureret årligt" + }, "seats": { "message": "Pladser" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Læs mere om Bitwardens API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "Fil-Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Tekst-Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Nøglealgoritme" }, + "sshPrivateKey": { + "message": "Privat nøgle" + }, + "sshPublicKey": { + "message": "Offentlig nøgle" + }, + "sshFingerprint": { + "message": "Fingeraftryk" + }, "sshKeyFingerprint": { "message": "Fingeraftryk" }, @@ -9774,10 +10088,6 @@ "message": "Inkludér specialtegn", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Tilføj vedhæftning" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Beskrivelseskode" }, + "cannotRemoveViewOnlyCollections": { + "message": "Samlinger med kun tilladelsen Vis kan ikke fjernes: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Vigtig notits" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Fjern medlemmer" }, + "devices": { + "message": "Enheder" + }, + "deviceListDescription": { + "message": "Der er blevet logget ind på kontoen på hver af enhederne nedenfor. Genkendes en enhed ikke, fjern den nu." + }, + "deviceListDescriptionTemp": { + "message": "Der er blevet logget ind på kontoen på hver af enhederne nedenfor." + }, "claimedDomains": { "message": "Registrerede domæner" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organisationsnavn må ikke overstige 50 tegn." }, - "resellerRenewalWarning": { - "message": "Abonnementet fornyes snart. For at sikre uafbrudt tjeneste, kontakt $RESELLER$ for at bekræfte fornyelsen inden $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Abonnementet fornyes snart. For at sikre uafbrudt tjeneste, kontakt $RESELLER$ inden $RENEWAL_DATE$ for at bekræfte fornyelsen.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "En faktura for abonnementet er udstedt pr. $ISSUED_DATE$. For at sikre uafbrudt tjeneste, kontakt $RESELLER$ for at bekræfte fornyelsen inden $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "En abonnementsfaktura er udstedt pr. $ISSUED_DATE$. For at sikre uafbrudt tjeneste, kontakt $RESELLER$ inden $DUE_DATE$ for at bekræfte fornyelsen.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "Fakturaen for abonnementet er ikke blevet betalt. For at sikre uafbrudt tjeneste, kontakt $RESELLER$ for at bekræfte fornyelsen inden $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "Abonnementsfakturaen er ikke blevet betalt. For at sikre uafbrudt tjeneste, kontakt $RESELLER$ inden $GRACE_PERIOD_END$ for at bekræfte fornyelsen.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organisationsabonnement genstartet" + }, + "restartSubscription": { + "message": "Genstart abonnementet" + }, + "suspendedManagedOrgMessage": { + "message": "Kontakt $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index 93422471457..135ab8af923 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Kritische Anwendungen" }, + "noCriticalAppsAtRisk": { + "message": "Keine kritischen Anwendungen gefährdet" + }, "accessIntelligence": { "message": "Zugriff auf Informationen" }, @@ -99,7 +102,7 @@ "message": "Anwendung" }, "atRiskPasswords": { - "message": "Risikoreiche Passwörter" + "message": "Gefährdete Passwörter" }, "requestPasswordChange": { "message": "Passwortänderung anfordern" @@ -111,17 +114,56 @@ "message": "Anwendungen suchen" }, "atRiskMembers": { - "message": "Risikoreiche Mitglieder" + "message": "Gefährdete Mitglieder" + }, + "atRiskMembersWithCount": { + "message": "Gefährdete Mitglieder ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Gefährdete Anwendungen ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "Diese Mitglieder melden sich bei Anwendungen mit schwachen, kompromittierten oder wiederverwendeten Passwörtern an." + }, + "atRiskApplicationsDescription": { + "message": "Diese Anwendungen haben schwache, kompromittierte oder wiederverwendete Passwörter." + }, + "atRiskMembersDescriptionWithApp": { + "message": "Diese Mitglieder melden sich bei $APPNAME$ mit schwachen, kompromittierten oder wiederverwendeten Passwörtern an.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } }, "totalMembers": { "message": "Mitglieder insgesamt" }, "atRiskApplications": { - "message": "Risikoreiche Anwendungen" + "message": "Gefährdete Anwendungen" }, "totalApplications": { "message": "Anwendungen insgesamt" }, + "unmarkAsCriticalApp": { + "message": "Markierung als kritische Anwendung aufheben" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Markierung als kritische Anwendung erfolgreich aufgehoben" + }, "whatTypeOfItem": { "message": "Um welche Art von Eintrag handelt es sich hierbei?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Notizen" }, + "privateNote": { + "message": "Private Notiz" + }, "note": { "message": "Notiz" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Zum Sortieren ziehen" }, + "dragToReorder": { + "message": "Ziehen zum Umsortieren" + }, "cfTypeText": { "message": "Text" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Ordner bearbeiten" }, + "editWithName": { + "message": "$ITEM$: $NAME$ bearbeiten", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "Neuer Ordner" + }, + "folderName": { + "message": "Ordnername" + }, + "folderHintText": { + "message": "Verschachtel einen Ordner, indem du den Namen des übergeordneten Ordners hinzufügst, gefolgt von einem „/“. Beispiel: Sozial/Foren" + }, + "deleteFolderPermanently": { + "message": "Bist du sicher, dass du diesen Ordner dauerhaft löschen willst?" + }, "baseDomain": { "message": "Basisdomäne", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Eintrags-Name" }, - "cannotRemoveViewOnlyCollections": { - "message": "Du kannst Sammlungen mit Leseberechtigung nicht entfernen: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "Bsp.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filter" }, - "moveSelectedToOrg": { - "message": "Auswahl in Organisation verschieben" - }, "deleteSelected": { "message": "Auswahl löschen" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Ausgewählte Einträge in $ORGNAME$ verschoben", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Einträge verschoben nach $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "Nein" }, + "location": { + "message": "Standort" + }, "loginOrCreateNewAccount": { "message": "Sie müssen sich anmelden oder ein neues Konto erstellen, um auf den Tresor zugreifen zu können." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Bei Bitwarden anmelden" }, + "enterTheCodeSentToYourEmail": { + "message": "Gib den an deine E-Mail-Adresse gesendeten Code ein" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Gib den Code aus deiner Authenticator-App ein" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Drücke zum Authentifizieren auf deinen YubiKey" + }, "authenticationTimeout": { "message": "Authentifizierungs-Timeout" }, "authenticationSessionTimedOut": { "message": "Die Authentifizierungssitzung ist abgelaufen. Bitte starte den Anmeldeprozess neu." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verifiziere deine Identität" }, + "weDontRecognizeThisDevice": { + "message": "Wir erkennen dieses Gerät nicht. Gib den an deine E-Mail-Adresse gesendeten Code ein, um deine Identität zu verifizieren." + }, + "continueLoggingIn": { + "message": "Anmeldung fortsetzen" + }, + "whatIsADevice": { + "message": "Was ist ein Gerät?" + }, + "aDeviceIs": { + "message": "Ein Gerät ist eine einzigartige Installation der Bitwarden-App, in der du dich angemeldet hast. Eine Neuinstallation, das Löschen von App-Daten oder das Löschen von Cookies könnte dazu führen, dass ein Gerät mehrfach erscheint." + }, "logInInitiated": { "message": "Anmeldung eingeleitet" }, + "logInRequestSent": { + "message": "Anfrage gesendet" + }, "submit": { "message": "Absenden" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Gib deine E-Mail-Adresse ein und dein Passwort-Hinweis wird dir zugesandt" }, - "passwordHint": { - "message": "Passwort-Hinweis" - }, - "enterEmailToGetHint": { - "message": "Geben Sie die E-Mail Adresse Ihres Kontos ein, um einen Hinweis auf ihr Master-Passwort zu erhalten." - }, "getMasterPasswordHint": { "message": "Hinweis zum Master-Passwort erhalten" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "Eine Benachrichtigung wurde an dein Gerät gesendet." }, + "notificationSentDevicePart1": { + "message": "Entsperre Bitwarden auf deinem Gerät oder mit der " + }, + "areYouTryingToAccessYourAccount": { + "message": "Versuchst du auf dein Konto zuzugreifen?" + }, + "accessAttemptBy": { + "message": "Zugriffsversuch von $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Zugriff bestätigen" + }, + "denyAccess": { + "message": "Zugriff ablehnen" + }, + "notificationSentDeviceAnchor": { + "message": "Web-App" + }, + "notificationSentDevicePart2": { + "message": "Stelle vor der Genehmigung sicher, dass die Fingerabdruck-Phrase mit der unten stehenden übereinstimmt." + }, + "notificationSentDeviceComplete": { + "message": "Entsperre Bitwarden auf deinem Gerät. Stelle vor der Genehmigung sicher, dass die Fingerabdruck-Phrase mit der unten stehenden übereinstimmt." + }, "aNotificationWasSentToYourDevice": { "message": "Eine Benachrichtigung wurde an dein Gerät gesendet" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Stelle sicher, dass dein Konto entsperrt ist und die Fingerabdruck-Phrase mit der vom anderen Gerät übereinstimmt" - }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Angemeldet bleiben" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Für 30 Tage auf diesem Gerät nicht mehr fragen" + }, "sendVerificationCodeEmailAgain": { "message": "E-Mail mit Bestätigungscode erneut versenden" }, "useAnotherTwoStepMethod": { "message": "Verwenden sie eine andere Zwei-Faktor-Anmelde-Methode" }, + "selectAnotherMethod": { + "message": "Wähle eine andere Methode", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Verwende deinen Wiederherstellungscode" + }, "insertYubiKey": { "message": "Stecken Sie Ihren YubiKey in einen USB-Port Ihres Computers und berühren Sie dessen Knopf." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Optionen für Zwei-Faktor-Authentifizierung" }, + "selectTwoStepLoginMethod": { + "message": "Zwei-Faktor-Authentifizierungsmethode auswählen" + }, "recoveryCodeDesc": { "message": "Zugang zu allen Zwei-Faktor-Anbietern verloren? Benutze deinen Wiederherstellungscode, um alle Zwei-Faktor-Anbieter in deinem Konto zu deaktivieren." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Von FIDO migriert)" }, + "openInNewTab": { + "message": "In neuem Tab öffnen" + }, "emailTitle": { "message": "E-Mail" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Wähle eine Organisation aus, in die du diesen Eintrag verschieben möchtest. Das Verschieben in eine Organisation überträgt das Eigentum an diese Organisation. Du bist nicht mehr der direkte Eigentümer dieses Eintrags, sobald er verschoben wurde." }, - "moveManyToOrgDesc": { - "message": "Wähle eine Organisation aus, in die du diese Einträge verschieben möchtest. Das Verschieben in eine Organisation überträgt das Eigentum der Einträge an diese Organisation. Du bist nicht mehr der direkte Eigentümer dieser Einträge, sobald sie verschoben wurden." - }, "collectionsDesc": { "message": "Bearbeite die Sammlungen, mit denen dieser Eintrag geteilt wird. Nur Organisationsmitglieder mit Zugriff auf diese Sammlungen werden diesen Eintrag sehen können." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "Du hast $COUNT$ Eintrag/Einträge ausgewählt. $MOVEABLE_COUNT$ Eintrag/Einträge kann/können in eine Organisation verschoben werden, $NONMOVEABLE_COUNT$ nicht.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Verifizierungscode (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Mehrdeutige Zeichen vermeiden", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Passwort neu generieren" - }, "length": { "message": "Länge" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Bitte melden Sie sich erneut an." }, + "currentSession": { + "message": "Aktuelle Sitzung" + }, + "requestPending": { + "message": "Anfrage ausstehend" + }, "logBackInOthersToo": { "message": "Bitte melden Sie sich wieder an. Wenn Sie andere Bitwarden-Anwendungen verwenden, melden Sie sich auch dort ab und wieder neu an." }, @@ -1782,21 +1881,36 @@ "dangerZone": { "message": "Gefahrenzone" }, - "dangerZoneDesc": { - "message": "Vorsicht, diese Aktionen sind nicht umkehrbar!" - }, - "dangerZoneDescSingular": { - "message": "Vorsicht, diese Aktion ist nicht mehr rückgängig zu machen!" - }, "deauthorizeSessions": { "message": "Sitzungen abmelden" }, "deauthorizeSessionsDesc": { - "message": "Bist du besorgt, dass dein Konto auf einem anderen Gerät angemeldet ist? Fahre unten fort, um alle Computer oder Geräte, die du zuvor verwendet hast, zu deaktivieren. Dieser Sicherheitsschritt wird empfohlen, wenn du zuvor einen öffentlichen Computer verwendet oder dein Passwort versehentlich auf einem Gerät gespeichert hast, das dir nicht gehört. Dieser Schritt löscht auch alle zuvor gespeicherten zweistufigen Anmeldesitzungen." + "message": "Bist du besorgt, dass dein Konto auf einem anderen Gerät angemeldet ist? Fahre unten fort, um dich von allen Computern oder Geräten, die du zuvor verwendet hast, abzumelden. Dieser Sicherheitsschritt wird empfohlen, wenn du zuvor einen öffentlichen Computer verwendet oder dein Passwort versehentlich auf einem Gerät gespeichert hast, das dir nicht gehört. Dieser Schritt löscht auch alle zuvor gespeicherten zweistufigen Anmeldesitzungen." }, "deauthorizeSessionsWarning": { "message": "Wenn du fortfährst, wirst du auch von deiner aktuellen Sitzung abgemeldet, so dass du dich erneut anmelden musst. Du wirst auch aufgefordert, dich erneut in zwei Schritten anzumelden, falls dies eingerichtet ist. Aktive Sitzungen auf anderen Geräten können noch bis zu einer Stunde lang aktiv bleiben." }, + "newDeviceLoginProtection": { + "message": "Neue Geräteanmeldung" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Neuen Geräte-Anmeldeschutz deaktivieren" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Neuen Geräte-Anmeldeschutz aktivieren" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Fahre unten fort, um die Verifizierungs-E-Mails von Bitwarden zu deaktivieren, wenn du dich von einem neuen Gerät aus anmeldest." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Fahre unten fort, um Verifizierungs-E-Mails von Bitwarden zu erhalten, wenn du dich von einem neuen Gerät aus anmeldest." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "Wenn der neue Geräte-Anmeldeschutz ausgeschaltet ist, kann jeder mit deinem Master-Passwort von jedem Gerät aus auf dein Konto zugreifen. Um dein Konto ohne Verifizierungs-E-Mails zu schützen, richte die Zwei-Faktor-Authentifizierung ein." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "Änderungen am neuen Geräte-Anmeldeschutz gespeichert" + }, "sessionsDeauthorized": { "message": "Alle Sitzungen wurden abgemeldet" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Durch die Aktivierung der Zwei-Faktor-Authentifizierung kannst du dich dauerhaft aus deinem Bitwarden-Konto aussperren. Ein Wiederherstellungscode ermöglicht es dir, auf dein Konto zuzugreifen, falls du deinen normalen Zwei-Faktor-Anbieter nicht mehr verwenden kannst (z.B. wenn du dein Gerät verlierst). Der Bitwarden-Support kann dir nicht helfen, wenn du den Zugang zu deinem Konto verlierst. Wir empfehlen dir, den Wiederherstellungscode aufzuschreiben oder auszudrucken und an einem sicheren Ort aufzubewahren." }, + "yourSingleUseRecoveryCode": { + "message": "Dein einmal benutzbarer Wiederherstellungscode kann benutzt werden, um die Zwei-Faktor-Authentifizierung auszuschalten, wenn du Zugang zu deinen Zwei-Faktor-Anbietern verlierst. Bitwarden empfiehlt dir, den Wiederherstellungscode aufzuschreiben und an einem sicheren Ort aufzubewahren." + }, "viewRecoveryCode": { "message": "Wiederherstellungscode anzeigen" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Verwalten" }, - "canManage": { - "message": "Darf verwalten" + "manageCollection": { + "message": "Sammlung verwalten" + }, + "viewItems": { + "message": "Einträge anzeigen" + }, + "viewItemsHidePass": { + "message": "Einträge anzeigen, versteckte Passwörter" + }, + "editItems": { + "message": "Einträge bearbeiten" + }, + "editItemsHidePass": { + "message": "Einträge bearbeiten, versteckte Passwörter" }, "disable": { "message": "Deaktivieren" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Zugriff widerrufen" }, + "revoke": { + "message": "Widerrufen" + }, "twoStepLoginProviderEnabled": { "message": "Dieser Zwei-Faktor-Authentifizierungsanbieter ist für dein Konto aktiviert." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "Es gab ein Problem beim Lesen des Sicherheitsschlüssels, bitte versuche es erneut." }, - "twoFactorWebAuthnWarning": { - "message": "Aufgrund von Plattformbeschränkungen kann WebAuthn nicht in allen Bitwarden-Anwendungen verwendet werden. Du solltest einen anderen Zwei-Faktor-Authentifizierungsanbieter aktivieren, damit du auf dein Konto zugreifen kannst, wenn WebAuthn nicht verwendet werden kann. Unterstützte Plattformen:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Web-Tresor und Browser-Erweiterungen auf einem Desktop/Laptop mit einem WebAuthn-fähigen Browser (Chrome, Opera, Vivaldi oder Firefox mit aktiviertem FIDO U2F)." + "twoFactorWebAuthnWarning1": { + "message": "Aufgrund von Plattformbeschränkungen kann WebAuthn nicht in allen Bitwarden-Anwendungen verwendet werden. Du solltest einen anderen Zwei-Faktor-Authentifizierungsanbieter einrichten, damit du auf dein Konto zugreifen kannst, wenn WebAuthn nicht verwendet werden kann." }, "twoFactorRecoveryYourCode": { "message": "Ihr Wiederherstellungsschlüssel für die Zwei-Faktor-Anmeldung in Bitwarden" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Du hast noch eine Einladung übrig." + }, + "inviteZeroEmailDesc": { + "message": "Du hast 0 Einladungen übrig." + }, "userUsingTwoStep": { "message": "Dieser Benutzer hat sein Konto mit einer Zwei-Faktor-Authentifizierung geschützt." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "SSO-Verknüpfung aufgehoben." + }, "unlinkedSsoUser": { "message": "SSO-Verknüpfung für Benutzer $ID$ aufgehoben.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Gerät" }, + "loginStatus": { + "message": "Anmeldestatus" + }, + "firstLogin": { + "message": "Erste Anmeldung" + }, + "trusted": { + "message": "Vertrauenswürdig" + }, + "needsApproval": { + "message": "Benötigt Genehmigung" + }, + "areYouTryingtoLogin": { + "message": "Versuchst du dich anzumelden?" + }, + "logInAttemptBy": { + "message": "Anmeldeversuch von $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Gerätetyp" + }, + "ipAddress": { + "message": "IP-Adresse" + }, + "confirmLogIn": { + "message": "Anmeldung genehmigen" + }, + "denyLogIn": { + "message": "Anmeldung ablehnen" + }, + "thisRequestIsNoLongerValid": { + "message": "Diese Anfrage ist nicht mehr gültig." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Anmeldung von $EMAIL$ auf $DEVICE$ bestätigt", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "Du hast einen Anmeldeversuch von einem anderen Gerät abgelehnt. Wenn du das wirklich warst, versuche dich erneut mit dem Gerät anzumelden." + }, + "loginRequestHasAlreadyExpired": { + "message": "Anmeldeanfrage ist bereits abgelaufen." + }, + "justNow": { + "message": "Gerade eben" + }, + "requestedXMinutesAgo": { + "message": "Vor $MINUTES$ Minuten angefordert", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Konto wird erstellt bei" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "Du verwendest einen nicht unterstützten Webbrowser. Der Web-Tresor funktioniert möglicherweise nicht richtig." }, + "youHaveAPendingLoginRequest": { + "message": "Du hast eine ausstehende Anmeldeanfrage von einem anderen Gerät." + }, + "reviewLoginRequest": { + "message": "Anmeldeanfrage überprüfen" + }, "freeTrialEndPromptCount": { "message": "Deine kostenlose Testversion endet in $COUNT$ Tagen.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "Falls du nicht mit deinen normalen Zwei-Faktor-Anmeldemethoden auf dein Konto zugreifen kannst, nutze deinen Zwei-Faktor-Wiederherstellungscode, um alle Zwei-Faktor-Anbieter für dein Konto zu deaktivieren." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Melde dich unten mit deinem einmal benutzbaren Wiederherstellungscode an. Dadurch werden alle Zwei-Faktor-Anbieter deines Kontos deaktiviert." + }, "recoverAccountTwoStep": { "message": "Zwei-Faktor-Authentifizierung wiederherstellen" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Aktualisierung des Verschlüsselungsschlüssels kann nicht fortgesetzt werden" }, + "editFieldLabel": { + "message": "$LABEL$ bearbeiten", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "$LABEL$ umsortieren. Verwende die Pfeiltasten, um den Eintrag nach oben oder unten zu verschieben.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ nach oben verschoben, Position $INDEX$ von $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ nach unten verschoben, Position $INDEX$ von $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "Beim Aktualisieren deines Verschlüsselungsschlüssels konnten deine Ordner nicht entschlüsselt werden. Um mit der Aktualisierung fortzufahren, müssen deine Ordner gelöscht werden. Es werden keine Tresor-Einträge gelöscht, wenn du fortfährst." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Mindestanforderungen für die Stärke des Master-Passworts festlegen." }, + "passwordStrengthScore": { + "message": "Bewertung der Passwortstärke $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Zwei-Faktor-Authentifizierung verlangen" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Mindestanforderungen für den Passwort-Generator festlegen." }, - "passwordGeneratorPolicyInEffect": { - "message": "Eine oder mehrere Organisationsrichtlinien beeinflussen deine Generator-Einstellungen." - }, "masterPasswordPolicyInEffect": { "message": "Eine oder mehrere Organisationsrichtlinien erfordern, dass dein Master-Passwort die folgenden Anforderungen erfüllt:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Organisationseigentümer und Administratoren sind von der Durchsetzung dieser Richtlinie ausgenommen." }, + "limitSendViews": { + "message": "Ansichten begrenzen" + }, + "limitSendViewsHint": { + "message": "Nach Erreichen des Limits kann niemand mehr dieses Send sehen.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ Ansichten übrig", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send-Details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Zu teilender Text" + }, "sendTypeFile": { "message": "Datei" }, "sendTypeText": { "message": "Text" }, + "sendPasswordDescV3": { + "message": "Füge ein optionales Passwort hinzu, mit dem Empfänger auf dieses Send zugreifen können.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Neues Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Send löschen", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Bist du sicher, dass du dieses Send löschen möchtest?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "Welche Art von Send ist das?", + "deleteSendPermanentConfirmation": { + "message": "Bist du sicher, dass du dieses Send dauerhaft löschen möchtest?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Löschdatum" }, - "deletionDateDesc": { - "message": "Das Send wird am angegebenen Datum zur angegebenen Uhrzeit dauerhaft gelöscht.", + "deletionDateDescV2": { + "message": "Das Send wird an diesem Datum dauerhaft gelöscht.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maximale Zugriffsanzahl" }, - "maxAccessCountDesc": { - "message": "Falls aktiviert, können Benutzer nicht mehr auf dieses Send zugreifen, sobald die maximale Zugriffsanzahl erreicht ist.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Aktuelle Zugriffsanzahl" - }, - "sendPasswordDesc": { - "message": "Optional ein Passwort verlangen, damit Benutzer auf dieses Send zugreifen können.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Private Notizen zu diesem Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Deaktiviert" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Bist du sicher, dass du das Passwort entfernen möchtest?" }, - "hideEmail": { - "message": "Meine E-Mail-Adresse vor den Empfängern ausblenden." - }, - "disableThisSend": { - "message": "Dieses Send deaktivieren, damit niemand darauf zugreifen kann.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Alle Sends" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Ausstehende Löschung" }, + "hideTextByDefault": { + "message": "Text standardmäßig ausblenden" + }, "expired": { "message": "Abgelaufen" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "Die folgenden Organisationsrichtlinien sind derzeit gültig:" - }, - "sendDisableHideEmailInEffect": { - "message": "Benutzer dürfen ihre E-Mail-Adresse beim Erstellen oder Bearbeiten eines Sends nicht vor den Empfängern verstecken.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "modifiedPolicyId": { "message": "Richtlinie $ID$ geändert.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Persönliches Eigentum für Organisationsbenutzer deaktivieren" }, - "textHiddenByDefault": { - "message": "Beim Zugriff auf dieses Send den Text standardmäßig ausblenden", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Ein eigener Name, um dieses Send zu beschreiben.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Der Text, den du versenden möchtest." - }, - "sendFileDesc": { - "message": "Die Datei, die du versenden möchtest." - }, - "copySendLinkOnSave": { - "message": "Den Link zum Teilen dieses Sends beim Speichern in meine Zwischenablage kopieren." - }, - "sendLinkLabel": { - "message": "Send-Link", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "Es gab einen Fehler beim Speichern deiner Lösch- und Verfallsdaten." }, + "hideYourEmail": { + "message": "Verberge deine E-Mail-Adresse vor Betrachtern." + }, "webAuthnFallbackMsg": { "message": "Um deine 2FA zu verifizieren, klicke bitte unten auf den Button." }, "webAuthnAuthenticate": { "message": "WebAuthn authentifizieren" }, + "readSecurityKey": { + "message": "Sicherheitsschlüssel auslesen" + }, + "awaitingSecurityKeyInteraction": { + "message": "Warte auf Sicherheitsschlüssel-Interaktion..." + }, "webAuthnNotSupported": { "message": "WebAuthn wird in diesem Browser nicht unterstützt." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Fehler" }, + "decryptionError": { + "message": "Entschlüsselungsfehler" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden konnte folgende(n) Tresor-Eintrag/Einträge nicht entschlüsseln." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktiere unser Customer Success Team", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": ", um zusätzlichen Datenverlust zu vermeiden.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "\"Benutzer verwalten\" muss zusammen mit der \"Kontowiederherstellung verwalten\"-Berechtigung erteilt werden" }, @@ -6516,15 +6791,6 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "Was möchtest du generieren?" - }, - "passwordType": { - "message": "Passworttyp" - }, - "regenerateUsername": { - "message": "Benutzername neu generieren" - }, "generateUsername": { "message": "Benutzername generieren" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Benutzernamenstyp" - }, "plusAddressedEmail": { "message": "Plus-adressierte E-Mail-Adresse", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Verwende den konfigurierten Catch-All-Posteingang deiner Domain." }, + "useThisEmail": { + "message": "Diese E-Mail-Adresse verwenden" + }, "random": { "message": "Zufällig", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ hat deine Anfrage abgelehnt. Bitte wende dich an deinen Dienstanbieter für Unterstützung.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ hat deine Anfrage abgelehnt: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Maskierte E-Mail-Konto-ID für $SERVICENAME$ konnte nicht abgerufen werden.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Hostname", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API-Zugriffstoken" - }, "deviceVerification": { "message": "Geräteverifizierung" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Für dein Konto ist die DUO Zwei-Faktor-Authentifizierung erforderlich." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Die Duo-Zwei-Faktor-Authentifizierung ist für dein Konto erforderlich. Folge den Schritten unten, um die Anmeldung abzuschließen." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Folge den Schritten unten, um die Anmeldung abzuschließen." + }, "launchDuo": { "message": "DUO starten" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Anzahl der Benutzer" }, - "loggingInAs": { - "message": "Anmelden als" - }, - "notYou": { - "message": "Nicht du?" - }, "pickAnAvatarColor": { "message": "Wähle eine Profilbild-Farbe" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "Keine Sammlung" }, - "canView": { - "message": "Darf anzeigen" - }, - "canViewExceptPass": { - "message": "Darf anzeigen, Passwörter ausgenommen" - }, - "canEdit": { - "message": "Darf bearbeiten" - }, - "canEditExceptPass": { - "message": "Darf bearbeiten, Passwörter ausgenommen" - }, "noCollectionsAdded": { "message": "Keine Sammlungen hinzugefügt" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Admin-Genehmigung anfragen" }, - "approveWithMasterPassword": { - "message": "Mit Master-Passwort genehmigen" - }, "trustedDeviceEncryption": { "message": "Vertrauenswürdige Geräteverschlüsselung" }, "trustedDevices": { "message": "Vertrauenswürdige Geräte" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Einmal authentifiziert, entschlüsseln Mitglieder Tresordaten mit einem Schlüssel auf ihrem Gerät. Die Richtlinie für", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Mitglieder benötigen kein Master-Passwort beim Anmelden mit SSO. Das Master-Passwort wird durch einen auf dem Gerät gespeicherten Verschlüsselungsschlüssel ersetzt, wodurch das Gerät vertrauenswürdig ist. Dem erste Gerät, auf das ein Mitglied sein Konto erstellt und sich anmeldet, wird vertraut. Neue Geräte müssen von einem bestehenden vertrauenswürdigen Gerät oder einem Administrator genehmigt werden. Die Richtlinie für eine", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "einzelne Organisationen", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "einzelne Organisation", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": ", die Richtlinie zum", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "Verlangen einer SSO-Authentifizierung", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "und die Richtlinie für die", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "Kontowiederherstellungsverwaltung", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "mit automatischer Registrierung werden aktiviert, wenn diese Option verwendet wird.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "werden aktiviert, wenn diese Option verwendet wird.", + "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": "Deine Organisationsberechtigungen wurden aktualisiert und verlangen, dass du ein Master-Passwort festlegen musst.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Anfrage genehmigen" }, + "deviceApproved": { + "message": "Gerät genehmigt" + }, + "deviceRemoved": { + "message": "Gerät entfernt" + }, + "removeDevice": { + "message": "Gerät entfernen" + }, + "removeDeviceConfirmation": { + "message": "Bist du sicher, das du dieses Gerät entfernen möchtest?" + }, "noDeviceRequests": { "message": "Keine Geräteanfragen" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Deine Anfrage wurde an deinen Administrator gesendet." }, - "youWillBeNotifiedOnceApproved": { - "message": "Nach einer Genehmigung wirst du benachrichtigt." - }, "troubleLoggingIn": { "message": "Probleme beim Anmelden?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Das Löschen von Sammlungen auf Eigentümer und Administratoren beschränken" }, + "limitItemDeletionDesc": { + "message": "Löschung von Einträgen auf Mitglieder mit der \"Darf verwalten\"-Berechtigung beschränken" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Besitzer und Administratoren können alle Sammlungen und Einträge verwalten" }, @@ -8494,9 +8778,6 @@ "message": "Selbst gehostete Server-URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias-Domain" - }, "alreadyHaveAccount": { "message": "Hast du bereits ein Konto?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "Du hast keinen Zugriff zur Verwaltung dieser Sammlung." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Fehlende \"Darf verwalten\"-Berechtigungen" + "grantManageCollectionWarningTitle": { + "message": "Berechtigungen zur Verwaltung von Sammlungen fehlen" }, - "grantAddAccessCollectionWarning": { - "message": "\"Darf verwalten\"-Berechtigungen gewähren, um die vollständige Sammlungsverwaltung einschließlich der Löschung von Sammlungen zu ermöglichen." + "grantManageCollectionWarning": { + "message": "Berechtigungen zur Verwaltung von Sammlungen gewähren, um die vollständige Verwaltung von Sammlungen, einschließlich der Löschung von Sammlungen, zu erlauben." }, "grantCollectionAccess": { "message": "Gewähre Gruppen oder Mitgliedern Zugriff auf diese Sammlung." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Konfiguriere die Geräteverwaltung für Bitwarden mithilfe der Implementierungsanleitung für deine Plattform." }, + "deviceIdMissing": { + "message": "Geräte-ID fehlt" + }, + "deviceTypeMissing": { + "message": "Gerätetyp fehlt" + }, + "deviceCreationDateMissing": { + "message": "Geräteerstellungsdatum fehlt" + }, + "desktopRequired": { + "message": "Desktop-Rechner erforderlich" + }, + "reopenLinkOnDesktop": { + "message": "Öffne diesen Link aus deiner E-Mail auf einem Desktop-Rechner." + }, "integrationCardTooltip": { "message": "$INTEGRATION$-Implementierungsanleitung starten.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "Monat pro Mitglied" }, + "monthPerMemberBilledAnnually": { + "message": "Monat pro Mitglied jährlich in Rechnung gestellt" + }, "seats": { "message": "Benutzerplätze" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Erfahre mehr über die API von Bitwarden" }, + "fileSend": { + "message": "Datei-Send" + }, "fileSends": { "message": "Datei-Sends" }, + "textSend": { + "message": "Text-Send" + }, "textSends": { "message": "Text-Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Schlüsselalgorithmus" }, + "sshPrivateKey": { + "message": "Privater Schlüssel" + }, + "sshPublicKey": { + "message": "Öffentlicher Schlüssel" + }, + "sshFingerprint": { + "message": "Fingerabdruck" + }, "sshKeyFingerprint": { "message": "Fingerabdruck" }, @@ -9774,10 +10088,6 @@ "message": "Sonderzeichen einschließen", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Anhang hinzufügen" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Beschreibungscode" }, + "cannotRemoveViewOnlyCollections": { + "message": "Du kannst Sammlungen mit Leseberechtigung nicht entfernen: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Wichtiger Hinweis" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Mitglieder entfernen" }, + "devices": { + "message": "Geräte" + }, + "deviceListDescription": { + "message": "Dein Konto ist bei jedem der folgenden Geräte angemeldet. Wenn du ein Gerät nicht wiedererkennst, entferne es jetzt." + }, + "deviceListDescriptionTemp": { + "message": "Dein Konto wurde bei jedem der unten aufgeführten Geräte angemeldet." + }, "claimedDomains": { "message": "Beanspruchte Domains" }, @@ -10020,7 +10348,69 @@ "organizationNameMaxLength": { "message": "Der Name der Organisation darf 50 Zeichen nicht überschreiten." }, - "resellerRenewalWarning": { + "sshKeyWrongPassword": { + "message": "Dein eingegebenes Passwort ist falsch." + }, + "importSshKey": { + "message": "Importieren" + }, + "confirmSshKeyPassword": { + "message": "Passwort bestätigen" + }, + "enterSshKeyPasswordDesc": { + "message": "Gib das Passwort für den SSH-Schlüssel ein." + }, + "enterSshKeyPassword": { + "message": "Passwort eingeben" + }, + "invalidSshKey": { + "message": "Der SSH-Schlüssel ist ungültig" + }, + "sshKeyTypeUnsupported": { + "message": "Der SSH-Schlüsseltyp wird nicht unterstützt" + }, + "importSshKeyFromClipboard": { + "message": "Schlüssel aus Zwischenablage importieren" + }, + "sshKeyImported": { + "message": "SSH-Schlüssel erfolgreich importiert" + }, + "copySSHPrivateKey": { + "message": "Privaten Schlüssel kopieren" + }, + "openingExtension": { + "message": "Bitwarden-Browser-Erweiterung wird geöffnet" + }, + "somethingWentWrong": { + "message": "Etwas ist schiefgelaufen..." + }, + "openingExtensionError": { + "message": "Wir hatten Probleme beim Öffnen der Bitwarden-Browser-Erweiterung. Klick auf den Button, um sie jetzt zu öffnen." + }, + "openExtension": { + "message": "Erweiterung öffnen" + }, + "doNotHaveExtension": { + "message": "Du hast die Bitwarden-Browser-Erweiterung nicht?" + }, + "installExtension": { + "message": "Erweiterung installieren" + }, + "openedExtension": { + "message": "Browser-Erweiterung geöffnet" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Die Bitwarden Browser-Erweiterung wurde erfolgreich geöffnet. Du kannst jetzt deine gefährdeten Passwörter überprüfen." + }, + "openExtensionManuallyPart1": { + "message": "Wir hatten Probleme beim Öffnen der Bitwarden-Browser-Erweiterung. Öffne das Bitwarden-Symbol", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "in der Symbolleiste.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { "message": "Dein Abonnement wird bald verlängert. Um einen ununterbrochenen Betrieb zu gewährleisten, kontaktiere $RESELLER$ um deine Verlängerung vor dem $RENEWAL_DATE$ zu bestätigen.", "placeholders": { "reseller": { @@ -10033,7 +10423,7 @@ } } }, - "resellerOpenInvoiceWarning": { + "resellerOpenInvoiceWarningMgs": { "message": "Eine Rechnung für dein Abonnement wurde am $ISSUED_DATE$ ausgestellt. Um einen ununterbrochenen Betrieb zu gewährleisten, kontaktiere $RESELLER$, um deine Verlängerung vor dem $DUE_DATE$ zu bestätigen.", "placeholders": { "reseller": { @@ -10050,7 +10440,7 @@ } } }, - "resellerPastDueWarning": { + "resellerPastDueWarningMsg": { "message": "Die Rechnung für dein Abonnement wurde nicht bezahlt. Um einen ununterbrochenen Betrieb zu gewährleisten, kontaktiere $RESELLER$, um deine Verlängerung vor dem $GRACE_PERIOD_END$ zu bestätigen.", "placeholders": { "reseller": { @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organisations-Abonnement neu gestartet" + }, + "restartSubscription": { + "message": "Abonnement neu starten" + }, + "suspendedManagedOrgMessage": { + "message": "Kontaktiere $PROVIDER$ für Hilfe.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administratoren haben nun die Möglichkeit, Mitgliedskonten, die zu einer beanspruchten Domain gehören, zu löschen." + }, + "deleteManagedUserWarningDesc": { + "message": "Diese Aktion löscht das Mitgliedskonto, einschließlich aller Einträge in seinem Tresor. Dies ersetzt die vorherige Entfernen-Aktion." + }, + "deleteManagedUserWarning": { + "message": "Löschen ist eine neue Aktion!" + }, + "seatsRemaining": { + "message": "Du hast $REMAINING$ Plätze von $TOTAL$ dieser Organisation zugewiesenen Plätzen verfügbar. Kontaktiere deinen Anbieter, um dein Abonnement zu verwalten.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Vorhandene Organisation" + }, + "selectOrganizationProviderPortal": { + "message": "Wähle eine Organisation aus, um sie deinem Anbieterportal hinzuzufügen." + }, + "noOrganizations": { + "message": "Keine Sammlungen vorhanden" + }, + "yourProviderSubscriptionCredit": { + "message": "Dein Anbieterabonnement erhält ein Guthaben für jede verbleibende Zeit im Abonnement der Organisation." + }, + "doYouWantToAddThisOrg": { + "message": "Möchtest du diese Organisation zu $PROVIDER$ hinzufügen?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Vorhandene Organisation hinzugefügt" + }, + "assignedExceedsAvailable": { + "message": "Die zugewiesenen Plätze überschreiten die verfügbaren Plätze." + }, + "changeAtRiskPassword": { + "message": "Gefährdetes Passwort ändern" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Entsperren mit PIN entfernen" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Mitgliedern nicht erlauben, ihr Konto mit einer PIN zu entsperren." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$-Abos haben keinen Zugriff auf echte Ereignisprotokolle", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Erhalte vollen Zugriff auf Ereignisprotokolle von Organisationen durch ein Upgrade auf ein Teams- oder Enterprise-Abo." + }, + "upgradeEventLogTitle": { + "message": "Upgrade für echte Ereignisprotokolldaten" + }, + "upgradeEventLogMessage": { + "message": "Diese Ereignisse sind nur Beispiele und spiegeln keine realen Ereignisse in deiner Bitwarden-Organisation wider." + }, + "cannotCreateCollection": { + "message": "Kostenlose Organisationen können bis zu 2 Sammlungen haben. Upgrade auf ein kostenpflichtiges Abo, um mehr Sammlungen hinzuzufügen." } } diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index 42d24fae72d..e20b5cc52a0 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -3,22 +3,25 @@ "message": "Όλες οι εφαρμογές" }, "criticalApplications": { - "message": "Critical applications" + "message": "Κρίσιμες εφαρμογές" + }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" }, "accessIntelligence": { - "message": "Access Intelligence" + "message": "Πληροφορίες Πρόσβασης" }, "riskInsights": { - "message": "Risk Insights" + "message": "Insights Κινδύνου" }, "passwordRisk": { - "message": "Password Risk" + "message": "Ρίσκος Κωδικού Πρόσβασης" }, "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." + "message": "Ελέξτε τους κωδικούς πρόσβασης (αδύναμους, εκτεθειμένους ή επαναχρησιμοποιούμενους) σε όλες τις εφαρμογές. Επιλέξτε τις πιο κρίσιμες εφαρμογές σας για να δώσετε προτεραιότητα στις ενέργειες ασφαλείας για τους χρήστες σας ώστε να αντιμετωπίσουν τους εκτεθειμένους κωδικούς πρόσβασης." }, "dataLastUpdated": { - "message": "Data last updated: $DATE$", + "message": "Τελευταία ενημέρωση δεδομένων: $DATE$", "placeholders": { "date": { "content": "$1", @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Σύνολο μελών" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Σύνολο εφαρμογών" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "Τι είδους στοιχείο είναι αυτό;" }, @@ -159,6 +201,9 @@ "notes": { "message": "Σημειώσεις" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Σημείωση" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Σύρετε για ταξινόμηση" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Κείμενο" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Επεξεργασία Φακέλου" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Είστε σίγουροι ότι θέλετε να διαγράψετε μόνιμα αυτόν το φάκελο;" + }, "baseDomain": { "message": "Βασικός τομέας", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Όνομα αντικειμένου" }, - "cannotRemoveViewOnlyCollections": { - "message": "Δεν μπορείτε να αφαιρέσετε συλλογές που έχουν μόνο δικαιώματα Προβολής: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "πχ.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Φίλτρο" }, - "moveSelectedToOrg": { - "message": "Μετακίνηση Επιλεγμένων στον Οργανισμό" - }, "deleteSelected": { "message": "Διαγραφή επιλεγμένων" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Τα επιλεγμένα αντικείμενα μετακινήθηκαν στο $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Τα αντικείμενα μεταφέρθηκαν στο $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "Όχι" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "Συνδεθείτε ή δημιουργήστε νέο λογαριασμό για να αποκτήσετε πρόσβαση στο vault σας." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { - "message": "Επαληθεύστε την ταυτότητά σας" + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." }, "logInInitiated": { "message": "Η σύνδεση ξεκίνησε" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Υποβολή" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Εισαγάγετε τη διεύθυνση email του λογαριασμού σας και θα σας αποσταλεί η υπόδειξη για τον κωδικό πρόσβασής σας" }, - "passwordHint": { - "message": "Υπόδειξη Κωδικού" - }, - "enterEmailToGetHint": { - "message": "Παρακαλούμε εισάγετε το email του λογαριασμού σας, ώστε να λάβετε την υπόδειξη του κύριου κωδικού πρόσβασης." - }, "getMasterPasswordHint": { "message": "Λήψη υπόδειξης κύριου κωδικού" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "Μια ειδοποίηση έχει σταλεί στη συσκευή σας." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "Έκδοση $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Να με θυμάσαι" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Αποστολή email κωδικού επαλήθευσης ξανά" }, "useAnotherTwoStepMethod": { "message": "Χρήση άλλης μεθόδου δύο παραγόντων" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Τοποθετήστε το YubiKey στη θύρα USB του υπολογιστή σας και έπειτα πατήστε το κουμπί του." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Επιλογές σύνδεσης δύο παραγόντων" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Έχετε χάσει την πρόσβαση σε όλους τους παρόχους δύο παραγόντων; Χρησιμοποιήστε τον κωδικό ανάκτησης για να απενεργοποιήσετε όλους τους παρόχους δύο παραγόντων από το λογαριασμό σας." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Μετεγκατάσταση από το FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "Email" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Επιλέξτε έναν οργανισμό στον οποίο θέλετε να μετακινήσετε αυτό το στοιχείο. Η μετακίνηση σε έναν οργανισμό μεταβιβάζει την ιδιοκτησία του στοιχείου σε αυτό τον οργανισμό. Δεν θα είστε πλέον ο άμεσος ιδιοκτήτης αυτού του στοιχείου μόλις το μετακινήσετε." }, - "moveManyToOrgDesc": { - "message": "Επιλέξτε έναν οργανισμό στον οποίο θέλετε να μετακινήσετε αυτά τα στοιχεία. Η μετακίνηση σε έναν οργανισμό μεταβιβάζει την ιδιοκτησία του στοιχείου σε αυτό τον οργανισμό. Δεν θα είστε πλέον ο άμεσος ιδιοκτήτης αυτού του στοιχείου μόλις το μετακινήσετε." - }, "collectionsDesc": { "message": "Επεξεργαστείτε τις συλλογές με τις οποίες μοιράζεται αυτό το στοιχείο. Μόνο οι χρήστες των οργανισμών που έχουν πρόσβαση σε αυτές τις συλλογές θα μπορούν να βλέπουν αυτό το στοιχείο." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "Έχετε επιλέξει $COUNT$ αντικείμενα. $MOVEABLE_COUNT$ αντικείμενα μπορούν να μετακινηθούν σε έναν οργανισμό, $NONMOVEABLE_COUNT$ δεν μπορεί.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Κωδικός Επαλήθευσης (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Επαναδημιουργία Κωδικού" - }, "length": { "message": "Μήκος" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Παρακαλούμε συνδεθείτε ξανά." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Παρακαλούμε συνδεθείτε ξανά. Εάν χρησιμοποιείτε άλλες εφαρμογές Bitwarden, αποσυνδεθείτε και επιστρέψτε σε αυτές επίσης." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Επικίνδυνη Ζώνη" }, - "dangerZoneDesc": { - "message": "Προσοχή, αυτές οι ενέργειες είναι μη αναστρέψιμες!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Κατάργηση Εξουσιοδότησης Συνεδριών" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Η διαδικασία θα σας αποσυνδέσει από την τρέχουσα συνεδρία και θα σας ζητήσει να συνδεθείτε ξανά. Οι ενεργές συνεδρίες σε άλλες συσκευές ενδέχεται να παραμείνουν ενεργοποιημένες για έως και μία ώρα." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "Η Ανακληθεί η Πρόσβαση από Όλες τις Συνεδρίες" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Η ενεργοποίηση σύνδεσης δύο βημάτων μπορεί να κλειδώσει οριστικά το λογαριασμό σας από το Bitwarden. Ένας κωδικός ανάκτησης σάς επιτρέπει να έχετε πρόσβαση στον λογαριασμό σας σε περίπτωση που δεν μπορείτε πλέον να χρησιμοποιήσετε τη σύνδεση δύο βημάτων (π. χ. χάνετε τη συσκευή σας). Η υποστήριξη πελατών του Bitwarden δεν θα είναι σε θέση να σας βοηθήσει αν χάσετε την πρόσβαση στο λογαριασμό σας. Συνιστούμε να γράψετε ή να εκτυπώσετε τον κωδικό ανάκτησης και να τον φυλάξετε σε ασφαλές μέρος." }, + "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." + }, "viewRecoveryCode": { "message": "Προβολή Κωδικού Ανάκτησης" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Διαχείριση" }, - "canManage": { - "message": "Δυνατότητα διαχείρισης" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Απενεργοποίηση" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Ανάκληση πρόσβασης" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "Ο πάροχος σύνδεσης δύο βημάτων του λογαριασμού σας, είναι ενεργοποιημένος." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "Παρουσιάστηκε πρόβλημα κατά την ανάγνωση του κλειδιού ασφαλείας. Προσπάθησε ξανά." }, - "twoFactorWebAuthnWarning": { - "message": "Λόγω περιορισμών της πλατφόρμας, το WebAuthn δεν μπορεί να χρησιμοποιηθεί σε όλες τις εφαρμογές Bitwarden. Θα πρέπει να ενεργοποιήσετε έναν άλλο πάροχο σύνδεσης δύο βημάτων, ώστε να έχετε πρόσβαση στο λογαριασμό σας όταν δεν μπορεί να χρησιμοποιηθεί το WebAuthn. Υποστηριζόμενες πλατφόρμες:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Web vault και επεκτάσεις browser σε έναν σταθερό / φορητό υπολογιστή με ένα πρόγραμμα περιήγησης WebAuthn (Chrome, Opera, Vivaldi, ή Firefox με FIDO U2F ενεργοποιημένο)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Ο Bitwarden κωδικός ανάκτησης, εισόδου δύο βημάτων" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "Αυτός ο χρήστης χρησιμοποιεί τρόπο σύνδεσης δύο βημάτων για να προστατεύσει το λογαριασμό του." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Αποσυνδεδεμένο SSO για το χρήστη $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Συσκευή" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Δημιουργία λογαριασμού στο" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "Χρησιμοποιείτε ένα μη υποστηριζόμενο browser. Το web vault ενδέχεται να μην λειτουργεί σωστά." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "Εάν δεν μπορείτε να αποκτήσετε πρόσβαση στο λογαριασμό σας μέσω των συνήθων μεθόδων σύνδεσης δύο βημάτων, μπορείτε να χρησιμοποιήσετε τον κωδικό ανάκτησης σύνδεσης δύο βημάτων για να απενεργοποιήσετε όλους τους παροχείς δύο βημάτων στο λογαριασμό σας." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Ανάκτηση Λογαριασμού Σύνδεσης Δύο Βημάτων" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Δεν είναι δυνατή η συνέχιση της ενημέρωσης του κλειδιού κρυπτογράφησης" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Ορίστε ελάχιστες απαιτήσεις, για ισχύ του κύριου κωδικού." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Απαίτηση για σύνδεση δύο βημάτων" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Ελάχιστες απαιτήσεις διαμόρφωσης γεννήτριας κωδικών." }, - "passwordGeneratorPolicyInEffect": { - "message": "Μία ή περισσότερες πολιτικές του οργανισμού επηρεάζουν τις ρυθμίσεις της γεννήτριας." - }, "masterPasswordPolicyInEffect": { "message": "Σε μία ή περισσότερες πολιτικές του οργανισμού απαιτείται ο κύριος κωδικός να πληρεί τις ακόλουθες απαιτήσεις:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Οι κάτοχοι και οι διαχειριστές του οργανισμού εξαιρούνται από την εφαρμογή αυτής της πολιτικής." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "Αρχείο" }, "sendTypeText": { "message": "Κείμενο" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Δημιουργία Νέου Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Διαγραφή Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Είστε βέβαιοι ότι θέλετε να διαγράψετε το Send;", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "Τι είδους Send είναι αυτό;", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Ημερομηνία διαγραφής" }, - "deletionDateDesc": { - "message": "Το Send θα διαγραφεί οριστικά την καθορισμένη ημερομηνία και ώρα.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Μέγιστος Αριθμός Πρόσβασης" }, - "maxAccessCountDesc": { - "message": "Εάν οριστεί, οι χρήστες δεν θα μπορούν πλέον να έχουν πρόσβαση σε αυτό το send μόλις επιτευχθεί ο μέγιστος αριθμός πρόσβασης.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Τρέχων Αριθμός Πρόσβασης" - }, - "sendPasswordDesc": { - "message": "Προαιρετικά απαιτείται κωδικός πρόσβασης για τους χρήστες για να έχουν πρόσβαση σε αυτό το Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Ιδιωτικές σημειώσεις σχετικά με αυτό το Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Απενεργοποιημένο" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Είστε βέβαιοι ότι θέλετε να καταργήσετε τον κωδικό πρόσβασης;" }, - "hideEmail": { - "message": "Απόκρυψη της διεύθυνσης email μου από τους παραλήπτες." - }, - "disableThisSend": { - "message": "Απενεργοποιήστε αυτό το Send έτσι ώστε κανείς να μην μπορεί να έχει πρόσβαση σε αυτό.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Όλα τα Sends" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Εκκρεμεί διαγραφή" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Έληξε" }, @@ -5161,13 +5441,6 @@ "message": "Μην επιτρέπετε στους χρήστες να αποκρύψουν τη διεύθυνση email τους από τους παραλήπτες κατά τη δημιουργία ή την επεξεργασία ενός send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendOptionsPolicyInEffect": { - "message": "Οι ακόλουθες οργανωτικές πολιτικές εφαρμόζονται επί του παρόντος:" - }, - "sendDisableHideEmailInEffect": { - "message": "Οι χρήστες δεν επιτρέπεται να αποκρύψουν τη διεύθυνση email τους από τους παραλήπτες κατά τη δημιουργία ή την επεξεργασία ενός send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "modifiedPolicyId": { "message": "Τροποποιημένη πολιτική $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Απενεργοποίηση προσωπικής ιδιοκτησίας για χρήστες οργανισμού" }, - "textHiddenByDefault": { - "message": "Κατά την πρόσβαση στην αποστολή, απόκρυψη του κειμένου από προεπιλογή", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Ένα φιλικό όνομα για την περιγραφή αυτού του Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Το κείμενο που θέλετε να στείλετε." - }, - "sendFileDesc": { - "message": "Το αρχείο που θέλετε να στείλετε." - }, - "copySendLinkOnSave": { - "message": "Αντιγράψτε το σύνδεσμο, για να μοιραστείτε αυτό το Send στο πρόχειρο μου, κατά την αποθήκευση." - }, - "sendLinkLabel": { - "message": "Σύνδεσμος Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "Παρουσιάστηκε σφάλμα κατά την αποθήκευση των ημερομηνιών διαγραφής και λήξης." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "Για να επαληθεύσετε τον 2FA σας παρακαλώ κάντε κλικ στο παρακάτω κουμπί." }, "webAuthnAuthenticate": { "message": "Ταυτοποίηση WebAutn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "Το WebAuthn δεν υποστηρίζεται σε αυτό το πρόγραμμα περιήγησης." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Σφάλμα" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Γεννήτρια", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "Τι θα θέλατε να δημιουργήσετε;" - }, - "passwordType": { - "message": "Τύπος Κωδικού" - }, - "regenerateUsername": { - "message": "Επαναδημιουργία Ονόματος Χρήστη" - }, "generateUsername": { "message": "Δημιουργία Ονόματος Χρήστη" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Τύπος Ονόματος Χρήστη" - }, "plusAddressedEmail": { "message": "Συν Διεύθυνση Email", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Χρησιμοποιήστε τα διαμορφωμένα εισερχόμενα catch-all του domain σας." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Τυχαίο", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Όνομα κεντρικού υπολογιστή", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "Διακριτικό πρόσβασης API" - }, "deviceVerification": { "message": "Επαλήθευση συσκευής" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Εκκίνηση Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Αριθμός χρηστών" }, - "loggingInAs": { - "message": "Σύνδεση ως" - }, - "notYou": { - "message": "Όχι εσείς;" - }, "pickAnAvatarColor": { "message": "Επιλέξτε ένα χρώμα avatar" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "Καμία συλλογή" }, - "canView": { - "message": "Δυνατότητα προβολής" - }, - "canViewExceptPass": { - "message": "Δυνατότητα προβολής, εκτός των κωδικών πρόσβασης" - }, - "canEdit": { - "message": "Δυνατότητα επεξεργασίας" - }, - "canEditExceptPass": { - "message": "Δυνατότητα επεξεργασίας, εκτός των κωδικών πρόσβασης" - }, "noCollectionsAdded": { "message": "Δεν προστέθηκαν συλλογές" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Αίτημα έγκρισης διαχειριστή" }, - "approveWithMasterPassword": { - "message": "Έγκριση με κύριο κωδικό πρόσβασης" - }, "trustedDeviceEncryption": { "message": "Κρυπτογράφηση έμπιστης συσκευής" }, "trustedDevices": { "message": "Έμπιστες συσκευές" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "Απαιτείται SSO", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO required", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Έγκριση αιτήματος" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Το αίτημά σας εστάλη στον διαχειριστή σας." }, - "youWillBeNotifiedOnceApproved": { - "message": "Θα ειδοποιηθείτε μόλις εγκριθεί." - }, "troubleLoggingIn": { "message": "Πρόβλημα σύνδεσης;" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Οι ιδιοκτήτες και οι διαχειριστές μπορούν να διαχειριστούν όλες τις συλλογές και τα στοιχεία" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Ψευδώνυμο τομέα" - }, "alreadyHaveAccount": { "message": "Έχετε ήδη λογαριασμό;" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "Δεν έχετε πρόσβαση για να διαχειριστείτε αυτήν τη συλλογή." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "μήνας ανά μέλος" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Θέσεις" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Μάθετε περισσότερα για το API του Bitwarden" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "Send αρχείων" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Send κειμένων" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Προσθήκη συνημμένου" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "Δεν μπορείτε να αφαιρέσετε συλλογές που έχουν μόνο δικαιώματα Προβολής: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 001918ef495..7f9c89484c2 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Critical applications" }, + "noCriticalAppsAtRisk":{ + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -35,7 +38,7 @@ "restoreMembers": { "message": "Restore members" }, - "cannotRestoreAccessError":{ + "cannotRestoreAccessError": { "message": "Cannot restore organization access" }, "allApplicationsWithCount": { @@ -113,6 +116,60 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +179,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "What type of item is this?" }, @@ -159,6 +222,9 @@ "notes": { "message": "Notes" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Note" }, @@ -383,6 +449,9 @@ "dragToSort": { "message": "Drag to sort" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Text" }, @@ -425,6 +494,31 @@ "editFolder": { "message": "Edit folder" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "Base domain", "description": "Domain name. Example: website.com" @@ -689,15 +783,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "ex.", "description": "Short abbreviation for 'example'." @@ -815,9 +900,6 @@ "filter": { "message": "Filter" }, - "moveSelectedToOrg": { - "message": "Move selected to organization" - }, "deleteSelected": { "message": "Delete selected" }, @@ -873,15 +955,6 @@ } } }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1057,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "Log in or create a new account to access your secure vault." }, @@ -1119,15 +1195,30 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, "whatIsADevice": { "message": "What is a device?" }, @@ -1137,6 +1228,9 @@ "logInInitiated": { "message": "Log in initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Submit" }, @@ -1201,12 +1295,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -1265,8 +1353,8 @@ "yourAccountIsLocked": { "message": "Your account is locked" }, - "uuid":{ - "message" : "UUID" + "uuid": { + "message": "UUID" }, "unlock": { "message": "Unlock" @@ -1326,12 +1414,39 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1365,12 +1480,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -1389,6 +1514,9 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Lost access to all of your two-step login providers? Use your recovery code to turn off all two-step login providers from your account." }, @@ -1431,6 +1559,9 @@ "webAuthnMigrated": { "message": "(Migrated from FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "Email" }, @@ -1449,9 +1580,6 @@ "moveToOrgDesc": { "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." }, - "moveManyToOrgDesc": { - "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved." - }, "collectionsDesc": { "message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item." }, @@ -1485,23 +1613,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Verification code (TOTP)" }, @@ -1619,9 +1730,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Regenerate password" - }, "length": { "message": "Length" }, @@ -1726,7 +1834,7 @@ }, "requestPending": { "message": "Request pending" - }, + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1794,12 +1902,6 @@ "dangerZone": { "message": "Danger zone" }, - "dangerZoneDesc": { - "message": "Careful, these actions are not reversible!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Deauthorize sessions" }, @@ -1809,6 +1911,27 @@ "deauthorizeSessionsWarning": { "message": "Proceeding will also log you out of your current session, requiring you to log back in. You will also be prompted for two-step login again, if set up. Active sessions on other devices may continue to remain active for up to one hour." }, + "newDeviceLoginProtection": { + "message":"New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message":"Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message":"Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message":"Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message":"Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message":"With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "All sessions deauthorized" }, @@ -2072,6 +2195,9 @@ "twoStepLoginRecoveryWarning": { "message": "Setting up two-step login can permanently lock you out of your Bitwarden account. A recovery code allows you to access your account in the event that you can no longer use your normal two-step login provider (example: you lose your device). Bitwarden support will not be able to assist you if you lose access to your account. We recommend you write down or print the recovery code and keep it in a safe place." }, + "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." + }, "viewRecoveryCode": { "message": "View recovery code" }, @@ -2110,8 +2236,20 @@ "manage": { "message": "Manage" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Turn off" @@ -2119,6 +2257,9 @@ "revokeAccess": { "message": "Revoke access" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "This two-step login provider is active on your account." }, @@ -2308,11 +2449,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "There was a problem reading the security key. Try again." }, - "twoFactorWebAuthnWarning": { - "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used. Supported platforms:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Web vault and browser extensions on a desktop/laptop with a WebAuthn supported browser (Chrome, Opera, Vivaldi, or Firefox with FIDO U2F turned on)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Your Bitwarden two-step login recovery code" @@ -3281,6 +3419,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3732,6 +3873,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { @@ -3789,6 +3933,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3912,6 +4117,12 @@ "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -4006,6 +4217,9 @@ "recoverAccountTwoStepDesc": { "message": "If you cannot access your account through your normal two-step login methods, you can use your two-step login recovery code to turn off all two-step providers on your account." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Recover account two-step login" }, @@ -4297,6 +4511,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4568,6 +4834,15 @@ "masterPassPolicyDesc": { "message": "Set requirements for master password strength." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Require two-step login" }, @@ -4583,9 +4858,6 @@ "passwordGeneratorPolicyDesc": { "message": "Set requirements for password generator." }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." - }, "masterPasswordPolicyInEffect": { "message": "One or more organization policies require your master password to meet the following requirements:" }, @@ -4851,12 +5123,40 @@ "requireSsoExemption": { "message": "Organization owners and admins are exempt from this policy's enforcement." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "File" }, "sendTypeText": { "message": "Text" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4881,19 +5181,15 @@ "message": "Delete Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Are you sure you want to delete this Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "What type of Send is this?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4906,21 +5202,6 @@ "maxAccessCount": { "message": "Maximum access count" }, - "maxAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Disabled" }, @@ -4947,13 +5228,6 @@ "removePasswordConfirmation": { "message": "Are you sure you want to remove the password?" }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, - "disableThisSend": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "All Sends" }, @@ -4964,6 +5238,9 @@ "pendingDeletion": { "message": "Pending deletion" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Expired" }, @@ -5185,13 +5462,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "The following organization policies are currently in effect:" - }, - "sendDisableHideEmailInEffect": { - "message": "Users are not allowed 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." - }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -5291,27 +5561,6 @@ "personalOwnershipCheckboxDesc": { "message": "Remove individual ownership for organization users" }, - "textHiddenByDefault": { - "message": "When accessing the Send, hide the text by default", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to Send." - }, - "sendFileDesc": { - "message": "The file you want to Send." - }, - "copySendLinkOnSave": { - "message": "Copy the link to share this Send to my clipboard upon save." - }, - "sendLinkLabel": { - "message": "Send link", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5460,12 +5709,21 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "To verify your 2FA please click the button below." }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn is not supported in this browser." }, @@ -5658,10 +5916,10 @@ "bulkFilteredMessage": { "message": "Excluded, not applicable for this action" }, - "nonCompliantMembersTitle":{ + "nonCompliantMembersTitle": { "message": "Non-compliant members" }, - "nonCompliantMembersError":{ + "nonCompliantMembersError": { "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" }, "fingerprint": { @@ -6554,15 +6812,6 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, - "regenerateUsername": { - "message": "Regenerate username" - }, "generateUsername": { "message": "Generate username" }, @@ -6603,9 +6852,6 @@ } } }, - "usernameType": { - "message": "Username type" - }, "plusAddressedEmail": { "message": "Plus addressed email", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6619,6 +6865,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random", "description": "Generates domain-based username using random letters" @@ -6768,6 +7017,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6822,9 +7095,6 @@ "message": "Hostname", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API access token" - }, "deviceVerification": { "message": "Device verification" }, @@ -7000,6 +7270,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7021,12 +7297,6 @@ "numberOfUsers": { "message": "Number of users" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "pickAnAvatarColor": { "message": "Pick an avatar color" }, @@ -7536,18 +7806,6 @@ "noCollection": { "message": "No collection" }, - "canView": { - "message": "Can view" - }, - "canViewExceptPass": { - "message": "Can view, except passwords" - }, - "canEdit": { - "message": "Can edit" - }, - "canEditExceptPass": { - "message": "Can edit, except passwords" - }, "noCollectionsAdded": { "message": "No collections added" }, @@ -8162,42 +8420,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8379,9 +8634,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -8495,6 +8747,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8544,9 +8799,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domain" - }, "alreadyHaveAccount": { "message": "Already have an account?" }, @@ -8608,11 +8860,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9078,7 +9330,7 @@ "message": "for Bitwarden using the implementation guide for your Identity Provider.", "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, - "userProvisioning":{ + "userProvisioning": { "message": "User provisioning" }, "scimIntegration": { @@ -9092,26 +9344,40 @@ "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, - "bwdc":{ + "bwdc": { "message": "Bitwarden Directory Connector" }, "bwdcDesc": { "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, - "eventManagement":{ + "eventManagement": { "message": "Event management" }, - "eventManagementDesc":{ + "eventManagementDesc": { "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, - "deviceManagement":{ + "deviceManagement": { "message": "Device management" }, - "deviceManagementDesc":{ + "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." - }, - "integrationCardTooltip":{ + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, + "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { "integration": { @@ -9120,7 +9386,7 @@ } } }, - "smIntegrationTooltip":{ + "smIntegrationTooltip": { "message": "Set up $INTEGRATION$.", "placeholders": { "integration": { @@ -9129,7 +9395,7 @@ } } }, - "smSdkTooltip":{ + "smSdkTooltip": { "message": "View $SDK$ repository", "placeholders": { "sdk": { @@ -9138,7 +9404,7 @@ } } }, - "integrationCardAriaLabel":{ + "integrationCardAriaLabel": { "message": "open $INTEGRATION$ implementation guide in a new tab.", "placeholders": { "integration": { @@ -9147,7 +9413,7 @@ } } }, - "smSdkAriaLabel":{ + "smSdkAriaLabel": { "message": "view $SDK$ repository in a new tab.", "placeholders": { "sdk": { @@ -9156,7 +9422,7 @@ } } }, - "smIntegrationCardAriaLabel":{ + "smIntegrationCardAriaLabel": { "message": "set up $INTEGRATION$ implementation guide in a new tab.", "placeholders": { "integration": { @@ -9177,6 +9443,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9560,7 +9829,7 @@ "message": "Config" }, "learnMoreAboutEmergencyAccess": { - "message":"Learn more about emergency access" + "message": "Learn more about emergency access" }, "learnMoreAboutMatchDetection": { "message": "Learn more about match detection" @@ -9601,9 +9870,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9697,6 +9972,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9828,10 +10112,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9851,7 +10131,7 @@ "selfHostingTitleProper": { "message": "Self-Hosting" }, - "claim-domain-single-org-warning" : { + "claim-domain-single-org-warning": { "message": "Claiming a domain will turn on the single organization policy." }, "single-org-revoked-user-warning": { @@ -9950,6 +10230,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -10083,8 +10372,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong":{ + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10096,8 +10447,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10113,8 +10464,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10140,5 +10491,84 @@ "example": "Acme c" } } + }, + "accountDeprovisioningNotification" : { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle" : { + "message" : "Upgrade for real event log data" + }, + "upgradeEventLogMessage":{ + "message" : "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index f37aa8150cf..b211ec070ab 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Critical applications" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "What type of item is this?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Notes" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Note" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Drag to sort" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Text" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Edit folder" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "Base domain", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "e.g.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filter" }, - "moveSelectedToOrg": { - "message": "Move selected to organisation" - }, "deleteSelected": { "message": "Delete selected" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "Log in or create a new account to access your secure vault." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognise this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Submit" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Lost access to all of your two-step login providers? Use your recovery code to turn off all two-step login providers from your account." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Migrated from FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "Email" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Choose an organisation that you wish to move this item to. Moving to an organisation transfers ownership of the item to that organisation. You will no longer be the direct owner of this item once it has been moved." }, - "moveManyToOrgDesc": { - "message": "Choose an organisation that you wish to move these items to. Moving to an organisation transfers ownership of the items to that organisation. You will no longer be the direct owner of these items once they have been moved." - }, "collectionsDesc": { "message": "Edit the collections that this item is being shared with. Only organisation users with access to these collections will be able to see this item." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organisation, $NONMOVEABLE_COUNT$ cannot.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Verification code (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Regenerate password" - }, "length": { "message": "Length" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications, log out and back in to those as well." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Danger zone" }, - "dangerZoneDesc": { - "message": "Careful, these actions are not reversible!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Deauthorise sessions" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Proceeding will also log you out of your current session, requiring you to log back in. You will also be prompted for two-step login again, if set up. Active sessions on other devices may continue to remain active for up to one hour." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails Bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have Bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "All sessions deauthorised" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Setting up two-step login can permanently lock you out of your Bitwarden account. A recovery code allows you to access your account in the event that you can no longer use your normal two-step login provider (example: you lose your device). Bitwarden support will not be able to assist you if you lose access to your account. We recommend you write down or print the recovery code and keep it in a safe place." }, + "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." + }, "viewRecoveryCode": { "message": "View recovery code" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Manage" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Turn off" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Revoke access" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "This two-step login provider is active on your account." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "There was a problem reading the security key. Try again." }, - "twoFactorWebAuthnWarning": { - "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used. Supported platforms:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Web vault and browser extensions on a desktop/laptop with a WebAuthn supported browser (Chrome, Opera, Vivaldi, or Firefox with FIDO U2F turned on)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Your Bitwarden two-step login recovery code" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "If you cannot access your account through your normal two-step login methods, you can use your two-step login recovery code to turn off all two-step providers on your account." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Recover account two-step login" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Set requirements for master password strength." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Require two-step login" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Set requirements for password generator." }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organisation policies are affecting your generator settings." - }, "masterPasswordPolicyInEffect": { "message": "One or more organisation policies require your master password to meet the following requirements:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Organisation owners and admins are exempt from this policy's enforcement." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "File" }, "sendTypeText": { "message": "Text" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Delete send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Are you sure you want to delete this send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "What type of send is this?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maximum access count" }, - "maxAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Disabled" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Are you sure you want to remove the password?" }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, - "disableThisSend": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "All sends" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Pending deletion" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Expired" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "The following organisation policies are currently in effect:" - }, - "sendDisableHideEmailInEffect": { - "message": "Users are not allowed 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." - }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Remove individual ownership for organisation users" }, - "textHiddenByDefault": { - "message": "When accessing the Send, hide the text by default", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to Send." - }, - "sendFileDesc": { - "message": "The file you want to Send." - }, - "copySendLinkOnSave": { - "message": "Copy the link to share this Send to my clipboard upon save." - }, - "sendLinkLabel": { - "message": "Send link", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "To verify your 2FA please click the button below." }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn is not supported in this browser." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, - "regenerateUsername": { - "message": "Regenerate username" - }, "generateUsername": { "message": "Generate username" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Username type" - }, "plusAddressedEmail": { "message": "Plus addressed email", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Hostname", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API access token" - }, "deviceVerification": { "message": "Device verification" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Number of users" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "pickAnAvatarColor": { "message": "Pick an avatar color" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "No collection" }, - "canView": { - "message": "Can view" - }, - "canViewExceptPass": { - "message": "Can view, except passwords" - }, - "canEdit": { - "message": "Can edit" - }, - "canEditExceptPass": { - "message": "Can edit, except passwords" - }, "noCollectionsAdded": { "message": "No collections added" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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 from where 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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organisation", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrolment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organisation permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domain" - }, "alreadyHaveAccount": { "message": "Already have an account?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognise a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,7 +10348,69 @@ "organizationNameMaxLength": { "message": "Organisation name cannot exceed 50 characters." }, - "resellerRenewalWarning": { + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { @@ -10033,7 +10423,7 @@ } } }, - "resellerOpenInvoiceWarning": { + "resellerOpenInvoiceWarningMgs": { "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { @@ -10050,7 +10440,7 @@ } } }, - "resellerPastDueWarning": { + "resellerPastDueWarningMsg": { "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organisation subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organisation. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organisation" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organisation to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organisations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organisation's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organisation to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organisation" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organisation event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organisation." + }, + "cannotCreateCollection": { + "message": "Free organisations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index ec67bb192c9..6b4b6e11ab3 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Critical applications" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "What type of item is this?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Notes" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Note" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Drag to sort" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Text" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Edit folder" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "Base domain", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "e.g.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filter" }, - "moveSelectedToOrg": { - "message": "Move Selected to Organisation" - }, "deleteSelected": { "message": "Delete selected" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "Log in or create a new account to access your secure vault." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognise this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Submit" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Lost access to all of your two-factor providers? Use your recovery code to disable all two-factor providers from your account." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Migrated from FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "Email" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Choose an organisation that you wish to move this item to. Moving to an organisation transfers ownership of the item to that organisation. You will no longer be the direct owner of this item once it has been moved." }, - "moveManyToOrgDesc": { - "message": "Choose an organisation that you wish to move these items to. Moving to an organisation transfers ownership of the items to that organisation. You will no longer be the direct owner of these items once they have been moved." - }, "collectionsDesc": { "message": "Edit the collections that this item is being shared with. Only organisation users with access to these collections will be able to see this item." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organisation, $NONMOVEABLE_COUNT$ cannot.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Verification code (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Regenerate password" - }, "length": { "message": "Length" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications, log out and back in to those as well." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Danger zone" }, - "dangerZoneDesc": { - "message": "Careful, these actions are not reversible!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Deauthorise sessions" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Proceeding will also log you out of your current session, requiring you to log back in. You will also be prompted for two-step login again, if enabled. Active sessions on other devices may continue to remain active for up to one hour." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails Bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have Bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "All sessions deauthorised" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Enabling two-step login can permanently lock you out of your Bitwarden account. A recovery code allows you to access your account in the event that you can no longer use your normal two-step login provider (e.g. you lose your device). Bitwarden support will not be able to assist you if you lose access to your account. We recommend you write down or print the recovery code and keep it in a safe place." }, + "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." + }, "viewRecoveryCode": { "message": "View recovery code" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Manage" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Disable" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Revoke access" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "This two-step login provider is enabled on your account." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "There was a problem reading the security key. Try again." }, - "twoFactorWebAuthnWarning": { - "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should enable another two-step login provider so that you can access your account when WebAuthn cannot be used. Supported platforms:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Web vault and browser extensions on a desktop/laptop with a WebAuthn enabled browser (Chrome, Opera, Vivaldi, or Firefox with FIDO U2F enabled)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Your Bitwarden two-step login recovery code" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "If you cannot access your account through your normal two-step login methods, you can use your two-step login recovery code to disable all two-step providers on your account." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Recover account two-step login" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Set minimum requirements for master password strength." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Require two-step login" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Set minimum requirements for password generator configuration." }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organisation policies are affecting your generator settings." - }, "masterPasswordPolicyInEffect": { "message": "One or more organisation policies require your master password to meet the following requirements:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Organization Owners and Administrators are exempt from this policy's enforcement." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "File" }, "sendTypeText": { "message": "Text" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Create New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Delete Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Are you sure you want to delete this Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "What type of Send is this?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Deletion Date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maximum Access Count" }, - "maxAccessCountDesc": { - "message": "If set, users will no longer be able to access this send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current Access Count" - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Disabled" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Are you sure you want to remove the password?" }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, - "disableThisSend": { - "message": "Disable this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "All Sends" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Pending deletion" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Expired" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "The following organisation policies are currently in effect:" - }, - "sendDisableHideEmailInEffect": { - "message": "Users are not allowed 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." - }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Disable personal ownership for organization users" }, - "textHiddenByDefault": { - "message": "When accessing the Send, hide the text by default", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to send." - }, - "sendFileDesc": { - "message": "The file you want to send." - }, - "copySendLinkOnSave": { - "message": "Copy the link to share this Send to my clipboard upon save." - }, - "sendLinkLabel": { - "message": "Send link", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "To verify your 2FA please click the button below." }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn is not supported in this browser." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, - "regenerateUsername": { - "message": "Regenerate username" - }, "generateUsername": { "message": "Generate username" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Username type" - }, "plusAddressedEmail": { "message": "Plus addressed email", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Hostname", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API access token" - }, "deviceVerification": { "message": "Device verification" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Number of users" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "pickAnAvatarColor": { "message": "Pick an avatar colour" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "No collection" }, - "canView": { - "message": "Can view" - }, - "canViewExceptPass": { - "message": "Can view, except passwords" - }, - "canEdit": { - "message": "Can edit" - }, - "canEditExceptPass": { - "message": "Can edit, except passwords" - }, "noCollectionsAdded": { "message": "No collections added" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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 from where 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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organisation", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organisation permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domain" - }, "alreadyHaveAccount": { "message": "Already have an account?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognise a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,7 +10348,69 @@ "organizationNameMaxLength": { "message": "Organisation name cannot exceed 50 characters." }, - "resellerRenewalWarning": { + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { @@ -10033,7 +10423,7 @@ } } }, - "resellerOpenInvoiceWarning": { + "resellerOpenInvoiceWarningMgs": { "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { @@ -10050,7 +10440,7 @@ } } }, - "resellerPastDueWarning": { + "resellerPastDueWarningMsg": { "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organisation subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organisation. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organisation" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organisation to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organisations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organisation's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organisation to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organisation" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organisation event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organisation." + }, + "cannotCreateCollection": { + "message": "Free organisations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index 0170c225c03..ce40767f4c1 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Critical applications" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "Kiuspeca estas ĉi tiu ero?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Notoj" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Note" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Trenu por ordigi" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Teksto" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Redakti dosierujon" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "Baza domajno", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "ekz.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filter" }, - "moveSelectedToOrg": { - "message": "Movu Elektitaĵojn al Organizo" - }, "deleteSelected": { "message": "Forigi Elektitajn" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "Ne" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "Ensalutu aŭ kreu novan konton por aliri vian sekuran trezorejon." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Ensaluti en Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Sendu" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Pasvorta Konsilo" - }, - "enterEmailToGetHint": { - "message": "Enigu vian retpoŝtadreson por ricevi vian ĉefan pasvortan aludon." - }, "getMasterPasswordHint": { "message": "Akiru ĉefan pasvortan sugeston" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "Versio $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Memoru min" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Sendu retpoŝtan kontrol-kodon denove" }, "useAnotherTwoStepMethod": { "message": "Uzu alian metodon de identigo en du-ŝtupa saluto" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Enmetu vian YubiKey en la USB-havenon de via komputilo, tiam tuŝu ĝian butonon." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Elektebloj de la du-ŝtupa saluto" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Ĉu vi perdis aliron al ĉiuj viaj du-faktoraj provizantoj? Uzu vian reakiran kodon por malŝalti ĉiujn du-faktorajn provizantojn de via konto." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Migrated from FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "Retpoŝto" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." }, - "moveManyToOrgDesc": { - "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved." - }, "collectionsDesc": { "message": "Redaktu la kolektojn kun kiuj ĉi tiu ero estas dividita. Nur organizaj uzantoj kun aliro al ĉi tiuj kolektoj povos vidi ĉi tiun eron." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Kontrola Kodo (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Regeneri Pasvorton" - }, "length": { "message": "Longo" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Bonvolu saluti refoje." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Bonvolu saluti refoje. Se vi uzas aliajn programojn de Bitwarden, adiaŭu kaj ankaŭ salutu ilin." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Danĝera Zono" }, - "dangerZoneDesc": { - "message": "Atentu, ĉi tiuj agoj ne estas reigeblaj!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Senrajtigi Sesiojn" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Se vi daŭrigos vian adiaŭadon de la nuna seanco, necesos vin saluti denove. Oni ankaŭ demandos de vi du-faktoran aŭtentigon, se tiu elekteblo estas ebligita. La seancoj aktivaj sur aliaj aparatoj povas resti daŭre aktivaj ankoraŭ unu horon." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "Ĉiuj Sesioj Neaŭtorizitaj" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Ebligi du-paŝan ensaluton povas konstante elŝlosi vin el via Bitwarden-konto. Rekuperiga kodo permesas vin aliri vian konton, se vi ne plu povas uzi vian normalan du-paŝan ensalutan provizanton (ekz. vi perdas Bitwarden-subteno ne povos helpi vin se vi perdos aliron al via konto. Ni rekomendas al vi skribi aŭ presi la reakiran kodon kaj konservi ĝin en sekura loko. " }, + "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." + }, "viewRecoveryCode": { "message": "Rigardi Rekuperan Kodon" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Administri" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Neebligi" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Revoke access" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "Ĉi tiu du-ŝtupa ensaluta provizanto estas ebligita en via konto." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "There was a problem reading the security key. Try again." }, - "twoFactorWebAuthnWarning": { - "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used. Supported platforms:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Web vault and browser extensions on a desktop/laptop with a WebAuthn supported browser (Chrome, Opera, Vivaldi, or Firefox with FIDO U2F turned on)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Via kodo de senpaneigo de du-ŝtupa saluto de Bitwarden" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "Ĉi tiu uzanto uzas du-paŝan ensaluton por protekti sian konton." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Aparato" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "Vi uzas nesubtenatan tTT-legilon. La ttt-volbo eble ne funkcias ĝuste." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "Se vi ne povas aliri vian konton per viaj normalaj du-ŝtupaj ensalutaj metodoj, vi povas uzi vian du-ŝtupan ensalutan rekuperan kodon por malŝalti ĉiujn du-ŝtupajn provizantojn en via konto." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Senpaneigi la du-faktoran aŭtentigon de la konto" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Agordi minimumajn postulojn por majstra pasvorta forto." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Devigu dufazan ensaluton" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Agordi minimumajn postulojn por agordi pasvortan generilon." }, - "passwordGeneratorPolicyInEffect": { - "message": "Unu aŭ pluraj organizaj politikoj influas viajn generatorajn agordojn." - }, "masterPasswordPolicyInEffect": { "message": "Unu aŭ pluraj organizaj politikoj postulas vian ĉefan pasvorton por plenumi la jenajn postulojn:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Organizaj Posedantoj kaj Administrantoj estas esceptitaj de la apliko de ĉi tiu politiko." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "Dosiero" }, "sendTypeText": { "message": "Teksto" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Krei novan sendon", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Forigi Sendu", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Ĉu vi certe volas forigi ĉi tiun Sendon?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "Kia Sendo estas ĉi tio?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Dato de Forigo" }, - "deletionDateDesc": { - "message": "La Sendo estos definitive forigita en la specifaj dato kaj horo.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maksimuma Aliro-Kalkulo" }, - "maxAccessCountDesc": { - "message": "Se agordite, uzantoj ne plu povos aliri ĉi tiun sendon post kiam la maksimuma alira kalkulo estos atingita.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Nuna Alira Kalkulo" - }, - "sendPasswordDesc": { - "message": "Laŭvole postulas pasvorton por uzantoj aliri ĉi tiun Sendon.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Privataj notoj pri ĉi tiu Sendo.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Neebligita" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Ĉu vi certe volas forigi la pasvorton?" }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, - "disableThisSend": { - "message": "Malŝalti ĉi tiun Sendon por ke neniu povu aliri ĝin.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Ĉiuj Sendoj" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Atendanta forigo" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Eksvalidiĝis" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "The following organization policies are currently in effect:" - }, - "sendDisableHideEmailInEffect": { - "message": "Users are not allowed 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." - }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Malebligi personan posedon por organizaj uzantoj" }, - "textHiddenByDefault": { - "message": "Alirante la Sendon, kaŝu la tekston defaŭlte", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Amika nomo por priskribi ĉi tiun Sendon.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "La teksto, kiun vi volas sendi." - }, - "sendFileDesc": { - "message": "La dosiero, kiun vi volas sendi." - }, - "copySendLinkOnSave": { - "message": "Kopiu la ligon por dividi ĉi tion Sendu al mia tondujo post konservado." - }, - "sendLinkLabel": { - "message": "Sendi ligon", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Sendi", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "Estis eraro konservante viajn forigajn kaj eksvalidajn datojn." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "To verify your 2FA please click the button below." }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn is not supported in this browser." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Eraro" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, - "regenerateUsername": { - "message": "Regenerate username" - }, "generateUsername": { "message": "Generate username" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Username type" - }, "plusAddressedEmail": { "message": "Plus addressed email", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Hazarda", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Domajna nomo", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API access token" - }, "deviceVerification": { "message": "Device verification" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Nombro de uzantoj" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "pickAnAvatarColor": { "message": "Pick an avatar color" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "No collection" }, - "canView": { - "message": "Can view" - }, - "canViewExceptPass": { - "message": "Can view, except passwords" - }, - "canEdit": { - "message": "Can edit" - }, - "canEditExceptPass": { - "message": "Can edit, except passwords" - }, "noCollectionsAdded": { "message": "No collections added" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domain" - }, "alreadyHaveAccount": { "message": "Already have an account?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index 3c3d7bb135d..1c71a29f48b 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -5,8 +5,11 @@ "criticalApplications": { "message": "Aplicaciones críticas" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { - "message": "Access Intelligence" + "message": "Inteligencia de Acceso" }, "riskInsights": { "message": "Risk Insights" @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "¿Qué tipo de elemento es este?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Notas" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Nota" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Arrastra para ordenar" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Texto" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Editar carpeta" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "Dominio base", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Nombre del elemento" }, - "cannotRemoveViewOnlyCollections": { - "message": "No puedes eliminar colecciones con permisos de solo visualización: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "ej.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filtro" }, - "moveSelectedToOrg": { - "message": "Mover los seleccionados a la organización" - }, "deleteSelected": { "message": "Eliminar selección" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Elementos seleccionados movidos a $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "Identifícate o crea una nueva cuenta para acceder a tu caja fuerte." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { - "message": "Verifica tu identidad" + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." }, "logInInitiated": { "message": "Inicio de sesión en proceso" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Enviar" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Pista de contraseña" - }, - "enterEmailToGetHint": { - "message": "Introduce el correo electrónico de tu cuenta para recibir la pista de tu contraseña maestra." - }, "getMasterPasswordHint": { "message": "Obtener pista de la contraseña maestra" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "Se ha enviado una notificación a tu dispositivo." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "Versión $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Recordarme" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Reenviar código de verificación por correo electrónico" }, "useAnotherTwoStepMethod": { "message": "Utilizar otro método de autenticación en dos pasos" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Inserta tu YubiKey en el puerto USB de tu equipo y posteriormente pulsa su botón." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Opciones de la autenticación en dos pasos" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "¿Has perdido el acceso a todos tus métodos de autenticación en dos pasos? Utiliza tu código de recuperación para deshabilitar todos los métodos de autenticación en dos pasos de tu cuenta." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Migrado desde FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "Correo electrónico" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Elige una organización a la que deseas mover este objeto. Moviendo a una organización transfiere la propiedad del objeto a esa organización. Ya no serás el dueño directo de este objeto una vez que haya sido movido." }, - "moveManyToOrgDesc": { - "message": "Elija una organización a la que desea mover estos elementos. Moviendo a una organización transfiere la propiedad de los elementos a esa organización. Ya no serás el dueño directo de estos objetos una vez que hayan sido movidos." - }, "collectionsDesc": { "message": "Elige las colecciones con la que este elemento va a ser compartido. Solo los miembros de la organización que puedan acceder a esas colecciones podrán ver el elemento." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "Ha seleccionado $COUNT$ elemento(s). Se pueden mover $MOVEABLE_COUNT$ elemento(s) a una organización, no se pueden mover $NONMOVEABLE_COUNT$.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Código de verificación (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Regenerar contraseña" - }, "length": { "message": "Longitud" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Por favor, vuelve a acceder." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Por favor, vuelve a acceder. Si estás utilizando otras aplicaciones de Bitwarden, cierra sesión y vuelva a acceder en ellas también." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Zona peligrosa" }, - "dangerZoneDesc": { - "message": "¡Cuidado, estas acciones no son reversibles!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Desautorizar sesiones" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Proceder también cerrará tu sesión actual, requiriendo que vuelvas a identificarte. También se te pedirá nuevamente tu autenticación en dos pasos en caso de que la tengas habilitada. Las sesiones activas en otros dispositivos pueden mantenerse activas hasta una hora más." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "Desautorizadas todas las sesiones" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Habilitar la autenticación en dos pasos puede impedirte acceder permanentemente a tu cuenta de Bitwarden. Un código de recuperación te permite acceder a la cuenta en caso de que no puedas usar más tu proveedor de autenticación en dos pasos (ej. si pierdes tu dispositivo). El soporte de Bitwarden no será capaz de asistirte si pierdes acceso a tu cuenta. Te recomendamos que escribas o imprimas este código y lo guardes en un lugar seguro." }, + "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." + }, "viewRecoveryCode": { "message": "Ver código de recuperación" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Gestionar" }, - "canManage": { - "message": "Puede gestionar" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Desactivar" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Revocar el acceso" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "Este proveedor de autenticación en dos pasos está habilitado para tu cuenta." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "Hubo un problema al leer la llave de seguridad. Inténtalo de nuevo." }, - "twoFactorWebAuthnWarning": { - "message": "Debido a las limitaciones de la plataforma, WebAuthn no puede ser utilizado en todas las aplicaciones de Bitwarden. Debería habilitar otro proveedor de inicio de sesión en dos-pasos para que pueda acceder a su cuenta cuando WebAuthn no pueda ser usado. Plataformas soportadas:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Caja fuerte web y extensiones de navegador en un escritorio/portátil con un navegador WebAuthn habilitado (Chrome, Opera, Vivaldi o Firefox con FIDO U2F habilitado)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Tu código de recuperación de inicio de sesión de dos pasos de Bitwarden" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "Este usuario está usando autenticación de dos pasos para proteger su cuenta." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "SSO desvinculado para el usuario $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Dispositivo" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creando una cuenta en" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "Está utilizando un navegador web no compatible. Es posible que la caja fuerte web no funcione correctamente." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "Si no puedes acceder a tu cuenta utilizando tus métodos normales de autenticación en dos pasos, puedes utilizar el código de recuperación de autenticación en dos pasos para deshabilitar todos los proveedores de tu cuenta." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Recuperar autenticación en dos pasos" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Establecer requisitos mínimos para la fortaleza de la contraseña maestra." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Requiere inicio de sesión en dos pasos" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Establecer requisitos mínimos para la configuración del generador de contraseñas." }, - "passwordGeneratorPolicyInEffect": { - "message": "Una o más políticas de la organización están afectando su configuración del generador." - }, "masterPasswordPolicyInEffect": { "message": "Una o más políticas de la organización requieren que su contraseña maestra cumpla con los siguientes requisitos:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Los propietarios y administradores de la organización están exentos de la aplicación de esta política." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "Archivo" }, "sendTypeText": { "message": "Texto" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Crear nuevo Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Eliminar Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "¿Estás seguro de eliminar este Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "¿Qué tipo de Send es este?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Fecha de eliminación" }, - "deletionDateDesc": { - "message": "El envío se eliminará permanentemente en la fecha y hora especificadas.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Número máximo de accesos" }, - "maxAccessCountDesc": { - "message": "Si se establece, los usuarios ya no podrán acceder a este envío una vez que se alcance el número máximo de accesos.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Número de accesos actuales" - }, - "sendPasswordDesc": { - "message": "Opcionalmente se requiere una contraseña para que los usuarios accedan a este Envío.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Notas privadas sobre este Envío.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Deshabilitado" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "¿Está seguro que desea eliminar la contraseña?" }, - "hideEmail": { - "message": "Ocultar mi dirección de correo electrónico a los destinatarios." - }, - "disableThisSend": { - "message": "Deshabilita este envío para que nadie pueda acceder a él.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Todos los Sends" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Borrado pendiente" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Caducado" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "Las siguientes políticas de organización están actualmente en vigor:" - }, - "sendDisableHideEmailInEffect": { - "message": "Los usuarios no pueden 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." - }, "modifiedPolicyId": { "message": "Política modificada $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Desactivar la propiedad personal para los usuarios de la organización" }, - "textHiddenByDefault": { - "message": "Al acceder al Enviar, oculta el texto por defecto", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Un nombre amigable para describir este Envío.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "El texto que desea enviar." - }, - "sendFileDesc": { - "message": "El archivo que desea enviar." - }, - "copySendLinkOnSave": { - "message": "Copia el enlace para compartir este envío a mi portapapeles al guardar." - }, - "sendLinkLabel": { - "message": "Enviar enlace", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Enviar", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "Hubo un error al guardar las fechas de eliminación y caducidad." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "Para verificar su 2FA por favor haga clic en el botón de abajo." }, "webAuthnAuthenticate": { "message": "Autenticar WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn no es compatible con este navegador." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "El permiso \"gestionar usuarios\" también debe ser otorgado junto con el permiso \"gestionar cuenta\"" }, @@ -6516,15 +6791,6 @@ "message": "Generador", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "¿Qué desea generar?" - }, - "passwordType": { - "message": "Tipo de contraseña" - }, - "regenerateUsername": { - "message": "Regenerar nombre de usuario" - }, "generateUsername": { "message": "Generar nombre de usuario" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Tipo de nombre de usuario" - }, "plusAddressedEmail": { "message": "Correo electrónico más dirigido", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Utiliza la bandeja de entrada global configurada de tu dominio." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Aleatorio", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Nombre del host", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "Token de acceso API" - }, "deviceVerification": { "message": "Verificación del dispositivo" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Iniciar Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Número de usuarios" }, - "loggingInAs": { - "message": "Iniciando sesión como" - }, - "notYou": { - "message": "¿No eres tú?" - }, "pickAnAvatarColor": { "message": "Elige un color de avatar" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "Sin colecciones" }, - "canView": { - "message": "Puede ver" - }, - "canViewExceptPass": { - "message": "Puede ver, excepto contraseñas" - }, - "canEdit": { - "message": "Puede editar" - }, - "canEditExceptPass": { - "message": "Puede editar, excepto contraseñas" - }, "noCollectionsAdded": { "message": "No hay colecciones añadidas" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Solicitar aprobación del administrador" }, - "approveWithMasterPassword": { - "message": "Aprobar con contraseña maestra" - }, "trustedDeviceEncryption": { "message": "Cifrado de dispositivo de confianza" }, "trustedDevices": { "message": "Dispositivos de confianza" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "organización única", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "single organization", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": "política,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "policy,", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "SSO requerido", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO required", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "política, y", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "policy, and", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "administración de recuperación de cuenta", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "account recovery administration", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Aprobar solicitud" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No hay solicitudes de dispositivo" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Su solicitud ha sido enviada a su administrador." }, - "youWillBeNotifiedOnceApproved": { - "message": "Se le notificará una vez aprobado." - }, "troubleLoggingIn": { "message": "¿Problemas para iniciar sesión?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Propietarios y administradores pueden gestionar todas las colecciones y elementos" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Dominio alias" - }, "alreadyHaveAccount": { "message": "¿Ya tienes una cuenta?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "No puedes eliminar colecciones con permisos de solo visualización: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index 700c748add8..f9a5b810ded 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Critical applications" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "Mis tüüpi kirje see on?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Märkmed" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Märge" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Lohista sorteerimiseks" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Tekst" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Muuda kausta" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "Domeen", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Kirje nimi" }, - "cannotRemoveViewOnlyCollections": { - "message": "Sa ei saa eemaldada neid kogumikke ainult vaatamisloaga: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "nt.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filter" }, - "moveSelectedToOrg": { - "message": "Teisalda valitud organisatsiooni" - }, "deleteSelected": { "message": "Kustuta valitud" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Valitud kirjed teisaldati $ORGNAME$-le", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Kirjed liigutatud $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "Ei" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "Logi sisse või loo uus konto." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { - "message": "Kinnitage oma Identiteet" + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." }, "logInInitiated": { "message": "Sisselogimine käivitatud" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Kinnita" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Parooli vihje" - }, - "enterEmailToGetHint": { - "message": "Ülemparooli vihje saamiseks sisesta oma konto e-posti aadress." - }, "getMasterPasswordHint": { "message": "Tuleta ülemparooli vihjega meelde" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "Sinu seadmesse saadeti teavitus." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "Versioon $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Jäta mind meelde" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Saada e-postile uus kinnituskood" }, "useAnotherTwoStepMethod": { "message": "Kasuta teist kaheastmelist sisselogimise meetodit" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Sisesta oma YubiKey arvuti USB porti ja kliki sellele nupule." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Kaheastmelise sisselogimise valikud" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Sul ei ole ligipääsu ühelegi kaheastmelise kinnitamise teenusele? Kasuta taastamise koodi, et kaheastmeline kinnitamine oma kontol välja lülitada." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(pärineb FIDO'lt)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "E-post" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Vali organisatsioon, kuhu soovid seda kirjet teisaldada. Teisaldamisega saab kirje omanikuks organisatsioon. Pärast kirje teisaldamist ei ole sa enam selle otsene omanik." }, - "moveManyToOrgDesc": { - "message": "Vali organisatsioon, kuhu soovid seda kirjet teisaldada. Teisaldamisega saab kirje omanikuks organisatsioon. Pärast kirje teisaldamist ei ole sa enam selle otsene omanik." - }, "collectionsDesc": { "message": "Muuda kollektsioone, millega seda kirjet jagatakse. Seda kirjet näevad üksnes organisatsiooni kasutajad, kes omavad nendele kollektsioonidele ligipääsu." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "Valisid $COUNT$ kirje(t). $MOVEABLE_COUNT$ kirje(t) saab teisaldada organisatsiooni, $NONMOVEABLE_COUNT$ ei saa.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Kinnituskood (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Genereeri parool uuesti" - }, "length": { "message": "Pikkus" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Palun logi uuesti sisse." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Palun logi uuesti sisse. Kui kasutad teisi Bitwardeni rakendusi, pead ka nendes uuesti sisse logima." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Ohtlik tsoon" }, - "dangerZoneDesc": { - "message": "Ettevaatust, neid toiminguid ei saa tagasi võtta!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Sessioonide tühistamine" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Jätkatest logitakse sind ka käimasolevast sessioonist välja, mistõttu pead kontosse uuesti sisse logima. Lisaks võidakse küsida kaheastmelist kinnitust, kui see on sisse lülitatud. Teised kontoga ühendatud seadmed võivad jääda sisselogituks kuni üheks tunniks." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "Kõikidest seadmetest on välja logitud" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Kaheastmelise kinnitamine aktiveerimine võib luua olukorra, kus sul on võimatu oma Bitwardeni kontosse sisse logida. Näiteks kui kaotad oma nutiseadme. Taastamise kood võimaldab aga kontole ligi pääseda ka olukorras, kus kaheastmelist kinnitamist ei ole võimalik läbi viia. Sellistel juhtudel ei saa ka Bitwardeni klienditugi sinu kontole ligipääsu taastada. Selle tõttu soovitame taastekoodi välja printida ja seda turvalises kohas hoida." }, + "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." + }, "viewRecoveryCode": { "message": "Vaata taastamise koodi" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Haldus" }, - "canManage": { - "message": "Saab muuta" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Keela" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Tühistada ligipääsu luba" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "See kaheastmelise kinnitamise teenus on sinu kontol sisse lülitatud." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "Turvavõtme lugemisel tekkis tõrge. Proovi uuesti." }, - "twoFactorWebAuthnWarning": { - "message": "Mõnede platvormi piirangute tõttu ei saa WebAuthn'i kõikide Bitwardeni rakendustega kasutada. Võiksid kaaluda teise kaheastmelise kinnitamise aktiveerimist olukordadeks, kus WebAuthn'i ei saa kasutada. Toetatud platvormid:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Veebihoidla- ja brauseri laiendused laua- ja sülearvutis, kus on WebAuthn toega brauser (Chrome, Opera, Vivaldi või Firefox, kus on FIDO U2F sisse lülitatud)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Bitwardeni kaheastmelise logimise varukood" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "Sellel kasutajal on kaheastmeline kinnitamine sisse lülitatud." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Kasutaja $ID$ SSO on eemaldatud.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Seade" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Konto loomise asukoht" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "Kasutad brauserit, mida ei toetata. Veebihoidla ei pruugi hästi töötada." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "Kui sa ei pääse oma kontole ühegi kaheastmeliste kinnitamise meetodi abiga ligi, saad selle välja lülitada. Selleks kasuta kaheastmelise kinnitamise tühistamise koodi." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Taasta kaheastmelise kinnitamise ligipääs" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Määra minimaalsed ülemparooli tugevuse tingimused." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Nõua kahe-astmelist sisselogimist" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Määra parooli genereerija konfiguratsiooni tingimused." }, - "passwordGeneratorPolicyInEffect": { - "message": "Organisatsiooni seaded mõjutavad parooli genereerija sätteid." - }, "masterPasswordPolicyInEffect": { "message": "Üks või enam organisatsiooni eeskirja nõuavad, et ülemparool vastaks nendele nõudmistele:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Selle poliitika rakendamine ei puuduta Omanikke ega Administraatoreid." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "Fail" }, "sendTypeText": { "message": "Tekst" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Loo uus Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Kustuta Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Soovid tõesti selle Sendi kustutada?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "Mis tüüpi Send see on?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Kustutamise kuupäev" }, - "deletionDateDesc": { - "message": "Send kustutatakse määratud kuupäeval ja kellaajal jäädavalt.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maksimaalne ligipääsude arv" }, - "maxAccessCountDesc": { - "message": "Selle valimisel ei saa kasutajad pärast maksimaalse ligipääsude arvu saavutamist sellele Sendile enam ligi.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Hetkeline ligipääsude arv" - }, - "sendPasswordDesc": { - "message": "Soovi korral nõua parooli, millega Sendile ligi pääseb.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Privaatne märkus selle Sendi kohta.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Keelatud" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Soovid kindlasti selle parooli eemaldada?" }, - "hideEmail": { - "message": "Ära näita saajatele minu e-posti aadressi." - }, - "disableThisSend": { - "message": "Keela see Send, et keegi ei pääseks sellele ligi.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Kõik Sendid" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Kustutamise ootel" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Aegunud" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "Hetkel on kehtivad järgmised organisatsiooni poliitikad:" - }, - "sendDisableHideEmailInEffect": { - "message": "Kasutajatel pole lubatud 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." - }, "modifiedPolicyId": { "message": "Muutis poliitikat $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Keela organisatsiooni liikmetel paroolide salvestamine isiklikku Hoidlasse" }, - "textHiddenByDefault": { - "message": "Sendi avamisel peida tekst automaatselt", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Sisesta Sendi nimi (kohustuslik).", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Tekst, mida soovid saata." - }, - "sendFileDesc": { - "message": "Fail, mida soovid saata." - }, - "copySendLinkOnSave": { - "message": "Salvestamisel kopeeri Sendi jagamise link lõikepuhvrisse." - }, - "sendLinkLabel": { - "message": "Sendi link", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "Kustutamis- ja aegumiskuupäevade salvestamisel ilmnes tõrge." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "2FA kinnitamiseks kliki alloleval nupul." }, "webAuthnAuthenticate": { "message": "WebAuthn kinnitamine" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "Sinu brauser ei toeta WebAuthn'i." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Viga" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Genereerija", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "Mida sa soovid genereerida?" - }, - "passwordType": { - "message": "Parooli tüüp" - }, - "regenerateUsername": { - "message": "Genereeri kasutajanimi uuesti" - }, "generateUsername": { "message": "Genereeri kasutajanimi" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Kasutajanime tüüp" - }, "plusAddressedEmail": { "message": "Plussiga e-posti aadress", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Kasuta domeenipõhist kogumisaadressi." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Juhuslik", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Hosti nimi", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API võti" - }, "deviceVerification": { "message": "Seadme kinnitamine" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Kasutajate arv" }, - "loggingInAs": { - "message": "Sisselogimas kui" - }, - "notYou": { - "message": "Pole sina?" - }, "pickAnAvatarColor": { "message": "Pick an avatar color" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "No collection" }, - "canView": { - "message": "Can view" - }, - "canViewExceptPass": { - "message": "Can view, except passwords" - }, - "canEdit": { - "message": "Can edit" - }, - "canEditExceptPass": { - "message": "Can edit, except passwords" - }, "noCollectionsAdded": { "message": "No collections added" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domeen" - }, "alreadyHaveAccount": { "message": "Already have an account?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "Sa ei saa eemaldada neid kogumikke ainult vaatamisloaga: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index 4c7719ada13..d0e99c73e66 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Critical applications" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "Zein elementu mota da hau?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Oharrak" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Note" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Arrastatu txukuntzeko" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Testua" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Editatu Karpeta" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "Oinarrizko domeinua", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "adb.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filter" }, - "moveSelectedToOrg": { - "message": "Mugitu hautatutako antolakundera" - }, "deleteSelected": { "message": "Ezabatu hautatutakoa" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Hautatutako elementuak $ORGNAME$-ra mugituak", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "Ez" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "Saioa hasi edo sortu kontu berri bat zure kutxa gotorrera sartzeko." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Saioa hastea martxan da" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Bidali" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Pasahitza gogoratzeko pista" - }, - "enterEmailToGetHint": { - "message": "Sartu zure kontuko emaila pasahitz nagusiaren pista jasotzeko." - }, "getMasterPasswordHint": { "message": "Jaso pasahitz nagusiaren pista" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "Jakinarazpen bat bidali da zure gailura." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "$VERSION_NUMBER$ bertsioa", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Gogora nazazu" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Berbidali email bidezko egiaztatze-kodea" }, "useAnotherTwoStepMethod": { "message": "Erabili bi urratseko saio hasierarako beste modu bat" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Sartu zure YubiKey-a ordenagailuko USB atakan, ondoren, sakatu bere botoia." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Bi urratseko saio hasieraren aukerak" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Bi urratseko egiaztatzeko modu guztietarako sarbidea galdu duzu? Erabili zure berreskuratze-kodea zure kontuko bi urratseko egiaztatze hornitzaile guztiak desaktibatzeko." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(FIDO-tik migratua)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "Emaila" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Aukeratu elementu hau zein erakundetara eraman nahi duzun. Erakunde batera pasatzeak elementuaren jabetza erakunde horretara transferitzen du. Zu ez zara elementu honen jabe zuzena izango mugitzen duzunean." }, - "moveManyToOrgDesc": { - "message": "Aukeratu elementu hauek zein erakundetara eraman nahi dituzun. Erakunde batera pasatzeak elementuen jabetzak erakunde horretara transferitzen ditu. Zu ez zara elementu horien jabe zuzena izango mugitzen dituzunean." - }, "collectionsDesc": { "message": "Aukeratu elementu hau zein bildumarekin partekatzen den. Bilduma horietarako sarbidea duten erakundeko erabiltzaileek bakarrik ikus dezakete elementu hau." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "$COUNT$ artikulu hautatu d(it)uzu. $MOVEABLE_COUNT$ artikulu erakunde batera alda daite(z)ke, $NONMOVEABLE_COUNT$ ezin d(ir)a.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Egiaztatze-kodea (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Berrezarri pasahitza" - }, "length": { "message": "Luzera" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Mesedez, hasi saioa berriro." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Mesedez, hasi saioa berriro. Bitwardenen beste aplikazioren bat erabiltzen ari bazara, itxi saioa eta hasi saioa berriro aplikazio horretan ere." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Eremu arriskutsua" }, - "dangerZoneDesc": { - "message": "Kontuz, ekintza hauek ez dira itzulgarriak!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Baimena kendu saio hasierei" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Jarraitzeak uneko saioa itxiko du eta berriro saioa hasteko eskatuko zaizu. Gaituta badago, berriz ere bi urratseko saioa hasiera eskatuko zaizu. Beste gailu batzuetako saio aktiboek ordubete iraun dezakete aktibo." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "Saio guztiei baimena kendua" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Bi urratseko saio hasiera gaitzeak betirako blokea dezake Bitwarden kontura sartzea. Berreskuratze-kode baten bidez, zure kontura sar zaitezke, bi urratseko saio hasierako hornitzailea erabili ezin baduzu (adb. gailua galtzen baduzu). Bitwarden-ek ezingo dizu lagundu zure konturako sarbidea galtzen baduzu. Berreskuratze-kodea idatzi edo inprimatzea eta leku seguruan edukitzea gomendatzen dugu." }, + "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." + }, "viewRecoveryCode": { "message": "Berreskuratze-kodea" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Kudeatu" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Desgaitu" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Sarbidea ezeztatu" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "Zure kontuan gaituta dago bi urratseko saio hasieraren hornitzaile hori." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "Arazo bat egon da segurtasun-gakoa irakurtzean. Saiatu berriro mesedez." }, - "twoFactorWebAuthnWarning": { - "message": "Plataformaren mugak direla eta, WebAuthn ezin da erabili Bitwarden-en aplikazio guztietan. Bi urratseko saio hasierako beste hornitzaile bat gaitu beharko duzu, WebAuthn erabili ezin denean zure kontuan sartzeko. Plataforma bateragarriak:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Webguneko kutxa gotorra eta nabigatzailearen gehiagarriak mahaigain/ordenagailu eramangarri batean, WebAuthn nabigatzaile gaitu batekin (Chrome, Opera, Vivaldi edo Firefox, FIDO U2F gaitua duena)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Zure Bitwardeneko bi urratseko saio hasierako berreskuratze-kodea" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "Erabiltzaile hau bi urratseko saio hasiera erabiltzen ari da bere kontua babesteko." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "$ID$ erabiltzailearentzako SSO lotura kendua.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Gailua" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "Euskarririk gabeko web nabigatzailea erabiltzen ari zara. Baliteke webguneko kutxa gotorrak behar bezala ez funtzionatzea." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "Bi urratseko saio hasiera bidez zure kontura ezin bazara sartu, bi urratseko saio hasierako berreskuratze kodea erabil dezakezu zure kontuan bi urratseko hornitzaile guztiak desgaitzeko." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Berreskuratu bi urratseko saio hasieradun kontua" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Ezarri pasahitz nagusiaren gutxieneko baldintzak." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Bi urratseko saio hasiera beharrezkoa da" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Ezarri pasahitz sortzailearen gutxieneko baldintzak." }, - "passwordGeneratorPolicyInEffect": { - "message": "Erakundeko politika batek edo gehiagok sortzailearen konfigurazioari eragiten diote." - }, "masterPasswordPolicyInEffect": { "message": "Erakundeko politika batek edo gehiagok pasahitz nagusia behar dute baldintza hauek betetzeko:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Erakundearen jabeak eta administratzaileak politika horretatik salbuetsita daude." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "Fitxategia" }, "sendTypeText": { "message": "Testua" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Sortu Send berria", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Ezabatu Send-a", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Ziur al zaude Send hau ezabatu nahi duzula?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "Zein Send mota da hau?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Ezabatze data" }, - "deletionDateDesc": { - "message": "Send-a betiko ezabatuko da zehaztutako datan eta orduan.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Sarbide kopuru maximoa" }, - "maxAccessCountDesc": { - "message": "Hala ezartzen bada, erabiltzaileak ezin izango dira Send honetara sartu gehienezko sarbide kopurura iritsi ondoren.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Uneko sarbide kopurua" - }, - "sendPasswordDesc": { - "message": "Nahi izanez gero, pasahitza eskatu erabiltzaileak bidalketa honetara sar daitezen.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Send honi buruzko ohar pribatuak.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Desgaitua" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Ziur al zaude pasahitz hau ezabatu nahi duzula?" }, - "hideEmail": { - "message": "Ezkutatu nire emaila hartzaileei." - }, - "disableThisSend": { - "message": "Desgaitu Send hau inor sar ez dadin.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Send guztiak" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Ezabatzea egiteke" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Iraungita" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "Une honetan, erakunderako politika hauek aplikatzen dira:" - }, - "sendDisableHideEmailInEffect": { - "message": "Erabiltzaileek ezin diete hartzaileei beren emaila ezkutatu Send bat sortu edo editatzen dutenean.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "modifiedPolicyId": { "message": "$ID$ politika aldatua.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Erakundearen erabiltzaileentzako jabetza pertsonala desgaitzea" }, - "textHiddenByDefault": { - "message": "Send-era sartzean, ezkutatu testua modu lehenetsian", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Send hau deskribatzeko izena.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Bidali nahi duzun testua." - }, - "sendFileDesc": { - "message": "Bidali nahi duzun fitxategia." - }, - "copySendLinkOnSave": { - "message": "Gordetzean kopiatu Send honen esteka arbelean, ondoren partekatzeko." - }, - "sendLinkLabel": { - "message": "Send esteka", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "Akatsa gertatu da ezabatze eta iraungitze datak gordetzean." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "Zure 2FA egiaztatzeko, klikatu beheko botoian." }, "webAuthnAuthenticate": { "message": "WebAuthn autentifikatu" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn ez da bateragarria nabigatzaile honetan." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Akatsa" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Sortzailea", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "Zer sortu nahi duzu?" - }, - "passwordType": { - "message": "Pasahitz mota" - }, - "regenerateUsername": { - "message": "Berrezarri erabiltzaile izena" - }, "generateUsername": { "message": "Sortu erabiltzaile izena" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Erabiltzaile izen mota" - }, "plusAddressedEmail": { "message": "Atzizkidun emaila", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Erabili zure domeinuan konfiguratutako sarrerako ontzia." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Ausazkoa", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Ostalariaren izena", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "Token sarbide API-a" - }, "deviceVerification": { "message": "Gailu egiaztapena" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Erabiltzaile kopurua" }, - "loggingInAs": { - "message": "Honela hasi saioa" - }, - "notYou": { - "message": "Ez zara zu?" - }, "pickAnAvatarColor": { "message": "Pick an avatar color" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "No collection" }, - "canView": { - "message": "Ikus dezake" - }, - "canViewExceptPass": { - "message": "Ikus dezake, pasahitza izan ezik" - }, - "canEdit": { - "message": "Editatu dezake" - }, - "canEditExceptPass": { - "message": "Editatu dezake, pasahitza izan ezik" - }, "noCollectionsAdded": { "message": "No collections added" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domain" - }, "alreadyHaveAccount": { "message": "Already have an account?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index fe0ba063a7b..04cddc65f2f 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Critical applications" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "این چه نوع موردی است؟" }, @@ -159,6 +201,9 @@ "notes": { "message": "یادداشت‌ها" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Note" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "برای مرتب‌سازی بکشید" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "متن" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "ويرايش پوشه" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "دامنه پایه", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "مثال.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "فیلتر" }, - "moveSelectedToOrg": { - "message": "انتقال مورد انتخاب شده به سازمان" - }, "deleteSelected": { "message": "حذف موارد انتخاب شده" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "موارد انتخاب شده به $ORGNAME$ منتقل شدند", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "خیر" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "وارد شوید یا یک حساب کاربری بسازید تا به گاوصندوق امن‌تان دسترسی یابید." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "ورود به سیستم آغاز شد" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "ثبت" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "یادآور کلمه عبور" - }, - "enterEmailToGetHint": { - "message": "برای دریافت یادآور کلمه عبور اصلی خود نشانی ایمیل‌تان را وارد کنید." - }, "getMasterPasswordHint": { "message": "دریافت یادآور کلمه عبور اصلی" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "یک اعلان به دستگاه شما ارسال شده است." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "نسخه $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "مرا به خاطر بسپار" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "ارسال دوباره ایمیل کد تأیید" }, "useAnotherTwoStepMethod": { "message": "استفاده از روش ورود دو مرحله‌ای دیگر" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "YubiKey خود را وارد پورت USB رایانه کنید، بعد دکمه آن را بفشارید." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "گزینه‌های ورود دو مرحله‌ای" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "دسترسی به تمامی ارائه‌دهندگان دو مرحله‌ای را از دست داده‌اید؟ از کد بازیابی خود برای غیرفعال‌سازی ارائه‌دهندگان دو مرحله‌ای از حسابتان استفاده کنید." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(مهاجرت از FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "ایمیل" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "سازمانی را انتخاب کنید که می‌خواهید این مورد را به آن منتقل کنید. انتقال به یک سازمان، مالکیت مورد را به آن سازمان منتقل می‌کند. پس از انتقال این مورد، دیگر مالک مستقیم آن نخواهید بود." }, - "moveManyToOrgDesc": { - "message": "سازمانی را انتخاب کنید که می‌خواهید این موارد را به آن منتقل کنید. انتقال به یک سازمان، مالکیت موارد را به آن سازمان منتقل می‌کند. پس از انتقال این موارد، دیگر مالک مستقیم آن‌ها نخواهید بود." - }, "collectionsDesc": { "message": "مجموعه‌هایی را ویرایش کنید که این مورد با آن‌ها به اشتراک گذاشته می‌شود. فقط کاربران سازمانی که به این مجموعه‌ها دسترسی دارند می‌توانند این مورد را ببینند." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "شما $COUNT$ مورد را انتخاب کرده اید. $MOVEABLE_COUNT$ مورد را می‌توان به یک سازمان منتقل کرد، $NONMOVEABLE_COUNT$ تا را نمی‌تواند.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "کد تأیید (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "تولید مجدد کلمه عبور" - }, "length": { "message": "طول" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "لطفاً دوباره وارد شوید." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "لطفاً دوباره وارد شوید. اگر از سایر برنامه‌های Bitwarden استفاده می‌کنید، از سیستم خارج شوید و دوباره به آن‌ها وارد شوید." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "منطقه‌ی خطر" }, - "dangerZoneDesc": { - "message": "مراقب باشید، این اقدامات قابل برگشت نیستند!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "لغو مجوز نشست‌ها" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "ادامه دادن همچنین شما را از نشست فعلی خود خارج می‌کند و باید دوباره وارد سیستم شوید. در صورت راه اندازی مجدداً از شما خواسته می‌شود تا دوباره به سیستم دو مرحله ای بپردازید. جلسات فعال در دستگاه‌های دیگر ممکن است تا یک ساعت فعال بمانند." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "همه نشست‌ها غیرمجاز است" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "راه‌اندازی ورود دو مرحله‌ای می‌تواند برای همیشه حساب Bitwarden شما را قفل کند. یک کد بازیابی به شما امکان می‌دهد در صورتی که دیگر نمی‌توانید از ارائه‌دهنده‌ی ورود دو مرحله‌ای معمولی خود استفاده کنید (به عنوان مثال: دستگاه خود را گم می‌کنید) به حساب خود دسترسی پیدا کنید. اگر دسترسی به حساب خود را از دست بدهید، پشتیبانی Bitwarden نمی‌تواند به شما کمک کند. توصیه می‌کنیم کد بازیابی را یادداشت یا چاپ کنید و آن را در مکانی امن نگهداری کنید." }, + "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." + }, "viewRecoveryCode": { "message": "نمایش کد بازیابی" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "مدیریت" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "خاموش کردن" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "لغو دسترسی" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "این ارائه دهنده ورود به سیستم دو مرحله ای در حساب شما فعال است." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "مشکلی در خواندن کلید امنیتی وجود داشت. دوباره امتحان کنید." }, - "twoFactorWebAuthnWarning": { - "message": "به دلیل محدودیت‌های پلتفرم، WebAuthn نمی‌تواند در همه برنامه‌های Bitwarden استفاده شود. باید یک ارائه‌دهنده‌ی ورود دو مرحله‌ای دیگر راه‌اندازی کنید تا زمانی که WebAuthn قابل استفاده نیست، بتوانید به حساب خود دسترسی داشته باشید. پلتفرم های پشتیبانی شده:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "گاوصندوق وب و برنامه‌های افزودنی مرورگر روی دسکتاپ/لپ‌تاپ با مرورگر پشتیبانی‌شده از WebAuthn (Chrome، Opera، Vivaldi یا Firefox با FIDO U2F روشن)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "کد بازیابی ورود دو مرحله ای Bitwarden شما" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "این کاربر از ورود دو مرحله ای برای محافظت از حساب خود استفاده می‌کند." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "SSO برای کاربر $ID$ پیوند نخورده است.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "دستگاه" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "شما از یک مرورگر وب پشتیبانی نشده استفاده می‌کنید. گاوصندوق وب ممکن است به درستی کار نکند." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "اگر نمی‌توانید از طریق روش‌های ورود دو مرحله‌ای معمولی به حساب خود دسترسی پیدا کنید، می‌توانید از کد بازیابی ورود به سیستم دو مرحله‌ای خود برای خاموش کردن همه ارائه‌دهندگان دو مرحله‌ای در حساب خود استفاده کنید." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "بازیابی حساب ورود دو مرحله ای" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "الزامات را برای قدرت کلمه عبور اصلی تنظیم کنید." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "فعال کردن ورود دو مرحله ای الزامیست" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "الزامات را برای تولید کننده کلمه عبور تنظیم کنید." }, - "passwordGeneratorPolicyInEffect": { - "message": "یک یا چند سیاست سازمان بر تنظیمات تولید کننده شما تأثیر می‌گذارد." - }, "masterPasswordPolicyInEffect": { "message": "یک یا چند سیاست سازمانی برای تأمین شرایط زیر به کلمه عبور اصلی شما احتیاج دارد:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "مالکان و سرپرستان سازمان از اجرای این سیاست مستثنی هستند." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "پرونده" }, "sendTypeText": { "message": "متن" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "ارسال جدید", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "حذف ارسال", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "آیا مطمئن هستید که می‌خواهید این ارسال را حذف کنید؟", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "این چه نوع ارسالی است؟", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "تاریخ حذف" }, - "deletionDateDesc": { - "message": "ارسال در تاریخ و ساعت مشخص شده برای همیشه حذف خواهد شد.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "تعداد دسترسی حداکثر" }, - "maxAccessCountDesc": { - "message": "در صورت تنظیم، با رسیدن به حداکثر تعداد دسترسی، کاربران دیگر نمی‌توانند به این ارسال دسترسی پیدا کنند.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "تعداد دسترسی فعلی" - }, - "sendPasswordDesc": { - "message": "به صورت اختیاری برای دسترسی کاربران به این ارسال به یک کلمه عبور نیاز دارید.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "یادداشت های خصوصی در مورد این ارسال.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "غیرفعال شد" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "مطمئنید که می‌خواهید کلمه عبور حذف شود؟" }, - "hideEmail": { - "message": "نشانی ایمیلم را از گیرندگان مخفی کن." - }, - "disableThisSend": { - "message": "این ارسال را غیرفعال کنید تا کسی نتواند به آن دسترسی پیدا کند.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "همه ارسال ها" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "در انتظار حذف" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "منقضی شده" }, @@ -5161,13 +5441,6 @@ "message": "هنگام ایجاد یا ویرایش ارسال، همیشه نشانی ایمیل اعضا را به گیرندگان نشان بده.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendOptionsPolicyInEffect": { - "message": "سیاست‌های سازمان زیر در حال حاضر در حال اجرا هستند:" - }, - "sendDisableHideEmailInEffect": { - "message": "کاربران مجاز به مخفی کردن نشانی ایمیل خود در هنگام ایجاد یا ویرایش ارسال از گیرندگان نیستند.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "modifiedPolicyId": { "message": "سیاست تغییر یافته $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "حذف مالکیت فردی برای کاربران سازمان" }, - "textHiddenByDefault": { - "message": "هنگام دسترسی به ارسال، متن را به طور پیش فرض پنهان کن", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "یک نام دوستانه برای توصیف این ارسال.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "متنی که می‌خواهید ارسال کنید." - }, - "sendFileDesc": { - "message": "پرونده ای که می‌خواهید ارسال کنید." - }, - "copySendLinkOnSave": { - "message": "این پیوند را برای به اشتراک گذاری ارسال بعد از ارسال کپی کن." - }, - "sendLinkLabel": { - "message": "ارسال پیوند", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "ارسال", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "هنگام ذخیره حذف و تاریخ انقضاء شما خطایی روی داد." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "برای تأیید 2FA خود لطفاً روی دکمه زیر کلیک کنید." }, "webAuthnAuthenticate": { "message": "تأیید اعتبار در WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn در این مرورگر پشتیبانی نمی‌شود." }, @@ -5655,6 +5916,20 @@ "error": { "message": "خطا" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "مدیر کاربران همچنین باید مجوز مدیریت بازیابی حساب کاربری را داشته باشد" }, @@ -6516,15 +6791,6 @@ "message": "تولید کننده", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "چه چیزی دوست دارید تولید کنید؟" - }, - "passwordType": { - "message": "نوع کلمه عبور" - }, - "regenerateUsername": { - "message": "ایجاد مجدد نام کاربری" - }, "generateUsername": { "message": "ایجاد نام کاربری" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "نوع نام کاربری" - }, "plusAddressedEmail": { "message": "به علاوه نشانی ایمیل داده شده", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "از صندوق ورودی پیکربندی شده دامنه خود استفاده کنید." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "تصادفی", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "نام میزبان", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "توکن دسترسی API" - }, "deviceVerification": { "message": "تأیید دستگاه" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "تعداد کاربران" }, - "loggingInAs": { - "message": "در حال ورود به عنوان" - }, - "notYou": { - "message": "شما نیستید؟" - }, "pickAnAvatarColor": { "message": "یک رنگ آواتار را انتخاب کنید" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "مجموعه ای وجود ندارد" }, - "canView": { - "message": "می‌تواند مشاهده کند" - }, - "canViewExceptPass": { - "message": "قابل مشاهده، به غیر از کلمه‌های عبور" - }, - "canEdit": { - "message": "می‌تواند ویرایش کند" - }, - "canEditExceptPass": { - "message": "قابل ویرایش، به غیر از کلمه‌های عبور" - }, "noCollectionsAdded": { "message": "مجموعه ای اضافه نشد" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "درخواست تأیید مدیر" }, - "approveWithMasterPassword": { - "message": "تأیید با کلمه عبور اصلی" - }, "trustedDeviceEncryption": { "message": "رمزگذاری دستگاه مورد اعتماد" }, "trustedDevices": { "message": "دستگاه‌های مورد اعتماد" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "پس از احراز هویت، اعضا با استفاده از کلید ذخیره شده در دستگاه خود، داده‌های گاوصندوق را رمزگشایی می‌کنند.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "سازمان واحد", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "single organization", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": "سیاست‌ها", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "policy,", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "SSO الزامی است", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO required", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "سیاست‌ها و", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "policy, and", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "مدیریت بازیابی حساب کاربری", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "account recovery administration", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "سیاست‌ها با ثبت نام خودکار زمانی که از این گزینه استفاده می‌شود روشن می‌شود.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "درخواست تأیید" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "بدون درخواست دستگاه" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "درخواست شما به مدیرتان فرستاده شد." }, - "youWillBeNotifiedOnceApproved": { - "message": "به محض تأیید مطلع خواهید شد." - }, "troubleLoggingIn": { "message": "در ورود مشکلی دارید؟" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "دامنه مستعار" - }, "alreadyHaveAccount": { "message": "پیشتر حساب کاربری داشته اید؟" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index 234cfb0ee3e..9427a4e8e22 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Kriittiset sovellukset" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "Riskialttiit jäsenet" }, + "atRiskMembersWithCount": { + "message": "Vaarantuneet jäsenet ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Vaarantuneet sovellukset ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Jäseniä yhteensä" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Sovelluksia yhteensä" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "Minkä tyyppinen kohde tämä on?" }, @@ -154,11 +196,14 @@ "message": "Uusi salasana" }, "passphrase": { - "message": "Salauslauseke" + "message": "Salauslause" }, "notes": { "message": "Merkinnät" }, + "privateNote": { + "message": "Yksityinen muistiinpano" + }, "note": { "message": "Muistiinpano" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Järjestele raahaamalla" }, + "dragToReorder": { + "message": "Järjestä vetämällä" + }, "cfTypeText": { "message": "Teksti" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Muokkaa kansiota" }, + "editWithName": { + "message": "Muokkaa $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "Uusi kansio" + }, + "folderName": { + "message": "Kansion nimi" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Haluatko varmasti poistaa tämän kansion pysyvästi?" + }, "baseDomain": { "message": "Pääverkkotunnus", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Kohteen nimi" }, - "cannotRemoveViewOnlyCollections": { - "message": "Et voi poistaa kokoelmia, joihin sinulla on vain tarkasteluoikeus: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "esim.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Suodattimet" }, - "moveSelectedToOrg": { - "message": "Siirrä valitut organisaatiolle" - }, "deleteSelected": { "message": "Poista valitut" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Valitut kohteet siirrettiin organisaatiolle $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Kohteet siirrettiin organisaatiolle $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "Ei" }, + "location": { + "message": "Sijainti" + }, "loginOrCreateNewAccount": { "message": "Käytä salattua holviasi kirjautumalla sisään tai luo uusi tili." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Kirjaudu Bitwardeniin" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Tunnistaudu koskettamalla YubiKeytäsi" + }, "authenticationTimeout": { "message": "Todennuksen aikakatkaisu" }, "authenticationSessionTimedOut": { "message": "Todennusistunto aikakatkaistiin. Ole hyvä ja aloita kirjautumisprosessi uudelleen." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Vahvista henkilöllisyytesi" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Jatka kirjautumista" + }, + "whatIsADevice": { + "message": "Mikä on laite?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Kirjautuminen aloitettu" }, + "logInRequestSent": { + "message": "Pyyntö lähetetty" + }, "submit": { "message": "Jatka" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Syötä tilisi sähköpostiosoite, niin salasanavihjeesi lähetetään sinulle sähköpostitse" }, - "passwordHint": { - "message": "Salasanavihje" - }, - "enterEmailToGetHint": { - "message": "Syötä tilisi sähköpostiosoite saadaksesi pääsalasanan vihjeen." - }, "getMasterPasswordHint": { "message": "Pyydä pääsalasanan vihjettä" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "Laitteellesi on lähetetty ilmoitus." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Ennen hyväksyntää varmista, että tunnistelause vastaa alla olevaa lausetta." + }, + "notificationSentDeviceComplete": { + "message": "Avaa Bitwarden laitteellasi. Ennen hyväksyntää varmista, että tunnistelause vastaa alla olevaa lausetta." + }, "aNotificationWasSentToYourDevice": { "message": "Laitteeseesi lähetettiin ilmoitus" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "Versio $VERSION_NUMBER$", "placeholders": { @@ -1359,14 +1459,24 @@ "rememberMe": { "message": "Muista minut" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Älä kysy uudelleen tällä laitteella 30 päivään" + }, "sendVerificationCodeEmailAgain": { "message": "Lähetä todennuskoodi sähköpostitse uudelleen" }, "useAnotherTwoStepMethod": { "message": "Käytä vaihtoehtoista todennustapaa" }, + "selectAnotherMethod": { + "message": "Valitse vaihtoehtoinen tapa", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Käytä palautuskoodiasi" + }, "insertYubiKey": { - "message": "Kytke YubiKey-todennuslaitteesi tietokoneen USB-porttiin ja paina sen painiketta." + "message": "Kytke YubiKey-suojausavaimesi tietokoneen USB-porttiin ja kosketa sen painiketta." }, "insertU2f": { "message": "Kytke suojausavaimesi tietokoneen USB-porttiin ja jos laitteessa on painike, paina sitä." @@ -1383,8 +1493,11 @@ "twoStepOptions": { "message": "Kaksivaiheisen kirjautumisen asetukset" }, + "selectTwoStepLoginMethod": { + "message": "Valitse todennustapa" + }, "recoveryCodeDesc": { - "message": "Etkö voi käyttää kaksivaiheisen kirjautumisen todentajiasi? Poista kaikki määritetyt todentajat käytöstä palautuskoodillasi." + "message": "Etkö voi käyttää kaksivaiheisen kirjautumisen todentajiasi? Poista kaikki tilillesi määritetyt todentajat käytöstä palautuskoodillasi." }, "recoveryCodeTitle": { "message": "Palautuskoodi" @@ -1400,7 +1513,7 @@ "message": "Yubico OTP -suojausavain" }, "yubiKeyDesc": { - "message": "Vahvista kirjatuminen YubiKey-todennuslaiteella. Toimii YubiKey 4 ja 5 -sarjojen sekä NEO -laitteiden kanssa." + "message": "Vahvista kirjatuminen YubiKey-suojausavaimella. Toimii YubiKey 4, 5 ja NEO -laitteiden kanssa." }, "duoDescV2": { "message": "Syötä Duo Securityn luoma koodi.", @@ -1414,7 +1527,7 @@ "message": "Vahvista kirjautuminen FIDO U2F -suojausavaimella." }, "u2fTitle": { - "message": "FIDO U2F ‑todennuslaite" + "message": "FIDO U2F ‑suojausavain" }, "webAuthnTitle": { "message": "Pääsyavain" @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(siirretty FIDO:sta)" }, + "openInNewTab": { + "message": "Avaa uudessa välilehdessä" + }, "emailTitle": { "message": "Sähköposti" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Valitse organisaatio, jolle haluat siirtää kohteen. Tämä siirtää kohteen organisaation omistukseen, etkä tämän jälkeen ole enää sen suora omistaja." }, - "moveManyToOrgDesc": { - "message": "Valitse organisaatio, jolle haluat siirtää kohteet. Tämä siirtää kohteet organisaation omistukseen, etkä tämän jälkeen ole enää niiden suora omistaja." - }, "collectionsDesc": { "message": "Muokkaa kokoelmia, joihin tämä kohde on jaettu. Kohteen näkevät vain ne organisaation käyttäjät, joilla on käyttöoikeudet näihin kokoelmiin." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "Olet valinnut $COUNT$ kohdetta. $MOVEABLE_COUNT$ kohdetta voidaan siirtää organisaatioon, $NONMOVEABLE_COUNT$ ei.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Todennuskoodi (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Vältä epäselviä merkkejä", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Luo uusi salasana" - }, "length": { "message": "Pituus" }, @@ -1689,7 +1782,7 @@ "message": "Vaihda sähköpostiosoite" }, "changeEmailTwoFactorWarning": { - "message": "Jatkamalla tilisi sähköpostiosoite vaihtuu, muttei kaksivaiheiseen kirjautumiseen käytettävä osoite. Voit vaihtaa sen kaksivaiheisen kirjautumisen asetuksista." + "message": "Jos jatkat, tilisi sähköpostiosoite vaihtuu, muttei kaksivaiheiseen kirjautumiseen käytettävä osoite. Tämän osoitteen voit vaihtaa kaksivaiheisen kirjautumisen asetuksista." }, "newEmail": { "message": "Uusi sähköpostiosoite" @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Kirjaudu sisään uudelleen." }, + "currentSession": { + "message": "Nykyinen istunto" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Kirjaudu uudelleen sisään. Jos käytät muita Bitwarden-sovelluksia, kirjaudu myös niihin uudelleen." }, @@ -1782,20 +1881,35 @@ "dangerZone": { "message": "Vaaravyöhyke" }, - "dangerZoneDesc": { - "message": "Ole varovainen! Näitä toimintoja ei ole mahdollista kumota!" - }, - "dangerZoneDescSingular": { - "message": "Ole varoivainen. Tätä ei ole mahdollista perua!" - }, "deauthorizeSessions": { "message": "Mitätöi kaikki istunnot" }, "deauthorizeSessionsDesc": { - "message": "Oletko huolissasi, että tilisi on kirjautuneena muissa laitteissa? Jatka alla kirjataksesi ulos kaikki aiemmin käyttämäsi tietokoneet ja muut laitteet. Tämä suojaustoimenpide on suositeltava, jos olet aiemmin käyttänyt esimerkiksi julkista tietokonetta tai vahingossa tallentanut salasanasi laitteelle, joka ei ole sinun. Tämä mitätöi myös kaikki aiemmin muistetut kaksivaiheiset kirjautumiset." + "message": "Oletko huolissasi, että tilisi on kirjautuneena muila laitteilla? Alta voit kirjata ulos kaikki aiemmin käyttämäsi tietokoneet ja muut laitteet. Tämä suojaustoimenpide on suositeltava, jos olet aiemmin käyttänyt esimerkiksi julkista tietokonetta tai vahingossa tallentanut salasanasi laitteelle, joka ei ole sinun. Tämä mitätöi myös kaikki aiemmin muistetut kaksivaiheiset kirjautumiset." }, "deauthorizeSessionsWarning": { - "message": "Jatkaminen uloskirjaa myös nykyisen istunnon pakottaen uudelleenkirjautumisen sekä kaksivaiheinen kirjautumisen, jos se on määritetty. Muiden laitteiden aktiiviset istunnot saattavat toimia vielä tunnin ajan." + "message": "Jatkaminen uloskirjaa myös nykyisen istunnon pakottaen uudelleenkirjautumisen sekä kaksivaiheinen kirjautumisen, jos määritetty. Muiden laitteiden aktiiviset istunnot saattavat toimia vielä tunnin ajan." + }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "Uuden laitteen sisäänkirjautumissuojan ollessa pois käytöstä kuka tahansa pääsalasanasi haltija voi käyttää tiliäsi millä tahansa laitteella. Suojaa tilsi ilman vahvistussähköposteja määrittämällä kaksivaiheinen kirjautuminen." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" }, "sessionsDeauthorized": { "message": "Kaikki istunnot mitätöitiin" @@ -2036,7 +2150,7 @@ "message": "Kaksivaiheinen kirjautuminen" }, "twoStepLoginEnforcement": { - "message": "Kaksivaiheisen kirjautumisen pakotus" + "message": "Kaksivaiheisen kirjautumisen pakottaminen" }, "twoStepLoginDesc": { "message": "Suojaa tilisi vaatimalla sisäänkirjautumiseen toinen todennusvaihe." @@ -2045,7 +2159,7 @@ "message": "Ota kaksivaiheinen kirjautuminen käyttöön organisaatiollesi." }, "twoStepLoginEnterpriseDescStart": { - "message": "Pakota jäseniä määrittämään Bitwardenin kaksivaiheinen kirjautuminen käytännöllä: ", + "message": "Pakota jäsenet määrittämään Bitwardenin kaksivaiheinen kirjautuminen käytännöllä: ", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Enforce Bitwarden Two-step Login options for members by using the Two-step Login Policy.'" }, "twoStepLoginPolicy": { @@ -2058,7 +2172,10 @@ "message": "Jos olet määrittänyt kertakirjautumisen tai aiot määrittää sen, on kaksivaiheinen kirjautuminen saatettu jo pakottaa identiteettitoimittajasi kautta." }, "twoStepLoginRecoveryWarning": { - "message": "Kaksivaiheisen kirjautumisen käyttöönotto voi lukita sinut ulos Bitwarden-tililtäsi pysyvästi. Palautuskoodi mahdollistaa pääsyn tilillesi myös silloin, kun et voi käyttää normaaleja kaksivaiheisen kirjautumisen todentajiasi (esim. kadotat todennuslaitteesi tai se varastetaan). Bitwardenin asiakaspalvelukaan ei voi auttaa sinua, jos menetät pääsyn tillesi. Suosittelemme, että kirjoitat palautuskoodin muistiin tai tulostat sen ja säilytät sitä turvallisessa paikassa (esim. kassakaapissa tai pankin tallelokerossa)." + "message": "Kaksivaiheisen kirjautumisen käyttöönotto voi lukita sinut ulos Bitwarden-tililtäsi pysyvästi. Palautuskoodi mahdollistaa pääsyn tilillesi myös silloin, kun et voi käyttää normaaleja kaksivaiheisen tunnistautumisen vahvistustapoja (esim. kadotat suojausavaimesi tai se varastetaan). Bitwardenin asiakaspalvelukaan ei voi auttaa sinua, jos menetät pääsyn tillesi. Suosittelemme, että kirjoitat palautuskoodin muistiin tai tulostat sen ja säilytät turvallisessa paikassa (esim. kassakaapissa tai pankin tallelokerossa)." + }, + "yourSingleUseRecoveryCode": { + "message": "Kertakäyttöisellä palautuskoodillasi voit poistaa kaksivaiheisen kirjautumisen käytöstä, mikäli et voi käyttää kaksivaiheista todennustapaasi. Bitwarden suosittelee kirjoittamaan palautuskoodin ylös ja säilyttämään sen turvallisessa paikassa." }, "viewRecoveryCode": { "message": "Näytä palautuskoodi" @@ -2098,8 +2215,20 @@ "manage": { "message": "Hallitse" }, - "canManage": { - "message": "Voi hallita" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "Näytä kohteet" + }, + "viewItemsHidePass": { + "message": "Näytä kohteet, piilotetut salasanat" + }, + "editItems": { + "message": "Muokkaa kohteita" + }, + "editItemsHidePass": { + "message": "Muokkaa kohteita, piilotettuja salasanoja" }, "disable": { "message": "Poista käytöstä" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Mitätöi käyttöoikeudet" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "Tämä kaksivaiheisen kirjautumisen todentaja on määritetty tilillesi." }, @@ -2153,7 +2285,7 @@ "message": "Avain" }, "twoStepAuthenticatorEnterCodeV2": { - "message": "Todennuskoodi" + "message": "Vahvistuskoodi" }, "twoStepAuthenticatorReaddDesc": { "message": "Jos sinun on lisättävä tai siirrettävä todennus toiseen laitteeseen, löydät alta todennussovelluksesi tarvitseman QR-koodin (tai avaimen)." @@ -2162,31 +2294,31 @@ "message": "Haluatko varmasti poistaa tämän kaksivaiheisen kirjautumisen todentajan käytöstä?" }, "twoStepDisabled": { - "message": "Kaksivaiheisen kirjautumisen todentaja on poistettu käytöstä." + "message": "Kaksivaiheisen kirjautumisen todentaja poistettiin käytöstä." }, "twoFactorYubikeyAdd": { - "message": "Lisää tilillesi YubiKey-todennuslaite" + "message": "Lisää tilillesi uusi YubiKey-suojausavain" }, "twoFactorYubikeyPlugIn": { - "message": "Kytke YubiKey-todennuslaitteesi tietokoneesi USB-porttiin." + "message": "Kytke YubiKey tietokoneesi USB-porttiin." }, "twoFactorYubikeySelectKey": { "message": "Valitse alta ensimmäinen tyhjä YubiKey-syöttökenttä." }, "twoFactorYubikeyTouchButton": { - "message": "Paina YubiKey-todennuslaitteen painiketta." + "message": "Kosketa YubiKeyn painiketta." }, "twoFactorYubikeySaveForm": { "message": "Tallenna lomake." }, "twoFactorYubikeyWarning": { - "message": "Alustakohtaisten rajoitusten vuoksi YubiKey-todennuslaiteet eivät ole käytettävissä kaikissa Bitwarden-sovelluksissa. Sinun tulisi määrittää eri kaksivaiheisen kirjautumisen todentaja, jotta pääset tilillesi myös silloin kun YubiKey-laitteen käyttö ei ole mahdollista. Tuetut alustat:" + "message": "Alustakohtaisten rajoitusten vuoksi YubiKey-avaimet eivät ole käytettävissä kaikissa Bitwarden-sovelluksissa ja sinun tulisi määrittää vaihtoehtoinen kaksivaiheisen tunnistautumisen vahvistustapa, jotta pääset tilillesi myös silloin kun YubiKey-avaimesi ei ole käytettävissä. Tuetut alustat:" }, "twoFactorYubikeySupportUsb": { - "message": "Verkkoholvi, työpöytäsovellus, CLI ja kaikki selainlaajennukset laitteessa, jossa on YubiKey-todennuslaitteen käyttöön soveltuva USB-portti." + "message": "Verkkoholvi, työpöytäsovellus, CLI ja kaikki selainlaajennukset laitteessa, jossa on YubiKeyn käyttöön soveltuva USB-portti." }, "twoFactorYubikeySupportMobile": { - "message": "Mobiilisovellukset laitteessa, jossa on NFC-ominaisuus tai YubiKey-todennuslaitteen kanssa yhteensopiva tietoliikenneportti." + "message": "Mobiilisovellukset laitteessa, jossa on NFC-ominaisuus tai YubiKey-yhteensopiva tiedonsiirtoliitäntä." }, "yubikeyX": { "message": "YubiKey $INDEX$", @@ -2207,7 +2339,7 @@ } }, "webAuthnkeyX": { - "message": "WebAuthn-todennuslaite $INDEX$", + "message": "WebAuthn-suojausavain $INDEX$", "placeholders": { "index": { "content": "$1", @@ -2219,16 +2351,16 @@ "message": "NFC-tuki" }, "twoFactorYubikeySupportsNfc": { - "message": "Jokin laitteeni tukee NFC-tekniikkaa." + "message": "Minulla on NFC-tekniikkaa tukeva avain." }, "twoFactorYubikeySupportsNfcDesc": { - "message": "Jos jokin YubiKey-todennuslaitteesi tukee NFC-tekniikkaa (kuten YubiKey NEO), näytetään mobiililaitteissa kehote NFC:n ollessa käytettävissä." + "message": "Jos jokin YubiKey-avaimesi tukee NFC-tekniikkaa (kuten YubiKey NEO), näytetään mobiililaitteissa kehote NFC:n ollessa käytettävissä." }, "yubikeysUpdated": { - "message": "YubiKey-todennuslaitteet päivitettiin" + "message": "YubiKeyt päivitettiin" }, "disableAllKeys": { - "message": "Poista kaikki todennuslaitteet käytöstä" + "message": "Poista kaikki avaimet käytöstä" }, "twoFactorDuoDesc": { "message": "Syötä Bitwarden-sovelluksen tiedot Duo Security -tilisi hallintapaneelista." @@ -2282,7 +2414,7 @@ "message": "Tallenna lomake." }, "twoFactorU2fWarning": { - "message": "Alustakohtaisten rajoitusten vuoksi FIDO U2F -todennuslaiteet eivät ole käytettävissä kaikissa Bitwarden-sovelluksissa. Sinun tulisi määrittää eri kaksivaiheisen kirjautumisen todentaja, jotta pääset tilillesi myös silloin kun FIDO U2F -laitteen käyttö ei ole mahdollista. Tuetut alustat:" + "message": "Alustakohtaisten rajoitusten vuoksi FIDO U2F -avaimet eivät ole käytettävissä kaikissa Bitwarden-sovelluksissa ja sinun tulisi määrittää vaihtoehtoinen kaksivaiheisen tunnistautumisen vahvistustapa, jotta pääset tilillesi myös silloin kun FIDO U2F -laitteesi ei ole käytettävissä. Tuetut alustat:" }, "twoFactorU2fSupportWeb": { "message": "Verkkoholvi ja selainlaajennukset pöytäkoneissa/kannettavissa, joissa on U2F-tekniikkaa tukeva selain (Chrome, Opera, Vivaldi tai Firefox FIDO U2F käyttöön otettuna)." @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "Suojausavainta luettaessa havaittiin ongelma. Yritä uudelleen." }, - "twoFactorWebAuthnWarning": { - "message": "Alustakohtaisten rajoitusten vuoksi WebAuthn-todennuslaiteet eivät ole käytettävissä kaikissa Bitwarden-sovelluksissa. Sinun tulisi määrittää eri kaksivaiheisen kirjautumisen todentaja, jotta pääset tilillesi myös silloin kun WebAuthn-laitteen käyttö ei ole mahdollista. Tuetut alustat:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Verkkoholvi ja selainlaajennukset pöytäkoneissa/kannettavissa, joissa on WebAuthn-tekniikkaa tukeva selain (Chrome, Opera, Vivaldi tai Firefox FIDO U2F käyttöön otettuna)." + "twoFactorWebAuthnWarning1": { + "message": "Alustakohtaisten rajoitusten vuoksi WebAuthn-avaimet eivät ole käytettävissä kaikissa Bitwarden-sovelluksissa ja sinun tulisi määrittää vaihtoehtoinen kaksivaiheisen tunnistautumisen vahvistustapa, jotta pääset tilillesi myös silloin kun WebAuthn-laitteesi ei ole käytettävissä." }, "twoFactorRecoveryYourCode": { "message": "Bitwardenin kaksivaiheisen kirjautumisen palautuskoodisi" @@ -2588,7 +2717,7 @@ "message": "1 Gt salattua tallennustilaa tiedostoliitteille." }, "premiumSignUpTwoStepOptions": { - "message": "Omisteiset kaksivaiheisen kirjautumisen vaihtoehdot, kuten YubiKey ja Duo." + "message": "Kaksivaiheisen kirjautumisen erikoisvaihtoehdot, kuten YubiKey ja Duo." }, "premiumSignUpEmergency": { "message": "Varmuuskäyttö" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Sinulla on 1 kutsu jäljellä." + }, + "inviteZeroEmailDesc": { + "message": "Sinulla on 0 kutsua jäljellä." + }, "userUsingTwoStep": { "message": "Käyttäjä on suojannut tilinsä kaksivaiheisella kirjautumisella." }, @@ -3369,7 +3504,7 @@ "message": "Sisäänkirjautumisyritys epäonnistui väärän salasanan vuoksi." }, "failedLogin2fa": { - "message": "Kirjautuminen epäonnistui virheellisen toisen vaiheen vahvistuksen vuoksi." + "message": "Kirjautuminen epäonnistui virheellisen todennuksen vuoksi." }, "incorrectPassword": { "message": "Virheellinen salasana" @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Poisti kertakirjautumisen käyttäjältä \"$ID$\".", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Laite" }, + "loginStatus": { + "message": "Kirjautumisen tila" + }, + "firstLogin": { + "message": "Ensimmäinen kirjautuminen" + }, + "trusted": { + "message": "Luotettu" + }, + "needsApproval": { + "message": "Vaatii hyväksynnän" + }, + "areYouTryingtoLogin": { + "message": "Yritätkö kirjautua sisään?" + }, + "logInAttemptBy": { + "message": "Kirjautumisyritys osoitteella $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Laitteen tyyppi" + }, + "ipAddress": { + "message": "IP-osoite" + }, + "confirmLogIn": { + "message": "Vahvista kirjautuminen" + }, + "denyLogIn": { + "message": "Hylkää kirjautuminen" + }, + "thisRequestIsNoLongerValid": { + "message": "Tämä pyyntö ei ole enää voimassa." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Kirjautumispyyntö on jo erääntynyt." + }, + "justNow": { + "message": "Juuri nyt" + }, + "requestedXMinutesAgo": { + "message": "Pyydetty $MINUTES$ minuuttia sitten", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Luodaan tili palvelimelle" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "Käytät selainta, jota ei tueta. Verkkoholvi ei välttämättä toimi oikein." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Tarkastele kirjautumispyyntöä" + }, "freeTrialEndPromptCount": { "message": "Ilmainen kokeilujakso päättyy $COUNT$ päivän kuluttua.", "placeholders": { @@ -3935,7 +4149,7 @@ "message": "Ilmainen kokeilujaksosi päättyy tänään." }, "clickHereToAddPaymentMethod": { - "message": "Click here to add a payment method." + "message": "Klikkaa tästä lisätäksesi maksutavan." }, "joinOrganization": { "message": "Liity organisaatioon" @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "Jos et pääse tilillesi käyttämilläsi kaksivaiheisen kirjautumisen todentajilla, voit kaksivaiheisen kirjautumisen palautuskoodillasi poistaa kaikki tilillesi määritetyt todentajat käytöstä." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Kirjaudu kertakäyttöisellä palautuskoodillasi alla. Tämä poistaa tilisi kaikki kaksivaiheiset kirjautumistavat käytöstä." + }, "recoverAccountTwoStep": { "message": "Vapauta tili kaksivaiheisesta kirjautumisesta" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Salausavaimen päivitystä ei voida jatkaa" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "Kansioidesi salausta ei voitu purkaa salausavaimesi päivityksen aikana. Jatkaaksesi päivitystä kansiosi on poistettava. Holvin kohteita ei poisteta, jos jatkat." }, @@ -4474,21 +4743,21 @@ "message": "Holvissasi on vanhoja tiedostoliitteitä, jotka on korjattava ennen kuin voit uudistaa tilisi salausavaimen." }, "yourAccountsFingerprint": { - "message": "Tilisi tunnistelauseke", + "message": "Tilisi tunnistelause", "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." }, "fingerprintEnsureIntegrityVerify": { - "message": "Varmistaaksesi salausavaintesi eheyden, vahvista käyttäjän tunnistelauseke ennen kuin jatkat.", + "message": "Varmistaaksesi salausavaintesi eheyden, vahvista käyttäjän tunnistelause ennen kuin jatkat.", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "fingerprintMatchInfo": { - "message": "Varmista, että vahvistavan laitteen holvi on avattu ja että se näyttää saman tunnistelausekkeen." + "message": "Varmista, että vahvistavan laitteen holvi on avattu ja että se näyttää saman tunnistelauseen." }, "fingerprintPhraseHeader": { - "message": "Tunnistelauseke" + "message": "Tunnistelause" }, "dontAskFingerprintAgain": { - "message": "Älä koskaan kehota vahvistamaan kutsuttujen käyttäjien tunnistelausekkeita (ei suositella)", + "message": "Älä koskaan kehota vahvistamaan kutsuttujen käyttäjien tunnistelauseita (ei suositella)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "youWillBeNotifiedOnceTheRequestIsApproved": { @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Aseta pääsalasanan vahvuusvaatimukset." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Vaadi kaksivaiheinen kirjautuminen" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Aseta salasanageneraattorin vaatimukset." }, - "passwordGeneratorPolicyInEffect": { - "message": "Yksi tai useampi organisaatiokäytäntö vaikuttaa generaattorisi asetuksiin." - }, "masterPasswordPolicyInEffect": { "message": "Yksi tai useampi organisaatiokäytäntö edellyttää, että pääsalasanasi täyttää seuraavat vaatimukset:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Organisaation omistajat ja ylläpitäjät on vapautettu tämän käytännön piiristä." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Sendin tiedot", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Jaettava teksti" + }, "sendTypeFile": { "message": "Tiedosto" }, "sendTypeText": { "message": "Teksti" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Uusi Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Poista Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Haluatko varmasti poistaa Sendin?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "Minkä tyyppinen Send tämä on?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Poistoajankohta" }, - "deletionDateDesc": { - "message": "Send poistuu pysyvästi määritettynä ajankohtana.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Käyttökertojen enimmäismäärä" }, - "maxAccessCountDesc": { - "message": "Jos määritetty, käyttäjät eivät voi avata Sendiä käyttökertojen enimmäismäärän täytyttyä.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Käyttökertojen nykyinen määrä" - }, - "sendPasswordDesc": { - "message": "Halutessasi, vaadi käyttäjiä syöttämään salasana Sendin avaamiseksi.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Yksityisiä merkintöjä tästä Sendistä.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Poistettu käytöstä" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Haluatko varmasti poistaa salasanan?" }, - "hideEmail": { - "message": "Piilota sähköpostiosoitteeni vastaanottajilta." - }, - "disableThisSend": { - "message": "Poista Send käytöstä, jottei kukaan voi avata sitä.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Kaikki Sendit" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Odottaa poistoa" }, + "hideTextByDefault": { + "message": "Piilota teksti oletuksena" + }, "expired": { "message": "Erääntynyt" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "Seuraavat organisaatiokäytännöt ovat aktiivisia:" - }, - "sendDisableHideEmailInEffect": { - "message": "Käyttäjiltä on estetty sähköpostiosoitteen piilotus kun he luovat tai muokkaavat Sendiä.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "modifiedPolicyId": { "message": "Muokkasi käytäntöä \"$ID$\".", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Poista yksityisen omistajuuden valinta käytöstä organisaation käyttäjiltä" }, - "textHiddenByDefault": { - "message": "Piilota teksti oletuksena kun Send avataan", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Kuvaava nimi Sendille.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Teksti, jonka haluat lähettää." - }, - "sendFileDesc": { - "message": "Tiedosto, jonka haluat lähettää." - }, - "copySendLinkOnSave": { - "message": "Kopioi Sendin linkki leikepöydälle tallennettaessa." - }, - "sendLinkLabel": { - "message": "Send-linkki", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "Tapahtui virhe tallennettaessa poisto- ja erääntymisajankohtia." }, + "hideYourEmail": { + "message": "Piilota sähköpostiosoitteeni avaajilta." + }, "webAuthnFallbackMsg": { "message": "Vahvista kaksivaiheinen kirjautuminen (2FA) alla olevalla painikeella." }, "webAuthnAuthenticate": { "message": "WebAuthn-todennus" }, + "readSecurityKey": { + "message": "Lue suojausavain" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn-todennusta ei tueta tässä selaimessa." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Virhe" }, + "decryptionError": { + "message": "Salauksen purkuvirhe" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Ota yhteyttä asiakkaaseen", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "lisätietojen menettämisen välttämiseksi.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "\"Tilien palautusavun hallinta\" -oikeuden kanssa on myönnettävä myös \"Käyttäjien hallinta\" -oikeus." }, @@ -6516,15 +6791,6 @@ "message": "Generaattori", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "Mitä haluat luoda?" - }, - "passwordType": { - "message": "Salasanan tyyppi" - }, - "regenerateUsername": { - "message": "Luo uusi käyttäjätunnus" - }, "generateUsername": { "message": "Luo käyttäjätunnus" }, @@ -6556,7 +6822,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " Käytä $RECOMMENDED$ tai useampaa sanaa vahvan salalauseen luomiseksi.", + "message": " Käytä vahvaan salalauseeseen ainakin $RECOMMENDED$ sanaa.", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Käyttäjätunnuksen tyyppi" - }, "plusAddressedEmail": { "message": "Plus+merkkinen sähköposti", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Käytä verkkotunnuksesi catch-all-postilaatikkoa." }, + "useThisEmail": { + "message": "Käytä tätä sähköpostia" + }, "random": { "message": "Satunnainen", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "$SERVICENAME$ -palvelun peittämän sähköpostitilin tunnistetta ei saatu.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Isäntä", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API-käyttötunniste" - }, "deviceVerification": { "message": "Laitevahvistus" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Tilillesi kirjautuminen vaatii Duo-vahvistuksen." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Avaa Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Käyttäjien määrä" }, - "loggingInAs": { - "message": "Kirjaudutaan tunnuksella" - }, - "notYou": { - "message": "Etkö se ollut sinä?" - }, "pickAnAvatarColor": { "message": "Valitse hahmon väri" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "Ei kokoelmaa" }, - "canView": { - "message": "Voi tarkastella" - }, - "canViewExceptPass": { - "message": "Voi tarkastella (ei salasanoja)" - }, - "canEdit": { - "message": "Voi muokata" - }, - "canEditExceptPass": { - "message": "Voi muokata (ei salasanoja)" - }, "noCollectionsAdded": { "message": "Kokoelmia ei ole lisätty" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Pyydä hyväksyntää ylläpidolta" }, - "approveWithMasterPassword": { - "message": "Hyväksy pääsalasanalla" - }, "trustedDeviceEncryption": { "message": "Luotettu laitesalaus" }, "trustedDevices": { "message": "Luotetut laitteet" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Kun jäsenet ovat tunnistautuneet, he voivat purkaa holvin salauksen omalla laitteellaan säilytettävällä avaimella.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "\"Yksittäinen organisaatio\"", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "single organization", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": "ja", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "policy,", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "\"Kertakirjautuminen vaaditaan\"", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO required", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "-käytännöt sekä", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "policy, and", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "\"Tilien palautusavun hallinta\"", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "account recovery administration", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "-käytäntö automaattisella liitoksella otetaan käyttöön tämän valinnan käyttöönoton yhteydessä.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Organisaatiosi käyttöoikeuksia muutettiin ja tämän seurauksena sinun on asetettava pääsalasana.", @@ -8212,7 +8484,7 @@ "message": "Aktivoi Salaisuushallinta" }, "yourOrganizationsFingerprint": { - "message": "Organisaatiosi tunnistelauseke", + "message": "Organisaatiosi tunnistelause", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their organization's public key with another user, for the purposes of sharing." }, "deviceApprovals": { @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Hyväksy pyyntö" }, + "deviceApproved": { + "message": "Laite hyväksyttiin" + }, + "deviceRemoved": { + "message": "Laite poistettiin" + }, + "removeDevice": { + "message": "Poista laite" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Laitepyyntöjä ei ole" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Pyyntösi on välitetty ylläpidollesi." }, - "youWillBeNotifiedOnceApproved": { - "message": "Saat ilmoituksen kun se on hyväksytty." - }, "troubleLoggingIn": { "message": "Ongelmia kirjautumisessa?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Rajoita kokoelmien poisto omistajille ja ylläpitäjille" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Omistajat ja ylläpitäjät voivat hallita kaikkia kokoelmia ja kohteita" }, @@ -8494,9 +8778,6 @@ "message": "Itse ylläpidetyn palvelimen URL-osoite", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Aliaksen verkkotunnus" - }, "alreadyHaveAccount": { "message": "Onko sinulla jo tili?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "Oikeutesi eivät riitä kokoelman hallintaan." }, - "grantAddAccessCollectionWarningTitle": { - "message": "\"Voi hallita\" -käyttöoikeus puuttuu" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Myönnä ”Voi hallita” -oikeus, joka mahdollistaa kokoelman täydellisen hallinnan, mukaan lukien sen poistamisen." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Myönnä ryhmille tai henkilöille kokoelman käyttöoikeus." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Laitteen luontipäivä puuttuu" + }, + "desktopRequired": { + "message": "Työpöytä vaaditaan" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "kuukaudessa/jäsen" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Käyttäjäpaikat" }, @@ -9518,7 +9817,7 @@ "message": "Lisätietoja holvisi hausta" }, "learnMoreAboutYourAccountFingerprintPhrase": { - "message": "Lisätietoja tilisi tunnistelausekkeesta" + "message": "Lisätietoja tilisi tunnistelauseesta" }, "impactOfRotatingYourEncryptionKey": { "message": "Salausavaimesi kierrätyksen vaikutus" @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Lisätietoja Bitwardenin API:sta" }, + "fileSend": { + "message": "Tiedosto-Send" + }, "fileSends": { "message": "Tiedosto-Sendit" }, + "textSend": { + "message": "Teksti-Send" + }, "textSends": { "message": "Teksti-Sendit" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Avainalgoritmi" }, + "sshPrivateKey": { + "message": "Yksityinen avain" + }, + "sshPublicKey": { + "message": "Julkinen avain" + }, + "sshFingerprint": { + "message": "Sormenjälki" + }, "sshKeyFingerprint": { "message": "Sormenjälki" }, @@ -9774,10 +10088,6 @@ "message": "Sisällytä erikoismerkkejä", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Lisää liite" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "Et voi poistaa kokoelmia, joihin sinulla on vain tarkasteluoikeus: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Tärkeä ilmoitus" }, @@ -9909,7 +10228,7 @@ "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." }, "remindMeLater": { - "message": "Remind me later" + "message": "Muistuta myöhemmin" }, "newDeviceVerificationNoticePageOneFormContent": { "message": "Do you have reliable access to your email, $EMAIL$?", @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Poista jäsenet" }, + "devices": { + "message": "Laitteet" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -9966,7 +10294,7 @@ "message": "Claimed" }, "domainStatusUnderVerification": { - "message": "Under verification" + "message": "Vahvistettavana" }, "claimedDomainsDesc": { "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "Syöttämäsi salasana on virheellinen." + }, + "importSshKey": { + "message": "Tuo" + }, + "confirmSshKeyPassword": { + "message": "Vahvista salasana" + }, + "enterSshKeyPasswordDesc": { + "message": "Syötä SSH-avaimen salasana." + }, + "enterSshKeyPassword": { + "message": "Syötä salasana" + }, + "invalidSshKey": { + "message": "SSH-avain on virheellinen" + }, + "sshKeyTypeUnsupported": { + "message": "SSH-avaintyyppiä ei ole tuettu" + }, + "importSshKeyFromClipboard": { + "message": "Tuo avain leikepöydältä" + }, + "sshKeyImported": { + "message": "SSH-avain on tuotu" + }, + "copySSHPrivateKey": { + "message": "Kopioi yksityinen avain" + }, + "openingExtension": { + "message": "Avataan Bitwarden-selainlaajennusta" + }, + "somethingWentWrong": { + "message": "Jokin meni pieleen..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Avaa laajennus" + }, + "doNotHaveExtension": { + "message": "Eikö sinulla ole Bitwarden-selainlaajennusta?" + }, + "installExtension": { + "message": "Asenna laajennus" + }, + "openedExtension": { + "message": "Avaa selainlaajennus" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organisaatiotilaus aloitettiin uudelleen" + }, + "restartSubscription": { + "message": "Aloita tilauksesi uudelleen" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Poisto on uusi toiminto!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Olemassa oleva organisaatio" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Olemassa oleva organisaatio lisättiin" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Vaihda vaarantunut salasana" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index f5a9b078984..414bbda91d8 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Critical applications" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "Ano'ng uri ng item na ito?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Mga Tala" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Note" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Hilahin para pagsunud-sunurin" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Teksto" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Baguhin ang folder" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "Base domain", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "hal.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Mga pansala" }, - "moveSelectedToOrg": { - "message": "Ilipat sa napiling organisasyon" - }, "deleteSelected": { "message": "Burahin ang napili" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Inilipat ang mga napiling item sa $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "Hindi" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "Mag-log in o gumawa ng bagong account para ma-access ang secure vault mo." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Sinimulan ang pag-log in" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Ipadala" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Palatandaan ng password" - }, - "enterEmailToGetHint": { - "message": "Ipasok ang email address ng account mo para makita ang palatandaan ng master password mo." - }, "getMasterPasswordHint": { "message": "Kunin ang palatandaan ng master password" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "Nakapagpadala na ng notipikasyon sa device mo." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "Bersyon $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Tandaan ako" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Ipadala ulit ang email na naglalaman ng code pamberipika" }, "useAnotherTwoStepMethod": { "message": "Gumamit ng ibang paraan sa dalawang-hakbang na pag-log in" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Ipasok ang YubiKey mo sa USB port ng iyong computer, tapos pindutin ang buton nito." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Mga opsyon para sa dalawang-hakbang na pag-log in" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Nawalan ng access sa lahat ng provider mo ng dalawang-hakbang na pag-log in? Gamitin ang code pang-recover mo para patayin ang lahat ng mga provider ng dalawang-hakbang na pag-log in sa account mo." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Inilipat mula sa FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "Email" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Piliin kung saang organisasyon mo ililipat ang item na ito. Magiging pag-aari ng organisasyon ang anumang item na ililipat mo rito. Hindi ka na ang direktang may-ari ng item na ito pagkalipat." }, - "moveManyToOrgDesc": { - "message": "Piliin kung saang organisasyon mo ililipat ang mga item na ito. Magiging pag-aari ng organisasyon ang mga item na ililipat mo rito. Hindi ka na ang direktang may-ari ng mga item na ito pagkalipat." - }, "collectionsDesc": { "message": "Baguhin kung saang mga koleksyon ibinabahagi ang item na ito. Ang mga user sa organisasyon na may access sa koleksyon na ito lang ang makakakita sa item." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "$COUNT$ item ang napili mo. Pwedeng mailipat sa isang organisasyon ang $MOVEABLE_COUNT$ item, habang $NONMOVEABLE_COUNT$ item ang hindi pwedeng mailipat.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Code pamberipika (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Gumawa ng isa pang password" - }, "length": { "message": "Haba" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Mangyaring mag-log in ulit." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Mangyaring mag-log in ulit. Kung gumagamit ka ng iba pang mga aplikasyon pang-Bitwarden, mag-log out at log in rin ulit sa mga iyon." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Mapanganib na lugar" }, - "dangerZoneDesc": { - "message": "Mag-ingat, wala nang bawian sa mga gagawin mo rito!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "I-deauthorize ang mga sesyon" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Mala-log out ka rin sa kasalukuyan mong sesyon kung tutuloy ka, at kakailanganin mong mag-log in ulit. Kakailanganin mo ring ulitin ang dalawang-hakbang na pag-log in kung naka-set up ito. Maaaring manatiling aktibo ang mga sesyon sa iba pang device nang hanggang isang oras." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "Na-deauthorize lahat ng mga sesyon" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Maaaring permanente kang ma-lock out sa account mo dahil sa dalawang-hakbang na pag-log in. Pwede kang gumamit ng code pang-recover sakaling hindi mo na magamit ang normal mong provider ng dalawang-hakbang na pag-log in (halimbawa: nawala ang device mo). Hindi ka matutulungan ng Bitwarden support kung mawalan ka ng access sa account mo. Mainam na isulat o i-print mo ang mga code pang-recover at itago ito sa ligtas na lugar." }, + "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." + }, "viewRecoveryCode": { "message": "Tingnan ang code pang-recover" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Pamahalaan" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Isara" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Tanggalin ang access" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "Aktibo ang provider na ito ng dalawang-hakbang na pag-log in sa account mo." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "Nagkaproblema sa pagbabasa ng security key. Pakisubukan ulit." }, - "twoFactorWebAuthnWarning": { - "message": "Hindi magagamit sa lahat ng mga aplikasyon ng Bitwarden ang WebAuthn dahil sa mga limitasyon ng platform. Dapat mag-set up ka ng isa pang provider ng dalawang-hakbang na pag-log in para ma-access mo ang account mo sakaling hindi magamit ang WebAuthn. Mga suportadong platform:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Web vault at mga ekstensyon pang-browser sa desktop/laptop na may browser na sinusuportahan ang WebAuthn (Chrome, Opera, Vivaldi, o Firefox na nakabukas ang FIDO U2F)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Code pang-recover mo sa dalawang-hakbang na pag-log in sa Bitwarden" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "Gumagamit ang user na ito ng dalawang hakbang na pag login upang maprotektahan ang kanilang account." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Hindi naka-link na SSO para sa user $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "Gumagamit ka ng isang hindi suportado na web browser. Ang web vault ay maaaring hindi gumana nang maayos." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "Kung hindi mo ma access ang iyong account sa pamamagitan ng iyong normal na dalawang hakbang na mga pamamaraan sa pag login, maaari mong gamitin ang iyong dalawang hakbang na code sa pagbawi ng pag login upang patayin ang lahat ng dalawang hakbang na provider sa iyong account." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Bawiin ang account dalawang hakbang na pag login" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Magtakda ng mga kinakailangan para sa lakas ng master password." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Kailangan ng dalawang-hakbang na pag-login" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Magtakda ng mga kinakailangan para sa generator ng password." }, - "passwordGeneratorPolicyInEffect": { - "message": "Isang o higit pang patakaran ng organisasyon ay nakakaapekto sa iyong mga setting ng generator." - }, "masterPasswordPolicyInEffect": { "message": "Isang o higit pang mga patakaran ng organisasyon ay nangangailangan ng iyong master password upang matugunan ang sumusunod na kinakailangan:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Ang mga may ari ng organisasyon at mga admin ay exempted mula sa pagpapatupad ng patakaran na ito." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "Mag-file" }, "sendTypeText": { "message": "Teksto" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Bagong Ipadala", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "I-delete ang Ipadala", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Sigurado ka bang gusto mo na i-delete ang Ipadala na ito?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "Anong type ng Send ito", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Petsa ng Pagtanggal" }, - "deletionDateDesc": { - "message": "Ang Ipadala ay tatanggalin nang permanente sa tinukoy na petsa at oras.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maximum na bilang ng access" }, - "maxAccessCountDesc": { - "message": "Kung nakatakda, ang mga user ay hindi na maaaring ma-access ang Send na ito pagkatapos makarating sa maximum access count.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Kasalukuyang access count" - }, - "sendPasswordDesc": { - "message": "Maipapayo na mag-require ng password para sa mga user na ma-access ang Send na ito.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Pribadong mga tala tungkol sa Send na ito.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Ipadala nai-delete" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Sigurado ka bang gusto mo na tanggalin ang password?" }, - "hideEmail": { - "message": "Itago ang aking email address mula sa mga tatanggap." - }, - "disableThisSend": { - "message": "Deactivate ang Send na ito para walang maka access dito.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Lahat ng Mga Padala" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Nakabinbing pagbura" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Paso na" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "Isang o higit pang mga patakaran ng organisasyon ay nakaapekto sa iyong mga pagpipilian sa Pagpadala." - }, - "sendDisableHideEmailInEffect": { - "message": "Hindi pinapayagan ang mga gumagamit na itago ang kanilang email address mula 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." - }, "modifiedPolicyId": { "message": "Binagong patakaran $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Alisin ang indibidwal na pagmamay ari para sa mga gumagamit ng organisasyon" }, - "textHiddenByDefault": { - "message": "Kapag na access ang Ipadala, itago ang teksto sa pamamagitan ng default", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Isang friendly name upang ilarawan ang Ipadala na ito.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Ang teksto na nais mong ipadala." - }, - "sendFileDesc": { - "message": "Ang file na gusto mong ipadala." - }, - "copySendLinkOnSave": { - "message": "Kopyahin ang link upang ibahagi ito Ipadala sa aking clipboard sa save." - }, - "sendLinkLabel": { - "message": "Magpadala ng link", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Ipadala", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "Nagkaroon ng error sa pag-save ng iyong mga petsa ng pagbura at pagpaso." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "Upang i verify ang iyong 2FA mangyaring i click ang pindutan sa ibaba." }, "webAuthnAuthenticate": { "message": "I-authenticate ang WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "Hindi suportado ang WebAuthn sa browser na ito." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Mali" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Magmamana", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "Ano ang nais mong bumuo?" - }, - "passwordType": { - "message": "Uri ng Password" - }, - "regenerateUsername": { - "message": "Muling bumuo ng username" - }, "generateUsername": { "message": "Lumikha ng username" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Uri ng username" - }, "plusAddressedEmail": { "message": "Plus na naka-address na email", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Gamitin ang naka configure na inbox ng catch all ng iyong domain." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Pangalan ng Hostname", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "Token ng Access sa API" - }, "deviceVerification": { "message": "Pag verify ng aparato" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Bilang ng mga gumagamit" }, - "loggingInAs": { - "message": "Naglolog-in bilang" - }, - "notYou": { - "message": "Hindi ikaw?" - }, "pickAnAvatarColor": { "message": "Pumili ng kulay ng avatar" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "Walang koleksyon" }, - "canView": { - "message": "Maaaring tingnan" - }, - "canViewExceptPass": { - "message": "Maaaring tingnan, maliban sa mga password" - }, - "canEdit": { - "message": "Maaaring i-edit" - }, - "canEditExceptPass": { - "message": "Maaaring mag edit, maliban sa mga password" - }, "noCollectionsAdded": { "message": "Walang idinagdag na mga koleksyon" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domain" - }, "alreadyHaveAccount": { "message": "Already have an account?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index e6c402293f8..95aa573f14b 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Applications critiques" }, + "noCriticalAppsAtRisk": { + "message": "Aucune application critique à risques" + }, "accessIntelligence": { "message": "Accéder à Intelligence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "Membres à risque" }, + "atRiskMembersWithCount": { + "message": "Membres à risque ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Applications à risque ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "Ces membres se connectent à des applications avec des mots de passe faibles, exposés ou réutilisés." + }, + "atRiskApplicationsDescription": { + "message": "Ces applications ont des mots de passe faibles, exposés ou réutilisés." + }, + "atRiskMembersDescriptionWithApp": { + "message": "Ces membres se connectent à $APPNAME$ avec des mots de passe faibles, exposés ou réutilisés.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total des membres" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total des applications" }, + "unmarkAsCriticalApp": { + "message": "Ne plus marquer comme application critique" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Marquage d'application critique retiré avec succès" + }, "whatTypeOfItem": { "message": "Quel type d'élément est-ce ?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Notes" }, + "privateNote": { + "message": "Note privée" + }, "note": { "message": "Note" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Glissez pour trier" }, + "dragToReorder": { + "message": "Faire glisser pour réorganiser" + }, "cfTypeText": { "message": "Texte" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Modifier le dossier" }, + "editWithName": { + "message": "Modifier $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "Nouveau dossier" + }, + "folderName": { + "message": "Nom de dossier" + }, + "folderHintText": { + "message": "Créez un sous-dossier en ajoutant le nom du dossier parent suivi d'un \"/\". Par exemple : Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Êtes-vous sûr de vouloir supprimer définitivement ce dossier ?" + }, "baseDomain": { "message": "Domaine de base", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Nom de l’élément" }, - "cannotRemoveViewOnlyCollections": { - "message": "Vous ne pouvez pas supprimer des collections avec les autorisations d'affichage uniquement : $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "ex.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filtre" }, - "moveSelectedToOrg": { - "message": "Déplacer la sélection vers l'organisation" - }, "deleteSelected": { "message": "Supprimer la sélection" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Les éléments sélectionnés ont été déplacés vers $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Éléments déplacés vers $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "Non" }, + "location": { + "message": "Localisation" + }, "loginOrCreateNewAccount": { "message": "Connectez-vous ou créez un nouveau compte pour accéder à votre coffre sécurisé." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Se connecter à Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Entrez le code envoyé à votre adresse courriel" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Entrez le code de votre application d'authentification" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Appuyez sur votre YubiKey pour vous authentifier" + }, "authenticationTimeout": { "message": "Délai d'authentification dépassé" }, "authenticationSessionTimedOut": { "message": "La session d'authentification a expiré. Veuillez redémarrer le processus de connexion." }, - "verifyIdentity": { - "message": "Vérifiez votre Identité" + "verifyYourIdentity": { + "message": "Vérifiez votre identité" + }, + "weDontRecognizeThisDevice": { + "message": "Nous ne reconnaissons pas cet appareil. Entrez le code envoyé à votre courriel pour vérifier votre identité." + }, + "continueLoggingIn": { + "message": "Continuer à vous connecter" + }, + "whatIsADevice": { + "message": "Qu'est-ce qu'un appareil ?" + }, + "aDeviceIs": { + "message": "Un appareil est une installation unique de l'application Bitwarden où vous vous êtes connecté. Réinstaller, effacer les données de l'application ou effacer vos cookies peut entraîner l'apparition d'un appareil plusieurs fois." }, "logInInitiated": { "message": "Connexion initiée" }, + "logInRequestSent": { + "message": "Demande envoyée" + }, "submit": { "message": "Soumettre" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Entrez l'adresse courriel de votre compte et l'indice de votre mot de passe vous sera envoyé" }, - "passwordHint": { - "message": "Indice de mot de passe" - }, - "enterEmailToGetHint": { - "message": "Saisissez l'adresse électronique de votre compte pour recevoir l'indice de votre mot de passe principal." - }, "getMasterPasswordHint": { "message": "Obtenir l'indice du mot de passe principal" }, @@ -1291,7 +1364,7 @@ "message": "Aucun élément à afficher." }, "noPermissionToViewAllCollectionItems": { - "message": "Vous n'avez pas la permission de voir tous les éléments de cette collection." + "message": "Vous n'avez pas l'autorisation d'afficher tous les éléments de cette collection." }, "youDoNotHavePermissions": { "message": "Vous n'avez pas les autorisations pour cette collection" @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "Une notification a été envoyée à votre appareil." }, + "notificationSentDevicePart1": { + "message": "Déverrouillez Bitwarden sur votre appareil ou sur le " + }, + "areYouTryingToAccessYourAccount": { + "message": "Essayez-vous d'accéder à votre compte ?" + }, + "accessAttemptBy": { + "message": "Tentative d'accès par $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirmer l'accès" + }, + "denyAccess": { + "message": "Refuser l'accès" + }, + "notificationSentDeviceAnchor": { + "message": "application web" + }, + "notificationSentDevicePart2": { + "message": "Assurez-vous que la phrase d'empreinte correspond à celle ci-dessous avant d'approuver." + }, + "notificationSentDeviceComplete": { + "message": "Déverrouillez Bitwarden sur votre appareil. Assurez-vous que la phrase d'empreinte correspond à celle ci-dessous avant d'approuver." + }, "aNotificationWasSentToYourDevice": { "message": "Une notification a été envoyée à votre appareil" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Assurez-vous que votre compte est déverrouillé et que la phrase d'empreinte correspond à l'autre appareil" - }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Se souvenir de moi" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Ne plus demander sur cet appareil pendant 30 jours" + }, "sendVerificationCodeEmailAgain": { "message": "Envoyer à nouveau le courriel de code de vérification" }, "useAnotherTwoStepMethod": { "message": "Utiliser une autre méthode d'authentification à deux facteurs" }, + "selectAnotherMethod": { + "message": "Sélectionnez une autre méthode", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Utilisez votre code de récupération" + }, "insertYubiKey": { "message": "Insérez votre YubiKey dans le port USB de votre ordinateur puis appuyez sur son bouton." }, @@ -1375,7 +1485,7 @@ "message": "Identifiant non disponible" }, "noTwoStepProviders": { - "message": "Ce compte dispose d'une authentification à deux facteurs de configurée, cependant, aucun des fournisseurs à deux facteurs configurés n'est pris en charge par ce navigateur web." + "message": "Ce compte dispose d'une configuration d'authentification à deux facteurs, cependant, aucun des fournisseurs d'authentification à deux facteurs configurés n'est pris en charge par ce navigateur web." }, "noTwoStepProviders2": { "message": "Merci d'utiliser un navigateur web compatible (comme Chrome) et/ou d'ajouter des services additionnels d'identification en deux étapes qui sont mieux supportés par les navigateurs web (comme par exemple une application d'authentification)." @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Options d'authentification à deux facteurs" }, + "selectTwoStepLoginMethod": { + "message": "Sélectionnez la méthode d'authentification à deux facteurs" + }, "recoveryCodeDesc": { "message": "Vous avez perdu l'accès à tous vos fournisseurs d'authentification à deux facteurs ? Utilisez votre code de récupération pour désactiver tous les fournisseurs d'authentification à deux facteurs de votre compte." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Migré depuis FIDO)" }, + "openInNewTab": { + "message": "Ouvrir dans un nouvel onglet" + }, "emailTitle": { "message": "Courriel" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Choisissez une organisation vers laquelle vous souhaitez déplacer cet élément. Déplacer un élément vers une organisation transfère la propriété de l'élément à cette organisation. Vous ne serez plus le propriétaire direct de cet élément une fois qu'il aura été déplacé." }, - "moveManyToOrgDesc": { - "message": "Choisissez une organisation vers laquelle vous souhaitez déplacer ces éléments. Déplacer des éléments vers une organisation transfère la propriété des éléments à cette organisation. Vous ne serez plus le propriétaire direct de ces éléments une fois qu'ils auront été déplacés." - }, "collectionsDesc": { "message": "Modifier les collections avec lesquelles cet élément est partagé. Seuls les utilisateurs de l'organisation avec un accès à ces collections pourront voir cet élément." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "Vous avez sélectionné $COUNT$ élément(s). $MOVEABLE_COUNT$ élément(s) peuvent être déplacés vers une organisation, $NONMOVEABLE_COUNT$ ne le peuvent pas.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Code de vérification (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Éviter les caractères ambigus", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Régénérer un mot de passe" - }, "length": { "message": "Longueur" }, @@ -1651,7 +1744,7 @@ "message": "Inclure un Chiffre" }, "generatorPolicyInEffect": { - "message": "Les exigences de la politique de sécurité de l'entreprise ont été appliquées aux options de votre générateur.", + "message": "Les exigences de la politique de sécurité Entreprise ont été appliquées aux options de votre générateur.", "description": "Indicates that a policy limits the credential generator screen." }, "passwordHistory": { @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Veuillez vous reconnecter." }, + "currentSession": { + "message": "Session en cours" + }, + "requestPending": { + "message": "Demande en attente" + }, "logBackInOthersToo": { "message": "Veuillez vous reconnecter. Si vous utilisez d'autres applications Bitwarden, déconnectez-vous et reconnectez-vous également de celles-ci." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Zone de danger" }, - "dangerZoneDesc": { - "message": "Attention, ces actions sont irréversibles !" - }, - "dangerZoneDescSingular": { - "message": "Attention, cette action est irréversible!" - }, "deauthorizeSessions": { "message": "Révoquer les sessions" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "En poursuivant, vous serez également déconnecté de votre session en cours, ce qui vous obligera à vous reconnecter. Vous serez également invité à vous reconnecter via l'authentification à deux facteurs, si elle est configurée. Les sessions actives sur d'autres appareils peuvent rester actives pendant encore une heure." }, + "newDeviceLoginProtection": { + "message": "Connexion à un nouvel appareil" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Désactivez la protection de connexion du nouvel appareil" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Activez la protection de connexion du nouvel appareil" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Procédez ci-dessous pour désactiver les courriels de vérification envoyés par Bitwarden lorsque vous vous connectez à partir d'un nouvel appareil." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Procédez ci-dessous pour que Bitwarden vous envoie des courriels de vérification lorsque vous vous connectez à partir d'un nouvel appareil." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "Avec la protection de connexion d'un nouvel appareil désactivée, toute personne ayant votre mot de passe maître peut accéder à votre compte depuis n'importe quel appareil. Pour protéger votre compte sans courriel de vérification, configurez la connexion en deux étapes." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "Modifications de la protection de connexion de l'appareil enregistrées" + }, "sessionsDeauthorized": { "message": "Toutes les sessions ont été révoquées" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "La configuration d'un système d'authentification à deux facteurs peut définitivement vous verrouiller l'accès à votre compte Bitwarden. Un code de récupération vous permet d'accéder à votre compte dans le cas où vous ne pourriez plus utiliser votre fournisseur normal d'authentification à deux facteurs (exemple : vous perdez votre appareil). L'assistance de Bitwarden ne pourra pas vous aider si vous perdez l'accès à votre compte. Nous vous recommandons de noter ou d'imprimer le code de récupération et de le conserver en lieu sûr." }, + "yourSingleUseRecoveryCode": { + "message": "Votre code de récupération à usage unique peut être utilisé pour désactiver la connexion en deux étapes si vous perdez l'accès à votre fournisseur de connexion en deux étapes. Bitwarden vous recommande d'écrire le code de récupération et de le conserver dans un endroit sûr." + }, "viewRecoveryCode": { "message": "Voir le code de récupération" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Gérer" }, - "canManage": { - "message": "Peut gérer" + "manageCollection": { + "message": "Gérer la collection" + }, + "viewItems": { + "message": "Afficher les éléments" + }, + "viewItemsHidePass": { + "message": "Afficher les éléments, les mots de passe cachés" + }, + "editItems": { + "message": "Modifier les items" + }, + "editItemsHidePass": { + "message": "Modifier les éléments, les mots de passe cachés" }, "disable": { "message": "Désactiver" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Révoquer l'Accès" }, + "revoke": { + "message": "Révoquer" + }, "twoStepLoginProviderEnabled": { "message": "Ce fournisseur d'authentification à deux facteurs est actif sur votre compte." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "Un problème est survenu lors de la lecture de la clé de sécurité. Veuillez réessayer." }, - "twoFactorWebAuthnWarning": { - "message": "En raison des limitations de la plate-forme, WebAuthn ne peut pas être utilisé sur toutes les applications Bitwarden. Vous devriez mettre en place un autre fournisseur d'authentification à deux facteurs afin de pouvoir accéder à votre compte lorsque WebAuthn ne peut pas être utilisé. Plateformes supportées :" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Coffre web et extensions sur un ordinateur fixe/portable avec un navigateur compatible WebAuthn (Chrome, Opera, Vivaldi ou Firefox avec FIDO U2F activé)." + "twoFactorWebAuthnWarning1": { + "message": "En raison des limitations de plate-forme, WebAuthn ne peut pas être utilisé sur toutes les applications Bitwarden. Vous devriez mettre en place un autre fournisseur d'authentification à deux facteurs afin de pouvoir accéder à votre compte lorsque WebAuthn ne peut pas être utilisé." }, "twoFactorRecoveryYourCode": { "message": "Votre code de récupération de d'authentification à deux facteurs Bitwarden" @@ -2985,7 +3114,7 @@ "message": "Pour les entreprises et autres équipes." }, "planNameTeamsStarter": { - "message": "Teams Starter" + "message": "Équipes Essentiel" }, "planNameEnterprise": { "message": "Entreprise" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Il vous reste 1 invitation." + }, + "inviteZeroEmailDesc": { + "message": "Il vous reste 0 invitations." + }, "userUsingTwoStep": { "message": "Cet utilisateur utilise l'authentification à deux facteurs pour protéger son compte." }, @@ -3285,7 +3420,7 @@ "message": "Propriétaire" }, "ownerDesc": { - "message": "L’utilisateur avec l’accès le plus élevé qui peut gérer tous les aspects de votre organisation." + "message": "Gérer tous les aspects de votre organisation, y compris la facturation et les abonnements" }, "clientOwnerDesc": { "message": "Cet utilisateur doit être indépendant du fournisseur. Si le fournisseur est dissocié de l'organisation, cet utilisateur conservera la propriété de l'organisation." @@ -3378,7 +3513,7 @@ "message": "Code incorrect" }, "incorrectPin": { - "message": "Code PIN incorrect" + "message": "NIP incorrect" }, "pin": { "message": "NIP", @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "SSO non lié." + }, "unlinkedSsoUser": { "message": "SSO dissocié pour l'utilisateur $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Appareil" }, + "loginStatus": { + "message": "Statut de connexion" + }, + "firstLogin": { + "message": "Première connexion" + }, + "trusted": { + "message": "Approuvé" + }, + "needsApproval": { + "message": "Requiert une approbation" + }, + "areYouTryingtoLogin": { + "message": "Essayez-vous de vous connecter ?" + }, + "logInAttemptBy": { + "message": "Tentative de connexion par $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Type d'appareil" + }, + "ipAddress": { + "message": "Adresse IP" + }, + "confirmLogIn": { + "message": "Confirmer la connexion" + }, + "denyLogIn": { + "message": "Refuser la connexion" + }, + "thisRequestIsNoLongerValid": { + "message": "Cette demande n'est plus valide." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Connexion confirmée pour $EMAIL$ sur $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "Vous avez refusé une tentative de connexion depuis un autre appareil. Si c'était vraiment vous, essayez de vous connecter à nouveau avec l'appareil." + }, + "loginRequestHasAlreadyExpired": { + "message": "La demande de connexion a déjà expiré." + }, + "justNow": { + "message": "À l’instant" + }, + "requestedXMinutesAgo": { + "message": "Demandé il y a $MINUTES$ minutes", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Création du compte sur" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "Vous utilisez un navigateur non supporté. Le coffre web pourrait ne pas fonctionner correctement." }, + "youHaveAPendingLoginRequest": { + "message": "Vous avez une demande de connexion en attente depuis un autre appareil." + }, + "reviewLoginRequest": { + "message": "Examiner la demande de connexion" + }, "freeTrialEndPromptCount": { "message": "Votre essai gratuit se termine dans $COUNT$ jours.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "Si vous ne pouvez pas accéder à votre compte par les méthodes normales d'authentification à deux facteurs, vous pouvez utiliser votre code de récupération d'authentification à deux facteurs pour désactiver tous les fournisseurs à deux facteurs sur votre compte." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Connectez-vous ci-dessous en utilisant votre code de récupération à usage unique. Cela désactivera tous les fournisseurs d'authentification à deux facteurs de votre compte." + }, "recoverAccountTwoStep": { "message": "Récupérer l'authentification à deux facteurs" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "La mise à jour de la clé de chiffrement ne peut pas continuer" }, + "editFieldLabel": { + "message": "Modifier $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Réorganiser $LABEL$. Utilisez la touche fléchée pour déplacer l'élément vers le haut ou vers le bas.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ déplacé vers le haut, position $INDEX$ de $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ déplacé vers le bas, position $INDEX$ de $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "Lors de la mise à jour de votre clé de chiffrement, vos dossiers n'ont pas pu être déchiffrés. Pour continuer avec la mise à jour, vos dossiers doivent être supprimés. Aucun élément du coffre ne sera supprimé si vous continuez." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Définir les exigences de robustesse du mot de passe principal." }, + "passwordStrengthScore": { + "message": "Score de force du mot de passe $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Exiger une authentification à deux facteurs" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Définir les exigences pour le générateur de mot de passe." }, - "passwordGeneratorPolicyInEffect": { - "message": "Une ou plusieurs politiques de sécurité de l'organisation affectent les paramètres de votre générateur." - }, "masterPasswordPolicyInEffect": { "message": "Une ou plusieurs politiques de sécurité de l'organisation exigent que votre mot de passe principal réponde aux exigences suivantes :" }, @@ -4731,7 +5006,7 @@ "message": "Pour vous connecter avec votre fournisseur de SSO, entrez l'identifiant SSO de votre organisation pour commencer. Vous devrez peut-être entrer cet identifiant SSO lorsque vous vous connecterez à partir d'un nouvel appareil." }, "enterpriseSingleSignOn": { - "message": "Portail de connexion unique d'entreprise (Single Sign-On)" + "message": "Portail de connexion unique Entreprise" }, "ssoHandOff": { "message": "Vous pouvez maintenant fermer cet onglet et continuer dans l'extension." @@ -4749,7 +5024,7 @@ "message": "Toutes les fonctionnalités pour les équipes, plus :" }, "includeAllTeamsStarterFeatures": { - "message": "Toutes les fonctionnalités de Teams Starter, plus :" + "message": "Toutes les fonctionnalités d'Équipes Essentiel, plus :" }, "chooseMonthlyOrAnnualBilling": { "message": "Choisissez la facturation mensuelle ou annuelle" @@ -4813,13 +5088,13 @@ "message": "Authentification par Connexion Unique (Single Sign-On)" }, "requireSsoPolicyDesc": { - "message": "Exiger que les utilisateurs se connectent avec la méthode du portail de connexion unique d'entreprise." + "message": "Exiger que les membres se connectent avec la méthode du portail de connexion unique Entreprise." }, "prerequisite": { "message": "Prérequis" }, "requireSsoPolicyReq": { - "message": "La politique d'entreprise \"Organisation Unique\" doit être activée avant d'activer cette politique." + "message": "La politique d'organisation unique Entreprise doit être activée avant d'activer cette politique." }, "requireSsoPolicyReqError": { "message": "La politique \"Organisation Unique\" n'est pas activée." @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Les propriétaires et les administrateurs de l'organisation sont exonérés de l'application de cette politique." }, + "limitSendViews": { + "message": "Limiter le nombre d'affichages" + }, + "limitSendViewsHint": { + "message": "Personne ne peut afficher ce Send une fois la limite atteinte.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ affichages restants", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Détails du Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Texte à partager" + }, "sendTypeFile": { "message": "Fichier" }, "sendTypeText": { "message": "Texte" }, + "sendPasswordDescV3": { + "message": "Ajouter un mot de passe facultatif pour que les destinataires puissent accéder à ce Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Nouveau Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Supprimer le Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Êtes-vous sûr de vouloir supprimer ce Send ?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "De quel type de Send s'agit-il ?", + "deleteSendPermanentConfirmation": { + "message": "Êtes-vous sûr de vouloir supprimer définitivement ce Send ?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Date de suppression" }, - "deletionDateDesc": { - "message": "Le Send sera définitivement supprimé à la date et heure spécifiées.", + "deletionDateDescV2": { + "message": "Le Send sera définitivement supprimé à cette date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Nombre maximum d'accès" }, - "maxAccessCountDesc": { - "message": "Si défini, les utilisateurs ne seront plus en mesure d'accéder à ce Send une fois que le nombre maximum d'accès sera atteint.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Nombre d'accès actuel" - }, - "sendPasswordDesc": { - "message": "Vous pouvez, si vous le souhaitez, exiger un mot de passe pour accéder à ce Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Notes privées à propos de ce Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Désactivé" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Êtes-vous sûr de vouloir supprimer le mot de passe ?" }, - "hideEmail": { - "message": "Masquer mon adresse électronique aux destinataires." - }, - "disableThisSend": { - "message": "Désactiver ce Send pour que personne ne puisse y accéder.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Tous les Sends" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "En attente de suppression" }, + "hideTextByDefault": { + "message": "Masquer le texte par défaut" + }, "expired": { "message": "Expiré" }, @@ -4981,7 +5261,7 @@ "message": "Accordez et gérez l'accès d'urgence pour les contacts de confiance. Les contacts de confiance peuvent demander l'accès pour afficher ou reprendre le contrôle de votre compte en cas d'urgence. Consultez notre page d'aide pour plus d'informations et de détails sur le fonctionnement du partage à divulgation nulle de connaissance (zero knowledge sharing)." }, "emergencyAccessOwnerWarning": { - "message": "Vous êtes propriétaire d'une ou de plusieurs organisations. Si vous autorisez la prise de contrôle à un contact d'urgence, il sera en mesure d'utiliser toutes vos permissions de propriétaire après la prise de contrôle." + "message": "Vous êtes propriétaire d'une ou de plusieurs organisations. Si vous autorisez la prise de contrôle à un contact d'urgence, il sera en mesure d'utiliser toutes vos autorisations de propriétaire après la prise de contrôle." }, "trustedEmergencyContacts": { "message": "Contacts d'urgence de confiance" @@ -5126,7 +5406,7 @@ "message": "Les propriétaires et les administrateurs de l'organisation sont exonérés de l'application de cette politique." }, "personalOwnershipSubmitError": { - "message": "En raison d'une politique d'entreprise, il vous est interdit d'enregistrer des éléments dans votre coffre personnel. Sélectionnez une organisation dans l'option Propriété et choisissez parmi les collections disponibles." + "message": "En raison d'une politique de sécurité Entreprise, il vous est interdit d'enregistrer des éléments dans votre coffre personnel. Sélectionnez une organisation dans l'option Propriété et choisissez parmi les collections disponibles." }, "disableSend": { "message": "Supprimer le Send" @@ -5143,7 +5423,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { - "message": "En raison d'une politique d'entreprise, vous ne pouvez que supprimer un Send existant.", + "message": "En raison d'une politique de sécurité Entreprise, vous ne pouvez que supprimer un Send existant.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendOptions": { @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "Les politiques de sécurité de l'organisation suivantes sont actuellement en vigueur :" - }, - "sendDisableHideEmailInEffect": { - "message": "Les utilisateurs ne sont pas autorisés à masquer leur adresse électronique aux 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." - }, "modifiedPolicyId": { "message": "Politique $ID$ modifiée.", "placeholders": { @@ -5208,7 +5481,7 @@ "message": "Permissions" }, "permission": { - "message": "Permission" + "message": "Autorisation" }, "accessEventLogs": { "message": "Accéder aux journaux d'événements" @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Supprimer la propriété individuelle des utilisateurs de l'organisation" }, - "textHiddenByDefault": { - "message": "Lors de l'accès à ce Send, masquer le texte par défaut", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Un nom convivial pour décrire ce Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Le texte que vous voulez envoyer." - }, - "sendFileDesc": { - "message": "Le fichier que vous voulez envoyer." - }, - "copySendLinkOnSave": { - "message": "Copier le lien de ce Send dans mon presse-papiers lors de l'enregistrement." - }, - "sendLinkLabel": { - "message": "Lien du Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "Une erreur s'est produite lors de l'enregistrement de vos dates de suppression et d'expiration." }, + "hideYourEmail": { + "message": "Masquer mon adresse courriel aux destinataires." + }, "webAuthnFallbackMsg": { "message": "Pour vérifier votre 2FA, veuillez cliquer sur le bouton ci-dessous." }, "webAuthnAuthenticate": { "message": "Authentifier WebAuthn" }, + "readSecurityKey": { + "message": "Lire la clé de sécurité" + }, + "awaitingSecurityKeyInteraction": { + "message": "En attente de l'interaction de la clé de sécurité..." + }, "webAuthnNotSupported": { "message": "WebAuthn n'est pas pris en charge dans ce navigateur." }, @@ -5545,7 +5806,7 @@ "message": "Les comptes existants avec les mots de passe principaux exigeront que les membres s'inscrivent d'eux-mêmes avant que les administrateurs puissent récupérer leurs comptes. L'inscription automatique activera la récupération du compte pour les nouveaux membres." }, "accountRecoverySingleOrgRequirementDesc": { - "message": "La politique Entreprise d'organisation unique doit être activée avant d'activer cette politique." + "message": "La politique de sécurité d'organisation unique Entreprise doit être activée avant d'activer cette politique de sécurité." }, "resetPasswordPolicyAutoEnroll": { "message": "Inscription automatique" @@ -5655,6 +5916,20 @@ "error": { "message": "Erreur" }, + "decryptionError": { + "message": "Erreur de déchiffrement" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden n'a pas pu déchiffrer le(s) élément(s) du coffre listé(s) ci-dessous." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contacter Customer Success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "pour éviter des pertes de données supplémentaires.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Gérer les utilisateurs exige également d'avoir l'autorisation de gérer la restauration de compte" }, @@ -6085,7 +6360,7 @@ "message": "Il vous a été offert un plan organisation Bitwarden \"Families\" gratuit. Pour continuer, vous devez vous connecter au compte qui a reçu l'offre." }, "sponsoredFamiliesAcceptFailed": { - "message": "Impossible d'accepter l'offre. Veuillez renvoyer le courriel de l'offre depuis votre compte d'entreprise et réessayer." + "message": "Impossible d'accepter l'offre. Veuillez renvoyer le courriel de l'offre depuis votre compte Entreprise et réessayer." }, "sponsoredFamiliesAcceptFailedShort": { "message": "Impossible d'accepter l'offre. $DESCRIPTION$", @@ -6476,7 +6751,7 @@ } }, "accessDenied": { - "message": "Accès Refusé. Vous n'avez pas la permission de voir cette page." + "message": "Accès Refusé. Vous n'avez pas l'autorisation d'afficher cette page." }, "masterPassword": { "message": "Mot de passe principal" @@ -6516,15 +6791,6 @@ "message": "Générateur", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "Que souhaitez-vous générer ?" - }, - "passwordType": { - "message": "Type de mot de passe" - }, - "regenerateUsername": { - "message": "Régénérer le Nom d'Utilisateur" - }, "generateUsername": { "message": "Générer le Nom d'Utilisateur" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Type de Nom d'Utilisateur" - }, "plusAddressedEmail": { "message": "Courriel Adressé Plus", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Utilisez la boîte de réception du collecteur (catch-all) configurée de votre domaine." }, + "useThisEmail": { + "message": "Utiliser ce courriel" + }, "random": { "message": "Aléatoire", "description": "Generates domain-based username using random letters" @@ -6612,7 +6878,7 @@ "message": "Service" }, "unknownCipher": { - "message": "Élément inconnu, vous devrez peut-être vous connecter avec un autre compte pour accéder à cet élément." + "message": "Élément inconnu, vous devrez peut-être demander l'autorisation d'accéder à cet élément." }, "cannotSponsorSelf": { "message": "Vous ne pouvez pas réclamer pour le compte actif. Saisissez un courriel différent." @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ a refusé votre demande. Veuillez contacter votre fournisseur de service pour assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ a refusé votre demande : $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Impossible d'obtenir l'ID du compte de courriel masqué $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Nom d'hôte", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "Jetons d'accès API" - }, "deviceVerification": { "message": "Vérification de l'Appareil" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "L'authentification à double facteur DUO est requise pour votre compte." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "L'authentification à deux facteurs est requise pour votre compte. Suivez les étapes ci-dessous pour terminer la connexion." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Suivez les étapes ci-dessous pour terminer de vous connecter." + }, "launchDuo": { "message": "Lancer DUO" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Nombre d’utilisateurs" }, - "loggingInAs": { - "message": "Connexion en tant que" - }, - "notYou": { - "message": "Ce n'est pas vous ?" - }, "pickAnAvatarColor": { "message": "Choisissez une couleur d'avatar" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "Aucune collection" }, - "canView": { - "message": "Peut voir" - }, - "canViewExceptPass": { - "message": "Peut voir, sauf les mots de passe" - }, - "canEdit": { - "message": "Peut modifier" - }, - "canEditExceptPass": { - "message": "Peut modifier, sauf les mots de passe" - }, "noCollectionsAdded": { "message": "Pas de collections ajoutées" }, @@ -7655,10 +7930,10 @@ } }, "verificationRequiredForActionSetPinToContinue": { - "message": "Vérification requise pour cette action. Définissez un code PIN pour continuer." + "message": "Vérification requise pour cette action. Définissez un NIP pour continuer." }, "setPin": { - "message": "Définir le code PIN" + "message": "Définir le NIP" }, "verifyWithBiometrics": { "message": "Vérifier par biométrie" @@ -7676,7 +7951,7 @@ "message": "Utiliser le mot de passe principal" }, "usePin": { - "message": "Utiliser le code PIN" + "message": "Utiliser un NIP" }, "useBiometrics": { "message": "Utiliser la biométrie" @@ -7700,7 +7975,7 @@ "message": "Sélectionner les groupes" }, "userPermissionOverrideHelperDesc": { - "message": "Les permissions définies pour un membre remplaceront les permissions définies par le groupe de ce membre." + "message": "Les autorisations définies pour un membre remplaceront les autorisations définies par le groupe de ce membre." }, "noMembersOrGroupsAdded": { "message": "Aucun membre ou groupe ajouté" @@ -7745,7 +8020,7 @@ } }, "teamsStarterPlanInvLimitReachedManageBilling": { - "message": "Les abonnements Teams Starter peuvent compter jusqu'à $SEATCOUNT$ membres. Passez à une offre payante pour inviter plus de membres.", + "message": "Les abonnements Équipes Essentiel peuvent compter jusqu'à $SEATCOUNT$ membres. Mettez à niveau vers une offre payante pour inviter plus de membres.", "placeholders": { "seatcount": { "content": "$1", @@ -7754,7 +8029,7 @@ } }, "teamsStarterPlanInvLimitReachedNoManageBilling": { - "message": "Les abonnements Teams Starter peuvent compter jusqu'à $SEATCOUNT$ membres. Contacter le propriétaire de votre organisation pour améliorer votre abonnements et inviter plus de membres.", + "message": "Les abonnements Équipes Essentiel peuvent compter jusqu'à $SEATCOUNT$ membres. Contacter le propriétaire de votre organisation pour mettre à niveau votre abonnements et inviter plus de membres.", "placeholders": { "seatcount": { "content": "$1", @@ -8087,7 +8362,7 @@ "message": "Exiger que les membres existants changent leurs mots de passe" }, "smProjectDeleteAccessRestricted": { - "message": "Vous n'avez pas les droits pour supprimer ce projet", + "message": "Vous n'avez pas les autorisations pour supprimer ce projet", "description": "The individual description shown to the user when the user doesn't have access to delete a project." }, "smProjectsDeleteBulkConfirmation": { @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Demander l'approbation de l'administrateur" }, - "approveWithMasterPassword": { - "message": "Approuver avec le mot de passe principal" - }, "trustedDeviceEncryption": { "message": "Chiffrement de l'appareil de confiance" }, "trustedDevices": { "message": "Appareils de confiance" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Une fois authentifiés, les membres déchiffreront les données du coffre à l'aide d'une clé enregistrée sur leur appareil. La", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Les membres n'auront pas besoin d'un mot de passe maître lors de la connexion avec SSO. Le mot de passe principal est remplacé par une clé de chiffrement stockée sur le périphérique, ce qui rend ce périphérique fiable. Le premier appareil avec lequel un membre créera son compte et se connectera sera fiable. Les nouveaux appareils devront être approuvés par un périphérique de confiance existant ou par un administrateur. La", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "d'organisation unique,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" - }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescLink1": { "message": "politique", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "SSO requise et", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "d'organisation unique,", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescLink2": { "message": "la politique", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "la politique d'administration de la restauration du compte", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "SSO requise et", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "avec inscription automatique sera activée quand cette option est utilisée.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "la politique", + "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.'" + }, + "memberDecryptionOptionTdeDescPart4": { + "message": "d'administration de récupération de compte seront activées si cette option est utilisée.", + "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": "Les autorisations de votre organisation ont été mises à jour, vous obligeant à définir un mot de passe principal.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approuver la demande" }, + "deviceApproved": { + "message": "Appareil approuvé" + }, + "deviceRemoved": { + "message": "Appareil supprimé" + }, + "removeDevice": { + "message": "Supprimer l'appareil" + }, + "removeDeviceConfirmation": { + "message": "Êtes-vous sûr de vouloir supprimer cet appareil ?" + }, "noDeviceRequests": { "message": "Aucune demande de l'appareil" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Votre demande a été envoyée à votre administrateur." }, - "youWillBeNotifiedOnceApproved": { - "message": "Vous serez notifié une fois approuvé." - }, "troubleLoggingIn": { "message": "Problème pour vous connecter ?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limiter la suppression de collections aux propriétaires et administrateurs" }, + "limitItemDeletionDesc": { + "message": "Limite la suppression de l'élément aux membres avec l'autorisation Peut gérer" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Les propriétaires et les administrateurs peuvent gérer toutes les collections et tous les éléments" }, @@ -8494,9 +8778,6 @@ "message": "URL du serveur auto-hébergé", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Domaine de l'alias" - }, "alreadyHaveAccount": { "message": "Vous avez déjà un compte ?" }, @@ -8507,7 +8788,7 @@ "message": "Sauter directement au contenu" }, "managePermissionRequired": { - "message": "Au moins un membre ou un groupe doit avoir la permission peut gérer." + "message": "Au moins un membre ou un groupe doit avoir l'autorisation peut gérer." }, "typePasskey": { "message": "Passkey" @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "Vous n'avez pas accès à la gestion de cette collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "\"Peut Gérer les Permissions\" manquant" + "grantManageCollectionWarningTitle": { + "message": "Autorisations pour Gérer la Collection manquantes" }, - "grantAddAccessCollectionWarning": { - "message": "Accorder \"Peut Gérer les Permissions\" pour permettre une gestion complète de la collection, y compris la suppression de la collection." + "grantManageCollectionWarning": { + "message": "Accorde les autorisations pour Gérer la collection pour permettre la gestion complète de la collection incluant sa suppression." }, "grantCollectionAccess": { "message": "Accorder l'accès à cette collection aux groupes ou aux membres." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configurez la gestion des appareils pour Bitwarden en utilisant le guide d'implémentation pour votre plateforme." }, + "deviceIdMissing": { + "message": "L'identification de l'appareil est manquante" + }, + "deviceTypeMissing": { + "message": "Le type d'appareil est manquant" + }, + "deviceCreationDateMissing": { + "message": "La date de création de l'appareil est manquante" + }, + "desktopRequired": { + "message": "Ordinateur de bureau requis" + }, + "reopenLinkOnDesktop": { + "message": "Réouvrez ce lien à partir de votre courriel sur un bureau." + }, "integrationCardTooltip": { "message": "Lancez le guide d'implémentation $INTEGRATION$.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "mois par membre" }, + "monthPerMemberBilledAnnually": { + "message": "mois par membre facturés annuellement" + }, "seats": { "message": "Licences" }, @@ -9272,16 +9571,16 @@ "message": "Informations sur les taxes mises à jour" }, "billingInvalidTaxIdError": { - "message": "Invalid tax ID, if you believe this is an error please contact support." + "message": "ID de taxe non valide, si vous pensez qu'il s'agit d'une erreur, veuillez contacter le support." }, "billingTaxIdTypeInferenceError": { - "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + "message": "Nous n'avons pu valider votre ID de taxes. Si vous pensez que c'est une erreur, veuillez contacter le support s'il-vous-plaît." }, "billingPreviewInvalidTaxIdError": { - "message": "Invalid tax ID, if you believe this is an error please contact support." + "message": "ID de taxe non valide, si vous pensez qu'il s'agit d'une erreur, veuillez contacter le support." }, "billingPreviewInvoiceError": { - "message": "An error occurred while previewing the invoice. Please try again later." + "message": "Une erreur s'est produite lors de la prévisualisation de la facture. Veuillez réessayer plus tard." }, "unverified": { "message": "Non vérifié" @@ -9324,7 +9623,7 @@ "message": "(Aucune collection)" }, "memberAccessReportNoCollectionPermission": { - "message": "(Aucune permission de collection)" + "message": "(Aucune Autorisation de Collection)" }, "memberAccessReportNoGroup": { "message": "(Aucun groupe)" @@ -9366,7 +9665,7 @@ "message": " Contactez le Support Client pour rétablir votre abonnement." }, "secretPeopleDescription": { - "message": "Autoriser les groupes ou les personnes à accéder à ce secret. Les permissions définies pour les personnes remplaceront les permissions définies par les groupes." + "message": "Autoriser les groupes ou les personnes à accéder à ce secret. Les autorisations définies pour les personnes remplaceront les autorisations définies par les groupes." }, "secretPeopleEmptyMessage": { "message": "Ajouter des personnes ou des groupes pour partager l'accès à ce secret" @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "En savoir plus sur l'API de Bitwarden" }, + "fileSend": { + "message": "Send d'un fichier" + }, "fileSends": { "message": "Déposer des Sends" }, + "textSend": { + "message": "Send d'un texte" + }, "textSends": { "message": "Texter des Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Algorithme de clé" }, + "sshPrivateKey": { + "message": "Clé privée" + }, + "sshPublicKey": { + "message": "Clé publique" + }, + "sshFingerprint": { + "message": "Empreinte digitale" + }, "sshKeyFingerprint": { "message": "Empreinte digitale" }, @@ -9774,10 +10088,6 @@ "message": "Inclure des caractères spéciaux", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Ajouter une pièce jointe" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Code descripteur" }, + "cannotRemoveViewOnlyCollections": { + "message": "Vous ne pouvez pas supprimer des collections avec les autorisations d'affichage uniquement : $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Avis important" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Retirer des membres" }, + "devices": { + "message": "Appareils" + }, + "deviceListDescription": { + "message": "Votre compte a été connecté à chacun des appareils ci-dessous. Si vous ne reconnaissez pas un appareil, supprimez-le maintenant." + }, + "deviceListDescriptionTemp": { + "message": "Votre compte a été connecté à chacun des appareils ci-dessous." + }, "claimedDomains": { "message": "Domaines réclamés" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Le nom de l'organisation ne doit pas dépasser 50 caractères." }, - "resellerRenewalWarning": { - "message": "Votre abonnement sera renouvelé bientôt. Pour assurer un service sans interruption, contactez $RESELLER$ pour confirmer votre renouvellement avant $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "Le mot de passe que vous avez entré est incorrect." + }, + "importSshKey": { + "message": "Importer" + }, + "confirmSshKeyPassword": { + "message": "Confirmez le mot de passe" + }, + "enterSshKeyPasswordDesc": { + "message": "Entrez le mot de passe pour la clé SSH." + }, + "enterSshKeyPassword": { + "message": "Entrer le mot de passe" + }, + "invalidSshKey": { + "message": "La clé SSH est invalide" + }, + "sshKeyTypeUnsupported": { + "message": "Le type de clé SSH n'est pas pris en charge" + }, + "importSshKeyFromClipboard": { + "message": "Importer une clé depuis le presse-papiers" + }, + "sshKeyImported": { + "message": "Clé SSH importée avec succès" + }, + "copySSHPrivateKey": { + "message": "Copier la clé privée" + }, + "openingExtension": { + "message": "Ouverture de l'extension de navigateur Bitwarden" + }, + "somethingWentWrong": { + "message": "Quelquechose n'a pas fonctionné..." + }, + "openingExtensionError": { + "message": "Nous avons eu des difficultés à ouvrir l'extension de navigateur Bitwarden. Cliquez sur le bouton pour l'ouvrir maintenant." + }, + "openExtension": { + "message": "Ouvrir l'extension" + }, + "doNotHaveExtension": { + "message": "Vous n'avez pas l'extension de navigateur Bitwarden?" + }, + "installExtension": { + "message": "Installer l'extension" + }, + "openedExtension": { + "message": "Extension de navigateur ouverte" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Ouverture réussie de l'extension du navigateur Bitwarden. Vous pouvez maintenant revoir vos mots de passe à risque." + }, + "openExtensionManuallyPart1": { + "message": "Nous avons eu des difficultés à ouvrir l'extension de navigateur Bitwarden. Ouvrez l'icône Bitwarden", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "de la barre d'outils.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Votre abonnement sera renouvelé bientôt. Pour assurer un service ininterrompu, contactez $RESELLER$ pour confirmer votre renouvellement avant le $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "Une facture pour votre abonnement a été émise sur $ISSUED_DATE$. Pour assurer un service sans interruption, contactez $RESELLER$ pour confirmer votre renouvellement avant $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "Une facture pour votre abonnement a été émise le $ISSUED_DATE$. Pour assurer un service ininterrompu, contactez $RESELLER$ pour confirmer votre renouvellement avant le $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "La facture de votre abonnement n'a pas été payée. Pour assurer un service sans interruption, contactez $RESELLER$ pour confirmer votre renouvellement avant $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "La facture de votre abonnement n'a pas été payée. Pour assurer un service ininterrompu, contactez $RESELLER$ pour confirmer votre renouvellement avant $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "L'abonnement à l'organisation a été redémarré" + }, + "restartSubscription": { + "message": "Redémarrez votre abonnement" + }, + "suspendedManagedOrgMessage": { + "message": "Contactez $PROVIDER$ pour obtenir de l'aide.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Les administrateurs ont maintenant la possibilité de supprimer les comptes de membre qui appartiennent à un domaine revendiqué." + }, + "deleteManagedUserWarningDesc": { + "message": "Cette action supprimera le compte du membre, incluant tous les éléments de son coffre. Cela remplace l'action précédente de Supprimer." + }, + "deleteManagedUserWarning": { + "message": "Supprimer est une nouvelle action !" + }, + "seatsRemaining": { + "message": "Vous avez $REMAINING$ places restantes sur $TOTAL$ attribuées à cette organisation. Contactez votre fournisseur pour gérer votre abonnement.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Organisation existante" + }, + "selectOrganizationProviderPortal": { + "message": "Sélectionnez une organisation à ajouter à votre Portail fournisseur." + }, + "noOrganizations": { + "message": "Il n'y a aucune organisation à lister" + }, + "yourProviderSubscriptionCredit": { + "message": "Votre abonnement à un fournisseur recevra un crédit pour tout temps restant dans l'abonnement de l'organisation." + }, + "doYouWantToAddThisOrg": { + "message": "Voulez-vous ajouter cette organisation à $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Organisation existante ajoutée" + }, + "assignedExceedsAvailable": { + "message": "Les places assignées dépassent les places disponibles." + }, + "changeAtRiskPassword": { + "message": "Changer le mot de passe à risque" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Supprimer Déverrouiller avec un NIP" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Ne pas autoriser les membres à déverrouiller leur compte avec un NIP." + }, + "limitedEventLogs": { + "message": "Les plans $PRODUCT_TYPE$ n'ont pas accès aux journaux d'événements réels", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Obtenez un accès complet aux journaux d'événements de l'organisation en mettant à niveau vers un plan Équipes ou Entreprise." + }, + "upgradeEventLogTitle": { + "message": "Mettez à niveau pour les données du journal des événements réels" + }, + "upgradeEventLogMessage": { + "message": "Ces événements sont des exemples et ne reflètent pas les événements réels au sein de votre organisation Bitwarden." + }, + "cannotCreateCollection": { + "message": "Les organisations gratuites peuvent avoir jusqu'à 2 collections. Passez à une offre payante pour ajouter plus de collections." } } diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index 986350b0637..38a94f9307f 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Critical applications" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "Que tipo de elemento é este?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Notas" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Nota" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Drag to sort" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Text" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Edit folder" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "Base domain", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Nome do elemento" }, - "cannotRemoveViewOnlyCollections": { - "message": "Non podes eliminar coleccións con permisos de só lectura: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "ex.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filter" }, - "moveSelectedToOrg": { - "message": "Move selected to organization" - }, "deleteSelected": { "message": "Delete selected" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "Non" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "Rexístrate ou crea unha nova conta para acceder ó teu baúl." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Submit" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Lost access to all of your two-step login providers? Use your recovery code to turn off all two-step login providers from your account." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Migrated from FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "Email" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." }, - "moveManyToOrgDesc": { - "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved." - }, "collectionsDesc": { "message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Verification code (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Regenerate password" - }, "length": { "message": "Length" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Danger zone" }, - "dangerZoneDesc": { - "message": "Careful, these actions are not reversible!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Deauthorize sessions" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Proceeding will also log you out of your current session, requiring you to log back in. You will also be prompted for two-step login again, if set up. Active sessions on other devices may continue to remain active for up to one hour." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "All sessions deauthorized" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Setting up two-step login can permanently lock you out of your Bitwarden account. A recovery code allows you to access your account in the event that you can no longer use your normal two-step login provider (example: you lose your device). Bitwarden support will not be able to assist you if you lose access to your account. We recommend you write down or print the recovery code and keep it in a safe place." }, + "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." + }, "viewRecoveryCode": { "message": "View recovery code" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Manage" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Turn off" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Revoke access" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "This two-step login provider is active on your account." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "There was a problem reading the security key. Try again." }, - "twoFactorWebAuthnWarning": { - "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used. Supported platforms:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Web vault and browser extensions on a desktop/laptop with a WebAuthn supported browser (Chrome, Opera, Vivaldi, or Firefox with FIDO U2F turned on)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Your Bitwarden two-step login recovery code" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "If you cannot access your account through your normal two-step login methods, you can use your two-step login recovery code to turn off all two-step providers on your account." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Recover account two-step login" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Set requirements for master password strength." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Require two-step login" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Set requirements for password generator." }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." - }, "masterPasswordPolicyInEffect": { "message": "One or more organization policies require your master password to meet the following requirements:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Organization owners and admins are exempt from this policy's enforcement." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "File" }, "sendTypeText": { "message": "Text" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Delete Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Are you sure you want to delete this Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "What type of Send is this?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maximum access count" }, - "maxAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Disabled" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Are you sure you want to remove the password?" }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, - "disableThisSend": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "All Sends" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Pending deletion" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Expired" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "The following organization policies are currently in effect:" - }, - "sendDisableHideEmailInEffect": { - "message": "Users are not allowed 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." - }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Remove individual ownership for organization users" }, - "textHiddenByDefault": { - "message": "When accessing the Send, hide the text by default", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to Send." - }, - "sendFileDesc": { - "message": "The file you want to Send." - }, - "copySendLinkOnSave": { - "message": "Copy the link to share this Send to my clipboard upon save." - }, - "sendLinkLabel": { - "message": "Send link", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "To verify your 2FA please click the button below." }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn is not supported in this browser." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, - "regenerateUsername": { - "message": "Regenerate username" - }, "generateUsername": { "message": "Generate username" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Username type" - }, "plusAddressedEmail": { "message": "Plus addressed email", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Hostname", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API access token" - }, "deviceVerification": { "message": "Device verification" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Number of users" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "pickAnAvatarColor": { "message": "Pick an avatar color" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "No collection" }, - "canView": { - "message": "Can view" - }, - "canViewExceptPass": { - "message": "Can view, except passwords" - }, - "canEdit": { - "message": "Can edit" - }, - "canEditExceptPass": { - "message": "Can edit, except passwords" - }, "noCollectionsAdded": { "message": "No collections added" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domain" - }, "alreadyHaveAccount": { "message": "Already have an account?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "Non podes eliminar coleccións con permisos de só lectura: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index 9c4ef530721..4df39c8496f 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -1,24 +1,27 @@ { "allApplications": { - "message": "All applications" + "message": "כל היישומים" }, "criticalApplications": { - "message": "Critical applications" + "message": "יישומים קריטיים" + }, + "noCriticalAppsAtRisk": { + "message": "אין יישומים קריטיים בסיכון" }, "accessIntelligence": { - "message": "Access Intelligence" + "message": "גישה למודיעין" }, "riskInsights": { - "message": "Risk Insights" + "message": "תובנות סיכון" }, "passwordRisk": { - "message": "Password Risk" + "message": "סיכון סיסמה" }, "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." + "message": "סקור סיסמאות בסיכון (חלשות, חשופות, או משומשות) בין יישומים. בחר את היישומים הכי קריטיים שלך על מנת לתעדף פעולות אבטחה עבור המשתמשים שלך כדי לטפל בסיסמאות בסיכון." }, "dataLastUpdated": { - "message": "Data last updated: $DATE$", + "message": "הנתונים עודכנו לאחרונה: $DATE$", "placeholders": { "date": { "content": "$1", @@ -27,19 +30,19 @@ } }, "notifiedMembers": { - "message": "Notified members" + "message": "חברים שהודיעו להם" }, "revokeMembers": { - "message": "Revoke members" + "message": "בטל חברים" }, "restoreMembers": { - "message": "Restore members" + "message": "שחזר חברים" }, "cannotRestoreAccessError": { - "message": "Cannot restore organization access" + "message": "לא ניתן לשחזר גישת ארגון" }, "allApplicationsWithCount": { - "message": "All applications ($COUNT$)", + "message": "כל היישומים ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -48,10 +51,10 @@ } }, "createNewLoginItem": { - "message": "Create new login item" + "message": "צור פריט כניסה חדש" }, "criticalApplicationsWithCount": { - "message": "Critical applications ($COUNT$)", + "message": "יישומים קריטיים ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -60,7 +63,7 @@ } }, "notifiedMembersWithCount": { - "message": "Notified members ($COUNT$)", + "message": "חברים שהודיעו להם ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -69,7 +72,7 @@ } }, "noAppsInOrgTitle": { - "message": "No applications found in $ORG NAME$", + "message": "לא נמצאו יישומים אצל $ORG NAME$", "placeholders": { "org name": { "content": "$1", @@ -78,49 +81,88 @@ } }, "noAppsInOrgDescription": { - "message": "As users save logins, applications appear here, showing any at-risk passwords. Mark critical apps and notify users to update passwords." + "message": "ככל שמשתמשים שומרים כניסות, יישומים מופיעים כאן, ומוצגות כל הסיסמאות בסיכון. סמן יישומים קריטיים והודע למשתמשים לעדכן סיסמאות." }, "noCriticalAppsTitle": { - "message": "You haven't marked any applications as a Critical" + "message": "לא סימנת אף יישום כקריטי" }, "noCriticalAppsDescription": { - "message": "Select your most critical applications to discover at-risk passwords, and notify users to change those passwords." + "message": "בחר את היישומים הכי קריטיים שלך כדי לגלות סיסמאות בסיכון, ולהודיע למשתמשים לשנות את הסיסמאות הללו." }, "markCriticalApps": { - "message": "Mark critical apps" + "message": "סמן יישומים קריטיים" }, "markAppAsCritical": { - "message": "Mark app as critical" + "message": "סמן יישום כקריטי" }, "appsMarkedAsCritical": { - "message": "Apps marked as critical" + "message": "יישומים המסומנים כקריטיים" }, "application": { - "message": "Application" + "message": "יישום" }, "atRiskPasswords": { - "message": "At-risk passwords" + "message": "סיסמאות בסיכון" }, "requestPasswordChange": { - "message": "Request password change" + "message": "בקש שינוי סיסמה" }, "totalPasswords": { - "message": "Total passwords" + "message": "סה\"כ סיסמאות" }, "searchApps": { - "message": "Search applications" + "message": "חפש יישומים" }, "atRiskMembers": { - "message": "At-risk members" + "message": "חברים בסיכון" + }, + "atRiskMembersWithCount": { + "message": "חברים בסיכון ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "יישומים בסיכון ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "חברים אלה נכנסו אל יישומים עם סיסמאות חלשות, חשופות, או משומשות." + }, + "atRiskApplicationsDescription": { + "message": "ליישומים האלה יש סיסמאות חלשות, חשופות, או משומשות." + }, + "atRiskMembersDescriptionWithApp": { + "message": "החברים האלה נכנסו אל $APPNAME$ עם סיסמאות חלשות, חשופות, או משומשות.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } }, "totalMembers": { - "message": "Total members" + "message": "סה\"כ חברים" }, "atRiskApplications": { - "message": "At-risk applications" + "message": "יישומים בסיכון" }, "totalApplications": { - "message": "Total applications" + "message": "סה\"כ יישומים" + }, + "unmarkAsCriticalApp": { + "message": "בטל סימון כיישום קריטי" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "בוטל סימון יישום קריטי בהצלחה" }, "whatTypeOfItem": { "message": "מאיזה סוג פריט זה?" @@ -159,8 +201,11 @@ "notes": { "message": "הערות" }, + "privateNote": { + "message": "הערה פרטית" + }, "note": { - "message": "Note" + "message": "הערה" }, "customFields": { "message": "שדות מותאמים אישית" @@ -169,22 +214,22 @@ "message": "שם בעל הכרטיס" }, "loginCredentials": { - "message": "Login credentials" + "message": "אישורי כניסה" }, "personalDetails": { - "message": "Personal details" + "message": "פרטים אישיים" }, "identification": { - "message": "Identification" + "message": "הזדהות" }, "contactInfo": { - "message": "Contact info" + "message": "פרטי קשר" }, "cardDetails": { - "message": "Card details" + "message": "פרטי כרטיס" }, "cardBrandDetails": { - "message": "$BRAND$ details", + "message": "פרטי $BRAND$", "placeholders": { "brand": { "content": "$1", @@ -193,19 +238,19 @@ } }, "itemHistory": { - "message": "Item history" + "message": "היסטוריית פריט" }, "authenticatorKey": { - "message": "Authenticator key" + "message": "מפתח מאמת" }, "autofillOptions": { - "message": "Autofill options" + "message": "אפשרויות מילוי אוטומטי" }, "websiteUri": { - "message": "Website (URI)" + "message": "אתר אינטרנט (URI)" }, "websiteUriCount": { - "message": "Website (URI) $COUNT$", + "message": "אתר אינטרנט (URI) $COUNT$", "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", "placeholders": { "count": { @@ -215,16 +260,16 @@ } }, "websiteAdded": { - "message": "Website added" + "message": "אתר האינטרנט נוסף" }, "addWebsite": { - "message": "Add website" + "message": "הוסף אתר אינטרנט" }, "deleteWebsite": { - "message": "Delete website" + "message": "מחק אתר אינטרנט" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "ברירת מחדל ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -234,7 +279,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "הצג זיהוי התאמה $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -243,7 +288,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "הסתר זיהוי התאמה $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -252,7 +297,7 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "למלא אוטומטית בעת טעינת עמוד?" }, "number": { "message": "מספר" @@ -264,25 +309,25 @@ "message": "תוקף" }, "securityCode": { - "message": "קוד האבטחה (CVV)" + "message": "קוד אבטחה (CVV)" }, "securityCodeSlashCVV": { - "message": "Security code / CVV" + "message": "קוד אבטחה / CVV" }, "identityName": { - "message": "שם הזהות" + "message": "שם זהות" }, "company": { "message": "חברה" }, "ssn": { - "message": "מספר ביטוח לאומי" + "message": "מספר תעודת זהות" }, "passportNumber": { "message": "מספר דרכון" }, "licenseNumber": { - "message": "מספר רשיון" + "message": "מספר רישיון" }, "email": { "message": "אימייל" @@ -339,37 +384,37 @@ "message": "העלמה" }, "mx": { - "message": "Mx" + "message": "מיקס" }, "dr": { "message": "דוקטור" }, "cardExpiredTitle": { - "message": "Expired card" + "message": "כרטיס פג תוקף" }, "cardExpiredMessage": { - "message": "If you've renewed it, update the card's information" + "message": "אם חידשת אותו, עדכן את פרטי הכרטיס" }, "expirationMonth": { - "message": "תוקף אשראי - חודש" + "message": "חודש תפוגה" }, "expirationYear": { - "message": "תוקף אשראי - שנה" + "message": "שנת תפוגה" }, "authenticatorKeyTotp": { "message": "מפתח מאמת (TOTP)" }, "totpHelperTitle": { - "message": "Make 2-step verification seamless" + "message": "הפוך את האימות הדו־שלבי לחלק" }, "totpHelper": { - "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + "message": "Bitwarden יכול לאחסון ולמלא קודים של אימות דו־שלבי. העתק והדבק את המפתח לשדה זה." }, "totpHelperWithCapture": { - "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + "message": "Bitwarden יכול לאחסון ולמלא קודים של אימות דו־שלבי. בחר את סמל המצלמה כדי לצלם את הקוד QR המאמת של אתר זה, או העתק והדבק את המפתח לתוך שדה זה." }, "learnMoreAboutAuthenticators": { - "message": "Learn more about authenticators" + "message": "למד עוד על מאמתים" }, "folder": { "message": "תיקייה" @@ -383,6 +428,9 @@ "dragToSort": { "message": "גרור כדי למיין" }, + "dragToReorder": { + "message": "גרור כדי לסדר מחדש" + }, "cfTypeText": { "message": "טקסט" }, @@ -393,17 +441,17 @@ "message": "אמת או שקר" }, "cfTypeCheckbox": { - "message": "Checkbox" + "message": "תיבת סימון" }, "cfTypeLinked": { "message": "מקושר", "description": "This describes a field that is 'linked' (related) to another field." }, "fieldType": { - "message": "Field type" + "message": "סוג שדה" }, "fieldLabel": { - "message": "Field label" + "message": "תווית שדה" }, "remove": { "message": "הסר" @@ -412,19 +460,44 @@ "message": "לא מוקצה" }, "noneFolder": { - "message": "ללא תיקיה", + "message": "ללא תיקייה", "description": "This is the folder for uncategorized items" }, "selfOwnershipLabel": { - "message": "You", + "message": "את/ה", "description": "Used as a label to indicate that the user is the owner of an item." }, "addFolder": { - "message": "הוסף תיקיה" + "message": "הוסף תיקייה" }, "editFolder": { "message": "ערוך תיקייה" }, + "editWithName": { + "message": "ערוך $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "תיקייה חדשה" + }, + "folderName": { + "message": "שם תיקייה" + }, + "folderHintText": { + "message": "צור תיקייה מקוננת על ידי הוספת שם תיקיית האב ואחריו “/”. דוגמה: חברתי/פורומים" + }, + "deleteFolderPermanently": { + "message": "האם אתה בטוח שברצונך למחוק תיקייה זו לצמיתות?" + }, "baseDomain": { "message": "שם בסיס הדומיין", "description": "Domain name. Example: website.com" @@ -459,17 +532,17 @@ "message": "לעולם לא" }, "toggleVisibility": { - "message": "הצג או הסתר" + "message": "שנה מצב נראות" }, "toggleCollapse": { - "message": "הצג או קפל", + "message": "שנה מצב כיווץ", "description": "Toggling an expand/collapse state." }, "generatePassword": { "message": "צור סיסמה" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "צור ביטוי סיסמה" }, "checkPassword": { "message": "בדוק אם הסיסמה נחשפה." @@ -520,35 +593,35 @@ "message": "חפש מועדפים" }, "searchLogin": { - "message": "Search logins", + "message": "חפש כניסות", "description": "Search Login type" }, "searchCard": { - "message": "Search cards", + "message": "חפש כרטיסים", "description": "Search Card type" }, "searchIdentity": { - "message": "Search identities", + "message": "חפש זהויות", "description": "Search Identity type" }, "searchSecureNote": { - "message": "Search secure notes", + "message": "חפש הערות מאובטחות", "description": "Search Secure Note type" }, "searchVault": { "message": "חפש כספת" }, "searchMyVault": { - "message": "Search my vault" + "message": "חפש בכספת שלי" }, "searchOrganization": { - "message": "Search organization" + "message": "חפש בארגון" }, "searchMembers": { - "message": "Search members" + "message": "חפש חברים" }, "searchGroups": { - "message": "Search groups" + "message": "חפש קבוצות" }, "allItems": { "message": "כל הפריטים" @@ -569,13 +642,13 @@ "message": "זהות" }, "typeSecureNote": { - "message": "פתק מאובטח" + "message": "הערה מאובטחת" }, "typeSshKey": { - "message": "SSH key" + "message": "מפתח SSH" }, "typeLoginPlural": { - "message": "התחברויות" + "message": "כניסות" }, "typeCardPlural": { "message": "כרטיסים" @@ -584,7 +657,7 @@ "message": "זהויות" }, "typeSecureNotePlural": { - "message": "פתקים מאובטחים" + "message": "הערות מאובטחות" }, "folders": { "message": "תיקיות" @@ -605,7 +678,7 @@ "message": "שם מלא" }, "address": { - "message": "Address" + "message": "כתובת" }, "address1": { "message": "כתובת 1" @@ -638,7 +711,7 @@ "message": "בחר" }, "newItem": { - "message": "New item" + "message": "פריט חדש" }, "addItem": { "message": "הוסף פריט" @@ -650,7 +723,7 @@ "message": "הצג פריט" }, "newItemHeader": { - "message": "New $TYPE$", + "message": "$TYPE$ חדש", "placeholders": { "type": { "content": "$1", @@ -659,7 +732,7 @@ } }, "editItemHeader": { - "message": "Edit $TYPE$", + "message": "ערוך $TYPE$", "placeholders": { "type": { "content": "$1", @@ -668,7 +741,7 @@ } }, "viewItemType": { - "message": "View $ITEMTYPE$", + "message": "הצג $ITEMTYPE$", "placeholders": { "itemtype": { "content": "$1", @@ -677,26 +750,17 @@ } }, "new": { - "message": "New", + "message": "חדש", "description": "for adding new items" }, "item": { - "message": "Item" + "message": "פריט" }, "itemDetails": { - "message": "Item details" + "message": "פרטי הפריט" }, "itemName": { - "message": "Item name" - }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } + "message": "שם הפריט" }, "ex": { "message": "לדוגמא", @@ -709,7 +773,7 @@ "message": "שתף" }, "moveToOrganization": { - "message": "העברה לארגון" + "message": "העבר לארגון" }, "valueCopied": { "message": "השדה $VALUE$ הועתק לזיכרון", @@ -722,7 +786,7 @@ } }, "copySuccessful": { - "message": "Copy Successful" + "message": "העתקה מוצלחת" }, "copyValue": { "message": "העתק ערך", @@ -733,11 +797,11 @@ "description": "Copy password to clipboard" }, "copyPassphrase": { - "message": "Copy passphrase", + "message": "העתק ביטוי סיסמה", "description": "Copy passphrase to clipboard" }, "passwordCopied": { - "message": "Password copied" + "message": "הסיסמה הועתקה" }, "copyUsername": { "message": "העתק שם משתמש", @@ -756,7 +820,7 @@ "description": "Copy URI to clipboard" }, "copyCustomField": { - "message": "Copy $FIELD$", + "message": "העתק $FIELD$", "placeholders": { "field": { "content": "$1", @@ -765,58 +829,55 @@ } }, "copyWebsite": { - "message": "Copy website" + "message": "העתק אתר אינטרנט" }, "copyNotes": { - "message": "Copy notes" + "message": "העתק הערות" }, "copyAddress": { - "message": "Copy address" + "message": "העתק כתובת" }, "copyPhone": { - "message": "Copy phone" + "message": "העתק טלפון" }, "copyEmail": { - "message": "Copy email" + "message": "העתק דוא\"ל" }, "copyCompany": { - "message": "Copy company" + "message": "העתק חברה" }, "copySSN": { - "message": "Copy Social Security number" + "message": "העתק מספר תעודת זהות" }, "copyPassportNumber": { - "message": "Copy passport number" + "message": "העתק מספר דרכון" }, "copyLicenseNumber": { - "message": "Copy license number" + "message": "העתק מספר רישיון" }, "copyName": { - "message": "Copy name" + "message": "העתק שם" }, "me": { - "message": "Me" + "message": "אני" }, "myVault": { "message": "הכספת שלי" }, "allVaults": { - "message": "All vaults" + "message": "כל הכספות" }, "vault": { "message": "כספת" }, "vaults": { - "message": "Vaults" + "message": "כספות" }, "vaultItems": { - "message": "Vault items" + "message": "פריטי כספת" }, "filter": { - "message": "Filter" - }, - "moveSelectedToOrg": { - "message": "העבר בחירה לארגון" + "message": "מסנן" }, "deleteSelected": { "message": "מחק בחירה" @@ -834,7 +895,7 @@ "message": "הפעל" }, "newAttachment": { - "message": "צרף קובץ חדש" + "message": "הוסף צרופה חדשה" }, "deletedAttachment": { "message": "קובץ מצורף שנמחק" @@ -843,7 +904,7 @@ "message": "האם אתה בטוח שברצונך למחוק קובץ מצורף זה?" }, "attachmentSaved": { - "message": "הקובץ המצורף נשמר." + "message": "הצרופה נשמרה" }, "file": { "message": "קובץ" @@ -852,16 +913,16 @@ "message": "בחר קובץ." }, "maxFileSize": { - "message": "גודל הקובץ המירבי הוא 500 מגה." + "message": "גודל הקובץ המרבי הוא 500MB." }, "addedItem": { - "message": "פריט שהתווסף" + "message": "הפריט נוסף" }, "editedItem": { - "message": "פריט שנערך" + "message": "הפריט נשמר" }, "movedItemToOrg": { - "message": "$ITEMNAME$ הועבר ל־$ORGNAME$", + "message": "$ITEMNAME$ הועבר אל $ORGNAME$", "placeholders": { "itemname": { "content": "$1", @@ -873,17 +934,8 @@ } } }, - "movedItemsToOrg": { - "message": "פריטים נבחרים הועברו ל־$ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { - "message": "Items moved to $ORGNAME$", + "message": "פריטים הועברו אל $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -892,7 +944,7 @@ } }, "itemMovedToOrg": { - "message": "Item moved to $ORGNAME$", + "message": "פריט הועבר אל $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -907,7 +959,7 @@ "message": "מחק תיקייה" }, "deleteAttachment": { - "message": "מחק קובץ מצורף" + "message": "מחק צרופה" }, "deleteItemConfirmation": { "message": "האם אתה בטוח שברצונך למחוק פריט זה?" @@ -919,61 +971,61 @@ "message": "הפריטים נשלחו לסל המחזור" }, "movedItems": { - "message": "פריטים שהועברו" + "message": "פריטים הועברו" }, "overwritePasswordConfirmation": { "message": "האם אתה בטוח שברצונך לדרוס את הסיסמה הנוכחית?" }, "editedFolder": { - "message": "תיקיה שנערכה" + "message": "תיקייה נשמרה" }, "addedFolder": { - "message": "תיקיה שנוספה" + "message": "תיקייה נוספה" }, "deleteFolderConfirmation": { "message": "האם אתה בטוח שברצונך למחוק את התיקייה?" }, "deletedFolder": { - "message": "תיקיה שנמחקה" + "message": "תיקייה נמחקה" }, "editInfo": { - "message": "Edit info" + "message": "ערוך מידע" }, "access": { - "message": "Access" + "message": "גישה" }, "accessLevel": { - "message": "Access level" + "message": "רמת גישה" }, "accessing": { - "message": "Accessing" + "message": "ניגש אל" }, "loggedOut": { "message": "בוצעה יציאה" }, "loggedOutDesc": { - "message": "You have been logged out of your account." + "message": "יצאת מהחשבון שלך." }, "loginExpired": { "message": "תוקף החיבור שלך הסתיים." }, "restartRegistration": { - "message": "Restart registration" + "message": "התחל הרשמה מחדש" }, "expiredLink": { - "message": "Expired link" + "message": "קישור פג תוקף" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "נא להתחיל הרשמה מחדש או לנסות להיכנס." }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "ייתכן שכבר יש לך חשבון" }, "logOutConfirmation": { "message": "האם אתה בטוח שברצונך להתנתק?" }, "logOut": { - "message": "התנתק" + "message": "צא" }, "ok": { "message": "אישור" @@ -984,95 +1036,98 @@ "no": { "message": "לא" }, + "location": { + "message": "מיקום" + }, "loginOrCreateNewAccount": { "message": "צור חשבון חדש או התחבר כדי לגשת לכספת המאובטחת שלך." }, "loginWithDevice": { - "message": "Log in with device" + "message": "כניסה עם מכשיר" }, "loginWithDeviceEnabledNote": { - "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" + "message": "כניסה עם מכשיר צריכה להיות מוגדרת בהגדרות של היישום Bitwarden. צריך אפשרות אחרת?" }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "צריך אפשרות אחרת?" }, "loginWithMasterPassword": { - "message": "Log in with master password" + "message": "כניסה עם סיסמה ראשית" }, "readingPasskeyLoading": { - "message": "Reading passkey..." + "message": "קורא מפתח גישה..." }, "readingPasskeyLoadingInfo": { - "message": "Keep this window open and follow prompts from your browser." + "message": "השאר חלון זה פתוח ועקוב אחר ההנחיות מהדפדפן שלך." }, "useADifferentLogInMethod": { - "message": "Use a different log in method" + "message": "השתמש בשיטת כניסה אחרת" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "כניסה עם מפתח גישה" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "השתמש בכניסה יחידה" }, "welcomeBack": { - "message": "Welcome back" + "message": "ברוך שובך" }, "invalidPasskeyPleaseTryAgain": { - "message": "Invalid Passkey. Please try again." + "message": "מפתח גישה לא חוקי. נא לנסות שוב." }, "twoFactorForPasskeysNotSupportedOnClientUpdateToLogIn": { - "message": "2FA for passkeys is not supported. Update the app to log in." + "message": "אימות דו־גורמי (2FA) עבור מפתחות גישה אינו נתמך. עדכן את היישום כדי להיכנס." }, "loginWithPasskeyInfo": { - "message": "Use a generated passkey that will automatically log you in without a password. Biometrics, like facial recognition or fingerprint, or another FIDO2 security method will verify your identity." + "message": "השתמש במפתח גישה שנוצר אשר יכניס אותך באופן אוטומטי ללא סיסמה. זיהוי ביומטרי, כמו זיהוי פנים או טביעת אצבע, או שיטת אבטחה מסוג FIDO2 אחרת יאמתו את זהותך." }, "newPasskey": { - "message": "New passkey" + "message": "מפתח גישה חדש" }, "learnMoreAboutPasswordless": { - "message": "Learn more about passwordless" + "message": "למד עוד על ללא סיסמה" }, "creatingPasskeyLoading": { - "message": "Creating passkey..." + "message": "יוצר מפתח גישה..." }, "creatingPasskeyLoadingInfo": { - "message": "Keep this window open and follow prompts from your browser." + "message": "השאר חלון זה פתוח ועקוב אחר ההנחיות מהדפדפן שלך." }, "errorCreatingPasskey": { - "message": "Error creating passkey" + "message": "שגיאה ביצירת מפתח גישה" }, "errorCreatingPasskeyInfo": { - "message": "There was a problem creating your passkey." + "message": "הייתה בעיה ביצירת מפתח הגישה שלך." }, "passkeySuccessfullyCreated": { - "message": "Passkey successfully created!" + "message": "מפתח גישה נוצר בהצלחה!" }, "customPasskeyNameInfo": { - "message": "Name your passkey to help you identify it." + "message": "תן שם למפתח הגישה שלך כדי לעזור לך לזהות אותו." }, "useForVaultEncryption": { - "message": "Use for vault encryption" + "message": "השתמש עבור הצפנת כספת" }, "useForVaultEncryptionInfo": { - "message": "Log in and unlock on supported devices without your master password. Follow the prompts from your browser to finalize setup." + "message": "היכנס ובטל נעילה במכשירים נתמכים ללא הסיסמה הראשית שלך. עקוב אחר ההנחיות מהדפדפן שלך כדי לסיים את ההגדרה." }, "useForVaultEncryptionErrorReadingPasskey": { - "message": "Error reading passkey. Try again or uncheck this option." + "message": "שגיאה בקריאת מפתח גישה. נסה שוב או בטל את הסימון של אפשרות זו." }, "encryptionNotSupported": { - "message": "Encryption not supported" + "message": "הצפנה לא נתמכת" }, "enablePasskeyEncryption": { - "message": "Set up encryption" + "message": "הגדר הצפנה" }, "usedForEncryption": { - "message": "Used for encryption" + "message": "משמש עבור הצפנה" }, "loginWithPasskeyEnabled": { - "message": "Log in with passkey turned on" + "message": "כניסה עם מפתח גישה מופעלת" }, "passkeySaved": { - "message": "$NAME$ saved", + "message": "$NAME$ נשמר", "placeholders": { "name": { "content": "$1", @@ -1081,55 +1136,79 @@ } }, "passkeyRemoved": { - "message": "Passkey removed" + "message": "מפתח גישה הוסר" }, "removePasskey": { - "message": "Remove passkey" + "message": "הסר מפתח גישה" }, "removePasskeyInfo": { - "message": "If all passkeys are removed, you will be unable to log into new devices without your master password." + "message": "אם כל מפתחות הגישה מוסרים, לא תוכל להיכנס למכשירים חדשים ללא הסיסמה הראשית שלך." }, "passkeyLimitReachedInfo": { - "message": "Passkey limit reached. Remove a passkey to add another." + "message": "הגעת למגבלת מפתחות גישה. הסר מפתח גישה כדי להוסיף אחד נוסף." }, "tryAgain": { - "message": "Try again" + "message": "נסה שוב" }, "createAccount": { "message": "צור חשבון" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "חדש ב־Bitwarden?" }, "setAStrongPassword": { - "message": "Set a strong password" + "message": "הגדר סיסמה חזקה" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "Finish creating your account by setting a password" + "message": "סיים ליצור את החשבון שלך על ידי הגדרת סיסמה" }, "newAroundHere": { - "message": "New around here?" + "message": "חדש כאן?" }, "startTrial": { - "message": "Start trial" + "message": "התחל ניסיון" }, "logIn": { - "message": "התחבר" + "message": "היכנס" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "היכנס אל Bitwarden" + }, + "enterTheCodeSentToYourEmail": { + "message": "הזן את הקוד שנשלח לדוא\"ל שלך" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "הזן את הקוד מיישום המאמת שלך" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "לחץ על ה־YubiKey שלך כדי לאמת" }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "פסק זמן לאימות" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "זמן אימות ההפעלה תם. נא להתחיל מחדש את תהליך הכניסה." }, - "verifyIdentity": { - "message": "Verify your Identity" + "verifyYourIdentity": { + "message": "אמת את זהותך" + }, + "weDontRecognizeThisDevice": { + "message": "אנחנו לא מזהים את המכשיר הזה. הזן את הקוד שנשלח לדוא\"ל שלך כדי לאמת את זהותך." + }, + "continueLoggingIn": { + "message": "המשך להיכנס" + }, + "whatIsADevice": { + "message": "מהו מכשיר?" + }, + "aDeviceIs": { + "message": "מכשיר הוא התקנה ייחודית של היישום Bitwarden היכן שנכנסת. התקנה מחדש, ניקוי נתוני היישום, או ניקוי העוגיות שלך עלולים לגרום למכשיר להופיע מספר פעמים." }, "logInInitiated": { - "message": "Log in initiated" + "message": "הכניסה החלה" + }, + "logInRequestSent": { + "message": "בקשה נשלחה" }, "submit": { "message": "שלח" @@ -1150,7 +1229,7 @@ "message": "הסיסמה הראשית היא הסיסמה שבאמצעותה תיגש לכספת שלך. חשוב מאוד שלא תשכח את הסיסמה הזו. אין שום דרך לשחזר אותה במקרה ושכחת אותה." }, "masterPassImportant": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "הסיסמה הראשית שלך לא ניתנת לשחזור אם אתה שוכח אותה!" }, "masterPassHintDesc": { "message": "ניתן להשתמש ברמז לסיסמה הראשית אם שכחת אותה." @@ -1159,16 +1238,16 @@ "message": "הקלד שוב סיסמה ראשית" }, "masterPassHint": { - "message": "רמז לסיסמה ראשית (אופציונאלי)" + "message": "רמז לסיסמה הראשית (אופציונלי)" }, "newMasterPassHint": { - "message": "New master password hint (optional)" + "message": "רמז לסיסמה הראשית חדש (אופציונלי)" }, "masterPassHintLabel": { - "message": "רמז לסיסמה ראשית" + "message": "רמז לסיסמה הראשית" }, "masterPassHintText": { - "message": "If you forget your password, the password hint can be sent to your email. $CURRENT$/$MAXIMUM$ character maximum.", + "message": "אם תשכח את הסיסמה שלך, הרמז לסיסמה יכול להישלח לדוא\"ל שלך. $CURRENT$/$MAXIMUM$ תווים לכל היותר.", "placeholders": { "current": { "content": "$1", @@ -1184,22 +1263,16 @@ "message": "הגדרות" }, "accountEmail": { - "message": "Account email" + "message": "דוא\"ל חשבון" }, "requestHint": { - "message": "Request hint" + "message": "בקש רמז" }, "requestPasswordHint": { - "message": "Request password hint" + "message": "בקש רמז לסיסמה" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { - "message": "Enter your account email address and your password hint will be sent to you" - }, - "passwordHint": { - "message": "רמז לסיסמה" - }, - "enterEmailToGetHint": { - "message": "הכנס את כתובת האימייל שלך לקבלת רמז עבור הסיסמה הראשית." + "message": "הזן את כתובת דוא\"ל החשבון שלך והרמז לסיסמה שלך יישלח אליך" }, "getMasterPasswordHint": { "message": "הצג את הרמז לסיסמה הראשית" @@ -1211,13 +1284,13 @@ "message": "כתובת אימייל לא תקינה." }, "masterPasswordRequired": { - "message": "Master password is required." + "message": "נדרשת סיסמה ראשית." }, "confirmMasterPasswordRequired": { - "message": "Master password retype is required." + "message": "נדרשת הזנה מחדש של הסיסמה הראשית." }, "masterPasswordMinlength": { - "message": "Master password must be at least $VALUE$ characters long.", + "message": "סיסמה ראשית חייבת להיות לפחות באורך $VALUE$ תווים.", "description": "The Master Password must be at least a specific number of characters long.", "placeholders": { "value": { @@ -1233,13 +1306,13 @@ "message": "החשבון החדש שלך נוצר בהצלחה! כעת ניתן להתחבר למערכת." }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "החשבון החדש שלך נוצר!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "נכנסת!" }, "trialAccountCreated": { - "message": "Account created successfully." + "message": "החשבון נוצר בהצלחה." }, "masterPassSent": { "message": "שלחנו לך אימייל עם רמז לסיסמה הראשית." @@ -1248,16 +1321,16 @@ "message": "אירעה שגיאה לא צפויה." }, "expirationDateError": { - "message": "Please select an expiration date that is in the future." + "message": "נא לבחור תאריך תפוגה שהוא בעתיד." }, "emailAddress": { - "message": "כתובת אימייל" + "message": "כתובת דוא\"ל" }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "הכספת שלך נעולה" }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "החשבון שלך נעול" }, "uuid": { "message": "UUID" @@ -1282,7 +1355,7 @@ "message": "סיסמה ראשית שגויה" }, "invalidFilePassword": { - "message": "Invalid file password, please use the password you entered when you created the export file." + "message": "סיסמת קובץ שגויה, נא להשתמש בסיסמה שהזנת כשיצרת את קובץ הייצוא." }, "lockNow": { "message": "נעל עכשיו" @@ -1291,10 +1364,10 @@ "message": "אין פריטים להצגה ברשימה." }, "noPermissionToViewAllCollectionItems": { - "message": "You do not have permission to view all items in this collection." + "message": "אין לך הרשאה להציג את כל הפריטים באוסף זה." }, "youDoNotHavePermissions": { - "message": "You do not have permissions to this collection" + "message": "אין לך הרשאות לאוסף זה" }, "noCollectionsInList": { "message": "אין אוספים להצגה ברשימה." @@ -1306,7 +1379,7 @@ "message": "אין משתמשים להצגה ברשימה." }, "noMembersInList": { - "message": "There are no members to list." + "message": "אין חברים להצגה ברשימה." }, "noEventsInList": { "message": "אין אירועים להצגה ברשימה." @@ -1318,13 +1391,40 @@ "message": "אינך משויך לארגון. ניתן לשתף באופן מאובטח פריטים רק עם משתמשים אחרים בתוך ארגון." }, "notificationSentDevice": { - "message": "A notification has been sent to your device." + "message": "התראה נשלחה למכשיר שלך." + }, + "notificationSentDevicePart1": { + "message": "בטל נעילת Bitwarden במכשיר שלך או ב" + }, + "areYouTryingToAccessYourAccount": { + "message": "האם אתה מנסה לגשת לחשבון שלך?" + }, + "accessAttemptBy": { + "message": "ניסיון גישה על ידי $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "אשר גישה" + }, + "denyAccess": { + "message": "דחה גישה" + }, + "notificationSentDeviceAnchor": { + "message": "יישום רשת" + }, + "notificationSentDevicePart2": { + "message": "וודא שביטוי טביעת האצבע תואם את זה שלמטה לפני שתאשר." + }, + "notificationSentDeviceComplete": { + "message": "פתח את Bitwarden במכשיר שלך. וודא שביטוי טביעת האצבע תואם את זה שלמטה לפני שתאשר." }, "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" - }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "message": "התראה נשלחה למכשיר שלך" }, "versionNumber": { "message": "גרסה $VERSION_NUMBER$", @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "זכור אותי" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "אל תשאל אותי שוב במכשיר זה למשך 30 יום" + }, "sendVerificationCodeEmailAgain": { "message": "שלח שוב קוד אימות לאימייל" }, "useAnotherTwoStepMethod": { "message": "השתמש בשיטה אחרת עבור כניסה דו שלבית" }, + "selectAnotherMethod": { + "message": "בחר שיטה אחרת", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "השתמש בקוד השחזור שלך" + }, "insertYubiKey": { "message": "הכנס את ה-YubiKey אל כניסת ה-USB במחשבך, ואז גע בכפתור שלו." }, @@ -1372,38 +1482,41 @@ "message": "הכנס את מפתח האבטחה שלך אל כניסת ה-USB במחשבך. אם יש לו כפתור, לחץ עליו." }, "loginUnavailable": { - "message": "פרטי כניסה לא זמינים" + "message": "כניסה לא זמינה" }, "noTwoStepProviders": { - "message": "כניסה דו-שלבית פעילה בחשבון זה, אך אף אחד מספקי הכניסה הדו-שלבית לא נתמכים בדפדפן זה." + "message": "לחשבון זה מוגדרת כניסה דו־שלבית, עם זאת, אף אחד מהספקים הדו־שלביים שהוגדרו אינו נתמך על ידי דפדפן זה." }, "noTwoStepProviders2": { "message": "אנא השתמש בדפדפן נתמך (כמו לדוגמא Chrome) ו\\או הוסף ספק כניסה דו-שלבית הנתמך בדפדפן זה (כמו לדוגמא אפליקצית אימות)." }, "twoStepOptions": { - "message": "אפשרויות כניסה דו שלבית" + "message": "אפשרויות כניסה דו־שלבית" + }, + "selectTwoStepLoginMethod": { + "message": "בחר שיטת כניסה דו־שלבית" }, "recoveryCodeDesc": { - "message": "איבדת גישה לכל ספקי האימות הדו-שלבי שלך? השתמש בקוד האימות כדי לבטל את הספקים הקיימים מתוך החשבון שלך." + "message": "איבדת גישה לכל ספקי הכניסות הדו־שלביות שלך? השתמש בקוד השחזור שלך כדי לכבות את כל ספקי הכניסות הדו־שלביות מהחשבון שלך." }, "recoveryCodeTitle": { "message": "קוד שחזור" }, "authenticatorAppTitle": { - "message": "אפליקציית אימות" + "message": "יישום מאמת" }, "authenticatorAppDescV2": { - "message": "Enter a code generated by an authenticator app like Bitwarden Authenticator.", + "message": "הזן קוד שנוצר על ידי יישום מאמת כמו מאמת Bitwarden.", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP security key" + "message": "מפתח אבטחה OTP של Yubico" }, "yubiKeyDesc": { - "message": "השתמש בYubiKey עבור גישה לחשבון שלך. עובד עם YubiKey מסדרה 4, סדרה 5, ומכשירי NEO." + "message": "השתמש ב־YubiKey מסדרה 4, 5, או מכשיר NEO." }, "duoDescV2": { - "message": "Enter a code generated by Duo Security.", + "message": "הזן קוד שנוצר על ידי Duo Security.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { @@ -1411,25 +1524,28 @@ "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "u2fDesc": { - "message": "השתמש בכל מפתח אבטחה התומך בFIDO U2F עבור גישה לחשבונך." + "message": "השתמש בכל מפתח אבטחה תואם FIDO U2F כדי לגשת לחשבונך." }, "u2fTitle": { "message": "מפתח אבטחה FIDO U2F" }, "webAuthnTitle": { - "message": "Passkey" + "message": "מפתח גישה" }, "webAuthnDesc": { - "message": "Use your device's biometrics or a FIDO2 compatible security key." + "message": "השתמש בזיהוי ביומטרי של המכשיר שלך או במפתח אבטחה תואם FIDO2." }, "webAuthnMigrated": { - "message": "(Migrated from FIDO)" + "message": "(הועבר מ־FIDO)" + }, + "openInNewTab": { + "message": "פתח בכרטיסיה חדשה" }, "emailTitle": { "message": "אימייל" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "הזן קוד שנשלח לדוא\"ל שלך." }, "continue": { "message": "המשך" @@ -1441,16 +1557,13 @@ "message": "ארגונים" }, "moveToOrgDesc": { - "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." - }, - "moveManyToOrgDesc": { - "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved." + "message": "בחר ארגון שאליו ברצונך להעביר פריט זה. העברה אל ארגון מעבירה בעלות של הפריט אל אותו ארגון. לא תוכל להיות הבעלים הישיר של פריט זה ברגע שהוא הועבר." }, "collectionsDesc": { "message": "ערוך את האוסף המשותף של פריט זה. רק משתמשים מורשים מתוך הארגון יוכלו לראות פריט זה." }, "deleteSelectedItemsDesc": { - "message": "בחרת $COUNT$ פריט(ים) למחיקה. האם אתה בטוח שברצונך למחוק את כולם?", + "message": "$COUNT$ פריט(ים) ישלח(ו) לאשפה.", "placeholders": { "count": { "content": "$1", @@ -1459,7 +1572,7 @@ } }, "deleteSelectedCollectionsDesc": { - "message": "$COUNT$ collection(s) will be permanently deleted.", + "message": "$COUNT$ אוספ(ים) יימחק(ו) לצמיתות.", "placeholders": { "count": { "content": "$1", @@ -1468,10 +1581,10 @@ } }, "deleteSelectedConfirmation": { - "message": "Are you sure you want to continue?" + "message": "האם אתה בטוח שברצונך להמשיך?" }, "moveSelectedItemsDesc": { - "message": "Choose a folder that you would like to add the $COUNT$ selected item(s) to.", + "message": "בחר תיקייה שאליה תרצה להוסיף את $COUNT$ הפריט(ים) שבחרת.", "placeholders": { "count": { "content": "$1", @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "קוד אימות (TOTP)" }, @@ -1503,142 +1599,139 @@ "message": "העתק קוד אימות" }, "copyUuid": { - "message": "Copy UUID" + "message": "העתק UUID" }, "errorRefreshingAccessToken": { - "message": "Access Token Refresh Error" + "message": "שגיאת רענון אסימון גישה" }, "errorRefreshingAccessTokenDesc": { - "message": "No refresh token or API keys found. Please try logging out and logging back in." + "message": "לא נמצאו אסימון רענון או מפתחות API. נא לנסות לצאת ולהיכנס חזרה." }, "warning": { "message": "אזהרה" }, "confirmVaultExport": { - "message": "Confirm vault export" + "message": "אשר ייצוא כספת" }, "confirmSecretsExport": { - "message": "Confirm secrets export" + "message": "אשר ייצוא סודות" }, "exportWarningDesc": { "message": "הקובץ מכיל את פרטי הכספת שלך בפורמט לא מוצפן. מומלץ להעביר את הקובץ רק בדרכים מוצפנות, ומאוד לא מומלץ לשמור או לשלוח את הקובץ הזה בדרכים לא מוצפנות (כדוגמת סתם אימייל). מחק את הקובץ מיד לאחר שסיימת את השימוש בו." }, "exportSecretsWarningDesc": { - "message": "This export contains your secrets data in an unencrypted format. You should not store or send the exported file over unsecure channels (such as email). Delete it immediately after you are done using it." + "message": "ייצוא זה מכיל את נתוני הכספת שלך בפורמט לא מוצפן. אתה לא אמור לאחסן או לשלוח את הקובץ המיוצא דרך ערוצים לא מאובטחים (כמו דוא\"ל). מחק אותו מיד לאחר שסיימת להשתמש בו." }, "encExportKeyWarningDesc": { - "message": "This export encrypts your data using your account's encryption key. If you ever rotate your account's encryption key you should export again since you will not be able to decrypt this export file." + "message": "ייצוא זה מצפין את הנתונים שלך באמצעות מפתח ההצפנה של חשבונך. אם אי פעם תבצע סיבוב למפתח ההצפנה של חשבונך, תצטרך לייצא שוב משום שלא תוכל לפענח קובץ ייצוא זה." }, "encExportAccountWarningDesc": { - "message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." + "message": "מפתחות הצפנת חשבון הם ייחודים לכל חשבון משתמש של Bitwarden, לכן אינך יכול לייבא ייצוא מוצפן אל תוך חשבון אחר." }, "export": { - "message": "Export" + "message": "ייצא" }, "exportFrom": { - "message": "Export from" + "message": "ייצא מ־" }, "exportVault": { - "message": "יצוא כספת" + "message": "ייצא כספת" }, "exportSecrets": { - "message": "Export secrets" + "message": "ייצא סודות" }, "fileFormat": { "message": "פורמט קובץ" }, "fileEncryptedExportWarningDesc": { - "message": "This file export will be password protected and require the file password to decrypt." + "message": "קובץ ייצוא זה יהיה מוגן סיסמה ודורש את סיסמת הקובץ כדי לפענח." }, "exportPasswordDescription": { - "message": "This password will be used to export and import this file" + "message": "סיסמה זו תשמש כדי לייצא ולייבא קובץ זה" }, "confirmMasterPassword": { - "message": "Confirm master password" + "message": "אמת סיסמה ראשית" }, "confirmFormat": { - "message": "Confirm format" + "message": "אשר פורמט" }, "filePassword": { - "message": "File password" + "message": "סיסמת קובץ" }, "confirmFilePassword": { - "message": "Confirm file password" + "message": "אשר סיסמת קובץ" }, "accountRestrictedOptionDescription": { - "message": "Use your account encryption key, derived from your account's username and Master Password, to encrypt the export and restrict import to only the current Bitwarden account." + "message": "השתמש במפתח הצפנת החשבון שלך, הנגזר משם המשתמש והסיסמה הראשית של חשבונך, כדי להצפין את הייצוא ולהגביל את הייבוא רק לחשבון Bitwarden הנוכחי." }, "passwordProtectedOptionDescription": { - "message": "Set a file password to encrypt the export and import it to any Bitwarden account using the password for decryption." + "message": "הגדר סיסמת קובץ כדי להצפין את הייצוא ולייבא אותו לכל חשבון Bitwarden באמצעות הסיסמה לפענוח." }, "exportTypeHeading": { - "message": "Export type" + "message": "סוג ייצוא" }, "accountRestricted": { - "message": "Account restricted" + "message": "מוגבל חשבון" }, "passwordProtected": { - "message": "Password protected" + "message": "מוגן סיסמה" }, "filePasswordAndConfirmFilePasswordDoNotMatch": { - "message": "“File password” and “Confirm file password“ do not match." + "message": "\"סיסמת קובץ\" ו\"אשר סיסמת קובץ\" אינם תואמים." }, "confirmVaultImport": { - "message": "Confirm vault import" + "message": "אשר ייבוא כספת" }, "confirmVaultImportDesc": { - "message": "This file is password-protected. Please enter the file password to import data." + "message": "קובץ זה מוגן סיסמה. נא להזין את סיסמת הקובץ כדי לייבא נתונים." }, "exportSuccess": { - "message": "הוצאת המידע מהכספת שלך הסתיימה." + "message": "נתוני הכספת יוצאו" }, "passwordGenerator": { - "message": "יוצר הסיסמאות" + "message": "מחולל הסיסמאות" }, "minComplexityScore": { - "message": "ניקוד מורכבות מינימלי" + "message": "ציון מורכבות מינימלי" }, "minNumbers": { - "message": "מינימום ספרות" + "message": "מינימום מספרים" }, "minSpecial": { - "message": "מינימום תווים מיוחדים", + "message": "מינימום מיוחדים", "description": "Minimum special characters" }, "ambiguous": { - "message": "המנע מאותיות ותווים דומים", + "message": "הימנע מתווים דו־משמעיים", "description": "deprecated. Use avoidAmbiguous instead." }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "הימנע מתווים דו־משמעיים", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "צור סיסמה חדשה" - }, "length": { "message": "אורך" }, "passwordMinLength": { - "message": "Minimum password length" + "message": "אורך סיסמה מינימלי" }, "uppercase": { - "message": "Uppercase (A-Z)", + "message": "אותיות גדולות (A-Z)", "description": "deprecated. Use uppercaseLabel instead." }, "lowercase": { - "message": "Lowercase (a-z)", + "message": "אותיות קטנות (a-z)", "description": "deprecated. Use lowercaseLabel instead." }, "numbers": { - "message": "Numbers (0-9)", + "message": "מספרים (0-9)", "description": "deprecated. Use numbersLabel instead." }, "specialCharacters": { - "message": "Special characters (!@#$%^&*)" + "message": "תווים מיוחדים (*&^%$#@!)" }, "numWords": { - "message": "מספר מילים" + "message": "מספר המילים" }, "wordSeparator": { "message": "מפריד מילים" @@ -1648,48 +1741,48 @@ "description": "Make the first letter of a word uppercase." }, "includeNumber": { - "message": "כלול מספרים" + "message": "כלול מספר" }, "generatorPolicyInEffect": { - "message": "Enterprise policy requirements have been applied to your generator options.", + "message": "דרישות מדיניות ארגונית הוחלו על אפשרויות המחולל שלך.", "description": "Indicates that a policy limits the credential generator screen." }, "passwordHistory": { "message": "היסטוריית סיסמאות" }, "generatorHistory": { - "message": "Generator history" + "message": "היסטוריית מחולל" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "נקה היסטוריית מחולל" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "אם תמשיך, כל הרשומות יימחקו לצמיתות מהיסטוריית המחולל. האם אתה בטוח שברצונך להמשיך?" }, "noPasswordsInList": { "message": "אין סיסמאות להצגה ברשימה." }, "clearHistory": { - "message": "Clear history" + "message": "נקה היסטוריה" }, "nothingToShow": { - "message": "Nothing to show" + "message": "אין מה להראות" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "לא יצרת שום דבר לאחרונה" }, "clear": { "message": "נקה", "description": "To clear something out. Example: To clear browser history." }, "accountUpdated": { - "message": "החשבון עודכן" + "message": "החשבון נשמר" }, "changeEmail": { - "message": "החלף אימייל" + "message": "שנה דוא\"ל" }, "changeEmailTwoFactorWarning": { - "message": "Proceeding will change your account email address. It will not change the email address used for two-step login authentication. You can change this email address in the two-step login settings." + "message": "המשך התהליך ישנה את כתובת הדוא\"ל של החשבון שלך. זה לא ישנה את כתובת הדוא\"ל המשמשת עבור אימות כניסה דו־שלבית. אתה יכול לשנות את כתובת דוא\"ל זו בהגדרות הכניסה הדו־שלבית." }, "newEmail": { "message": "דוא\"ל חדש" @@ -1710,11 +1803,17 @@ "message": "בכדי להמשיך הסשן הנוכחי ינותק, ותדרש להזין את פרטי הכניסה החדשים. כל הסשנים הפעילים במכשירים אחרים ישארו פעילים עד שעה ממועד הכניסה החדשה." }, "emailChanged": { - "message": "כתובת האימייל שונתה" + "message": "דוא\"ל נשמר" }, "logBackIn": { "message": "אנא התחבר שוב." }, + "currentSession": { + "message": "הפעלה נוכחית" + }, + "requestPending": { + "message": "בקשה בהמתנה" + }, "logBackInOthersToo": { "message": "אנא התחבר שוב. אם אתה משתמש באפליקציות נוספות של Bitwarden, סגור את החיבור והתחבר שוב גם באפליקציות הללו." }, @@ -1722,7 +1821,7 @@ "message": "החלף סיסמה ראשית" }, "masterPasswordChanged": { - "message": "הסיסמה הראשית הוחלפה" + "message": "הסיסמה הראשית נשמרה" }, "currentMasterPass": { "message": "סיסמה ראשית נוכחית" @@ -1740,7 +1839,7 @@ "message": "אלגוריתם KDF" }, "kdfIterations": { - "message": "איטרציות KDF" + "message": "חזרות KDF" }, "kdfIterationsDesc": { "message": "קביעת ערך גבוה עבור מספר האיטרציות של KDF עוזרת בהגנה על הסיסמה הראשית שלך מפני תקיפת Brute force (תְּקִיפָה כּוֹחָנִית). אנו ממליצים להשתמש בערך $VALUE$ או ערך גבוה יותר.", @@ -1752,7 +1851,7 @@ } }, "kdfIterationsWarning": { - "message": "קביעת ערך גבוה מדי עבור מספר האיטרציות KDF עלול לגרום לבעיות ביצועים בזמן הכניסה (ובזמן ביטול הנעילה) לחשבון Bitwarden במכשירים בעלי מעבד חלש. אנו ממליצים שתעלה את הערך בקפיצות של $INCREMENT$ ובדוק את ההשפעה של הביצועים בכל המכשירים שלך.", + "message": "הגדרת חזרות KDF שלך לערך גבוה מדי עלולה לגרום לביצועים ירודים בעת כניסה אל (וביטול נעילת) Bitwarden במכשירים איטיים או ישנים יותר. אנו ממליצים להגדיל את הערך במרווחים של $INCREMENT$ ואז לבדוק את כל המכשירים שלך.", "placeholders": { "increment": { "content": "$1", @@ -1761,47 +1860,62 @@ } }, "kdfMemory": { - "message": "KDF memory (MB)", + "message": "זיכרון KDF (ב־MB)", "description": "Memory refers to computer memory (RAM). MB is short for megabytes." }, "argon2Warning": { - "message": "Setting your KDF iterations, memory, and parallelism too high could result in poor performance when logging into (and unlocking) Bitwarden on slower or older devices. We recommend changing these individually in small increments and then test all of your devices." + "message": "הגדרת חזרות, זיכרון, ומקבילות ה־KDF שלך לערכים גבוהים מדי עלולה לגרום לביצועים ירודים בעת כניסה אל (וביטול נעילת) Bitwarden במכשירים איטיים או ישנים יותר. אנו ממליצים לשנות אותם באופן נפרד במרווחים קטנים ואז לבדוק את כל המכשירים שלך." }, "kdfParallelism": { - "message": "KDF parallelism" + "message": "מקבילות KDF" }, "argon2Desc": { - "message": "Higher KDF iterations, memory, and parallelism can help protect your master password from being brute forced by an attacker." + "message": "ערכי חזרות, זיכרון, ומקבילות KDF גבוהים יותר יכולים לעזור להגן על הסיסמה הראשית מפני תקיפה כוחנית על ידי תוקף." }, "changeKdf": { "message": "שנה KDF" }, "encKeySettingsChanged": { - "message": "הגדרות מפתח ההצפנה השתנו" + "message": "הגדרות מפתח ההצפנה נשמרו" }, "dangerZone": { - "message": "אזור מסוכן" - }, - "dangerZoneDesc": { - "message": "זהירות, פעולות אלה לא ניתנות לביטול!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" + "message": "אזור סכנה" }, "deauthorizeSessions": { - "message": "בטל הרשאות סשנים" + "message": "בטל אישור הפעלות" }, "deauthorizeSessionsDesc": { "message": "מודאג אם השארת את החשבון שלך מחובר במכשיר אחר? המשך כאן להסרת ההרשאות של סשנים מכל המחשבים או המכשירים שהשתמשת בעבר. צעד אבטחה זה מומלץ אם השתמשת בעבר במחשב ציבורי או ששמרת את הסיסמה בטעות במכשיר שאינו שלך. כמו כן, צעד זה ינקה גם את כל הסיסמאות השמורות עבור סשנים שהשתמשו באימות דו-שלבי." }, "deauthorizeSessionsWarning": { - "message": "בכדי להמשיך הסשן הנוכחי ינותק, ותדרש להזין את פרטי הכניסה החדשים וגם את פרטי האימות הדו-שלבי, אם הוא מאופשר. כל הסשנים הפעילים במכשירים אחרים ישארו פעילים עד שעה ממועד הכניסה החדשה." + "message": "המשך התהליך יוציא אותך גם מההפעלה הנוכחית שלך, מה שידרוש ממך להיכנס חזרה. אתה גם תתבקש לבצע כניסה דו־שלבית שוב, אם מוגדרת. הפעלות פעילות במכשירים אחרים עלולות להישאר פעילות למשך עד שעה אחת." + }, + "newDeviceLoginProtection": { + "message": "כניסת מכשיר חדש" + }, + "turnOffNewDeviceLoginProtection": { + "message": "כבה הגנת כניסת מכשיר חדש" + }, + "turnOnNewDeviceLoginProtection": { + "message": "הפעל הגנת כניסת מכשיר חדש" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "המשך למטה כדי לכבות הודעות דוא\"ל של אימות ש־Bitwarden שולח כאשר אתה נכנס ממכשיר חדש." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "המשך למטה כדי ש־Bitwarden ישלח לך הודעות דוא\"ל של אימות כאשר אתה נכנס ממכשיר חדש." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "עם הגנת כניסת מכשיר חדש כבויה, כל אחד עם הסיסמה הראשית שלך יכול לגשת למכשיר שלך מכל מכשיר. כדי להגן על חשבונך ללא הודעות דוא\"ל של אימות, הגדר כניסה דו־שלבית." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "שינויי הגנת כניסת מכשיר חדש נשמרו" }, "sessionsDeauthorized": { - "message": "הוסרה ההרשאה מכל הסשנים" + "message": "כל אישורי ההפעלות בוטלו" }, "accountIsOwnedMessage": { - "message": "This account is owned by $ORGANIZATIONNAME$", + "message": "חשבון זה הוא בבעלות $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -1810,13 +1924,13 @@ } }, "purgeVault": { - "message": "מחק תוכן כספת" + "message": "טיהור כספת" }, "purgedOrganizationVault": { "message": "מחק תוכן כספת ארגונית." }, "vaultAccessedByProvider": { - "message": "Vault accessed by Provider." + "message": "בוצעה גישה לפריט על ידי ספק." }, "purgeVaultDesc": { "message": "המשך כאן בכדי למחוק את כל הפריטים והתיקיות שבכספת שלך. פריטים השייכים לארגון לא ימחקו." @@ -1828,13 +1942,13 @@ "message": "מחיקת תוכן הכספת היא סופית. פעולה זו היא בלתי הפיכה." }, "vaultPurged": { - "message": "המידע בכספת נמחק." + "message": "הכספת טוהרה." }, "deleteAccount": { "message": "מחק חשבון" }, "deleteAccountDesc": { - "message": "המשך כאן בכדי למחוק את החשבון שלך וכל המידע המשויך אליו." + "message": "המשך למטה כדי למחוק את החשבון שלך ואת כל נתוני הכספת." }, "deleteAccountWarning": { "message": "מחיקת החשבון היא פעולה בלתי הפיכה." @@ -1846,7 +1960,7 @@ "message": "חשבונך נסגר וכל המידע המשויך אליו נמחק." }, "deleteOrganizationWarning": { - "message": "Deleting your organization is permanent. It cannot be undone." + "message": "מחיקת הארגון שלך היא לצמיתות. לא ניתן לבטלה." }, "myAccount": { "message": "החשבון שלי" @@ -1858,36 +1972,36 @@ "message": "ייבא נתונים" }, "onboardingImportDataDetailsPartOne": { - "message": "If you don't have any data to import, you can create a ", + "message": "אם אין לך נתוני לייבא, אתה יכול ליצור ", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { - "message": "new item", + "message": "פריט חדש", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLoginLink": { - "message": "new login", + "message": "כניסה חדשה", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new login instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsPartTwoNoOrgs": { - "message": " instead.", + "message": " במקום.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." }, "onboardingImportDataDetailsPartTwoWithOrgs": { - "message": " instead. You may need to wait until your administrator confirms your organization membership.", + "message": " במקום. ייתכן שתצטרך לחכות עד שהמנהל שלך יאשר את החברות בארגון שלך.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, "importError": { - "message": "Import error" + "message": "שגיאת ייבוא" }, "importErrorDesc": { - "message": "There was a problem with the data you tried to import. Please resolve the errors listed below in your source file and try again." + "message": "הייתה בעיה עם הנתונים שניסית לייבא. נא לפתור את השגיאות למטה בקובץ המקור שלך ולנסות שוב." }, "importSuccess": { - "message": "נתונים יובאו בהצלחה אל תוך הכספת שלך." + "message": "הנתונים יובאו בהצלחה" }, "importSuccessNumberOfItems": { - "message": "A total of $AMOUNT$ items were imported.", + "message": "בסך הכל יובאו $AMOUNT$ פריטים.", "placeholders": { "amount": { "content": "$1", @@ -1896,10 +2010,10 @@ } }, "dataExportSuccess": { - "message": "Data successfully exported" + "message": "הנתונים יוצאו בהצלחה" }, "importWarning": { - "message": "You are importing data to $ORGANIZATION$. Your data may be shared with members of this organization. Do you want to proceed?", + "message": "אתה מייבא נתונים אל $ORGANIZATION$. ייתכן שהנתונים שלך ישותפו עם חברים של הארגון הזה. האם אתה רוצה להמשיך?", "placeholders": { "organization": { "content": "$1", @@ -1914,22 +2028,22 @@ "message": "לא יובא דבר." }, "importEncKeyError": { - "message": "Error decrypting the exported file. Your encryption key does not match the encryption key used export the data." + "message": "שגיאה בפענוח הקובץ המיוצא. מפתח ההצפנה שלך אינו תואם את מפתח ההצפנה המשמש לייצוא הנתונים." }, "destination": { - "message": "Destination" + "message": "יעד" }, "learnAboutImportOptions": { - "message": "Learn about your import options" + "message": "למד על אפשרויות הייבוא שלך" }, "selectImportFolder": { - "message": "Select a folder" + "message": "בחר תיקייה" }, "selectImportCollection": { - "message": "Select a collection" + "message": "בחר אוסף" }, "importTargetHint": { - "message": "Select this option if you want the imported file contents moved to a $DESTINATION$", + "message": "בחר באפשרות זו אם ברצונך להעביר את תוכן הקובץ המיובא אל $DESTINATION$", "description": "Located as a hint under the import target. Will be appended by either folder or collection, depending if the user is importing into an individual or an organizational vault.", "placeholders": { "destination": { @@ -1939,7 +2053,7 @@ } }, "importUnassignedItemsError": { - "message": "File contains unassigned items." + "message": "קובץ מכיל פריטים לא מוקצים." }, "selectFormat": { "message": "בחר את פורמט הקובץ לייבוא" @@ -1948,10 +2062,10 @@ "message": "בחר את הקובץ לייבוא" }, "chooseFile": { - "message": "Choose File" + "message": "בחר קובץ" }, "noFileChosen": { - "message": "No file chosen" + "message": "לא נבחר קובץ" }, "orCopyPasteFileContents": { "message": "או העתק\\הדבק את תוכן הקובץ ליבוא" @@ -1970,13 +2084,13 @@ "message": "אפשרויות" }, "preferences": { - "message": "Preferences" + "message": "העדפות" }, "preferencesDesc": { - "message": "Customize your web vault experience." + "message": "התאם אישית את חווית כספת הרשת שלך." }, "preferencesUpdated": { - "message": "Preferences saved" + "message": "העדפות נשמרו" }, "language": { "message": "שפה" @@ -1985,10 +2099,10 @@ "message": "שנה את השפה של כספת הרשת." }, "enableFavicon": { - "message": "Show website icons" + "message": "הצג סמלי אתר אינטרנט" }, "faviconDesc": { - "message": "Show a recognizable image next to each login." + "message": "הצג תמונה מוכרת ליד כל כניסה." }, "default": { "message": "ברירת מחדל" @@ -2000,10 +2114,10 @@ "message": "אם אתה משתמש באותם פרטי כניסה עבור אתרים שונים באותו דומיין, באפשרות לסמן את האתר כ\"שווה\". הערכים הרגילים שנוצרים על ידי Bitwarden מסומנים כדומיין \"גלובלי\"." }, "globalEqDomains": { - "message": "דומיינים גלובליים שווים" + "message": "דומיינים שקולים גלובליים" }, "customEqDomains": { - "message": "דומיינים שווים מותאמים אישית" + "message": "דומיינים שקולים מותאמים אישית" }, "exclude": { "message": "אל תכלול" @@ -2030,38 +2144,41 @@ } }, "domainsUpdated": { - "message": "הדומיינים עודכנו" + "message": "הדומיינים נשמרו" }, "twoStepLogin": { - "message": "התחברות בשני-שלבים" + "message": "כניסה דו־שלבית" }, "twoStepLoginEnforcement": { - "message": "Two-step Login Enforcement" + "message": "אכיפת כניסה דו־שלבית" }, "twoStepLoginDesc": { "message": "שפר את אבטחת החשבון שלך על ידי דרישת צעד נוסף עבור כל נסיון חיבור." }, "twoStepLoginTeamsDesc": { - "message": "Enable two-step login for your organization." + "message": "אפשר כניסה דו־שלבית עבור הארגון שלך." }, "twoStepLoginEnterpriseDescStart": { - "message": "Enforce Bitwarden Two-step Login options for members by using the ", + "message": "אכוף אפשרויות כניסה דו־שלבית של Bitwarden עבור חברים על ידי שימוש ב", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Enforce Bitwarden Two-step Login options for members by using the Two-step Login Policy.'" }, "twoStepLoginPolicy": { - "message": "Two-step Login Policy" + "message": "מדיניות כניסה דו־שלבית" }, "twoStepLoginOrganizationDuoDesc": { - "message": "To enforce Two-step Login through Duo, use the options below." + "message": "כדי לאכוף כניסה דו־שלבית דרך Duo, השתמש באפשרויות למטה." }, "twoStepLoginOrganizationSsoDesc": { - "message": "If you have setup SSO or plan to, Two-step Login may already be enforced through your Identity Provider." + "message": "אם הגדרת SSO או מתכוון לעשות כן, ייתכן שכניסה דו־שלבית כבר נאכפת דרך ספק הזהות שלך." }, "twoStepLoginRecoveryWarning": { - "message": "שים לב: שימוש לא נכון בכניסה דו-שלבית עשוי לגרום לך להנעל ללא גישה לחשבון Bitwarden שלך. מומלץ לשמור קוד שחזור לגישה לחשבון שלך למקרה שלא תוכל להשתמש בספק הכניסה הדו-שלבית (לדוגמא: איבדת את הפלאפון או את מפתח החומרה שלך). גם צוות התמיכה של Bitwarden לא יוכל לעזור לך במקרה שתאבד גישה לחשבון שלך. אנו ממליצים שתכתוב או תדפיס את קודי השחזור ותשמור אותם במקום בטוח." + "message": "הגדרת כניסה דו־שלבית יכולה לנעול אותך לצמיתות מחוץ לחשבון Bitwarden שלך. קוד שחזור מאפשר לך לגשת לחשבון שלך במקרה שאתה לא יכול להשתמש בספק הכניסה הד־שלבית הרגיל שלך (דוגמה: איבדת את המכשיר שלך). התמיכה של Bitwarden לא תוכל לסייע לך אם תאבד גישה לחשבון שלך. אנו ממליצים שתכתוב או תדפיס את קוד השחזור ותשמור אותו במקום בטוח." + }, + "yourSingleUseRecoveryCode": { + "message": "ניתן להשתמש בקוד השחזור החד־פעמי שלך כדי לכבות כניסה דו־שלבית במקרה שאתה מאבד גישה לספק הכניסה הדו־שלבית שלך. Bitwarden ממליץ לך לרשום את קוד השחזור ולשמור אותו במקום בטוח." }, "viewRecoveryCode": { - "message": "צפה בקוד שחזור" + "message": "הצג קוד שחזור" }, "providers": { "message": "ספקים", @@ -2074,59 +2191,74 @@ "message": "מופעל" }, "restoreAccess": { - "message": "Restore access" + "message": "שחזר גישה" }, "premium": { "message": "פרימיום", "description": "Premium membership" }, "premiumMembership": { - "message": "חשבון פרימיום" + "message": "חברות פרימיום" }, "premiumRequired": { - "message": "נדרש חשבון פרימיום" + "message": "נדרש פרימיום" }, "premiumRequiredDesc": { - "message": "בכדי להשתמש ביכולת זו יש צורך בחשבון פרמיום." + "message": "נדרשת חברות פרימיום כדי להשתמש בתכונה זו." }, "youHavePremiumAccess": { "message": "יש לך גישת פרימיום" }, "alreadyPremiumFromOrg": { - "message": "לארגון שאתה חבר בו, כבר יש גישת פרימיום, ולכן יש לך גישה ליכולות פרמיום." + "message": "כבר יש לך גישה ליכולות פרימיום בזכות ארגון שאתה חבר בו." }, "manage": { "message": "נהל" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "ניהול אוסף" + }, + "viewItems": { + "message": "הצג פריטים" + }, + "viewItemsHidePass": { + "message": "הצג פריטים, סיסמאות מוסתרות" + }, + "editItems": { + "message": "ערוך פריטים" + }, + "editItemsHidePass": { + "message": "ערוך פריטים, סיסמאות מוסתרות" }, "disable": { - "message": "בטל" + "message": "כבה" }, "revokeAccess": { - "message": "Revoke access" + "message": "בטל גישה" + }, + "revoke": { + "message": "בטל" }, "twoStepLoginProviderEnabled": { - "message": "ספק כניסה דו-שלבית זה נתמך בחשבון שלך." + "message": "ספק כניסה דו־שלבית זה פעיל בחשבון שלך." }, "twoStepLoginAuthDesc": { "message": "הזן את הסיסמה הראשית שלך בכדי לשנות הגדרות הנוגעות לכניסה דו-שלבית." }, "twoStepAuthenticatorInstructionPrefix": { - "message": "Download an authenticator app such as" + "message": "הורד יישום מאמת כגון" }, "twoStepAuthenticatorInstructionInfix1": { "message": "," }, "twoStepAuthenticatorInstructionInfix2": { - "message": "or" + "message": "או" }, "twoStepAuthenticatorInstructionSuffix": { "message": "." }, "continueToExternalUrlTitle": { - "message": "Continue to $URL$?", + "message": "להמשיך אל $URL$?", "placeholders": { "url": { "content": "$1", @@ -2135,34 +2267,34 @@ } }, "continueToExternalUrlDesc": { - "message": "You are leaving Bitwarden and launching an external website in a new window." + "message": "אתה עוזב את Bitwarden ופותח אתר אינטרנט חיצוני בחלון חדש." }, "twoStepContinueToBitwardenUrlTitle": { - "message": "Continue to bitwarden.com?" + "message": "להמשיך אל 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 מאפשר לך לאחסן מפתחות מאמת וליצור קודי TOTP עבור זרימת אימות דו־שלבית. למד עוד באתר האינטרנט bitwarden.com." }, "twoStepAuthenticatorScanCodeV2": { - "message": "Scan the QR code below with your authenticator app or enter the key." + "message": "סרוק את קוד ה־QR למטה עם יישום המאמת שלך או הזן את המפתח." }, "twoStepAuthenticatorQRCanvasError": { - "message": "Could not load QR code. Try again or use the key below." + "message": "לא היה ניתן לטעון קוד QR. נסה שוב או השתמש במפתח למטה." }, "key": { "message": "מפתח" }, "twoStepAuthenticatorEnterCodeV2": { - "message": "Verification code" + "message": "קוד אימות" }, "twoStepAuthenticatorReaddDesc": { "message": "במקרה שאתה צריך את אפשרות הכניסה זמינה גם במכשיר אחר, כאן ניתן למצוא את קוד הQR (או המפתח) הנחוץ לאפליקציית האימות במכשיר הנוסף." }, "twoStepDisableDesc": { - "message": "האם אתה בטוח שברצונך לבטל את הספק הזה עבור הכניסה הדו-שלבית?" + "message": "האם אתה בטוח שברצונך לכבות ספק כניסה דו־שלבית זה?" }, "twoStepDisabled": { - "message": "ספק עבור כניסה דו-שלבית מבוטל." + "message": "ספק כניסה דו־שלבית כבוי." }, "twoFactorYubikeyAdd": { "message": "הוסף מפתח YubiKey לחשבונך" @@ -2180,7 +2312,7 @@ "message": "שמור את הטופס." }, "twoFactorYubikeyWarning": { - "message": "עקב מגבלות פלטפורמה, לא ניתן להשתמש בYubiKey בכל האפליקציות של Bitwarden. עליך לאפשר ספק כניסה דו-שלבית נוסף למקרה שבו הYubiKey שלך לא זמין. פלטפורמות נתמכות:" + "message": "עקב מגבלות פלטפורמה, לא ניתן להשתמש במפתחות YubiKey בכל היישומים של Bitwarden. עליך להגדיר ספק כניסה דו־שלבית אחר כך שתוכל לגשת לחשבון שלך כאשר לא ניתן להשתמש במפתחות YubiKey. פלטפורמות נתמכות:" }, "twoFactorYubikeySupportUsb": { "message": "כספת רשת, אפליקציית שולחן עבודה, שורת הפקודה, וכל התוספים לדפדפן על מכשיר עם חיבור USB עבור הYubiKey שלך." @@ -2207,7 +2339,7 @@ } }, "webAuthnkeyX": { - "message": "WebAuthn Key $INDEX$", + "message": "מפתח WebAuthn $INDEX$", "placeholders": { "index": { "content": "$1", @@ -2228,19 +2360,19 @@ "message": "מפתחות YubiKey עודכנו" }, "disableAllKeys": { - "message": "בטל את כל המפתחות" + "message": "השבת את כל המפתחות" }, "twoFactorDuoDesc": { "message": "הזן את פרטי אפליקציית Bitwarden מתוך עמוד הניהול של Duo." }, "twoFactorDuoClientId": { - "message": "Client Id" + "message": "מזהה משתמש" }, "twoFactorDuoClientSecret": { - "message": "Client Secret" + "message": "סוד לקוח" }, "twoFactorDuoApiHostname": { - "message": "שם שרת הAPI" + "message": "שם מארח API" }, "twoFactorEmailDesc": { "message": "עקוב אחר הצעדים הבאים להגדרת כניסה דו-שלבית עם אימייל:" @@ -2261,7 +2393,7 @@ "message": "האם אתה בטוח שברצונך למחוק מפתח אבטחה זה?" }, "twoFactorWebAuthnAdd": { - "message": "Add a WebAuthn security key to your account" + "message": "הוסף מפתח אבטחה מסוג WebAuthn לחשבון שלך" }, "readKey": { "message": "קרא מפתח" @@ -2282,31 +2414,28 @@ "message": "שמור את הטופס." }, "twoFactorU2fWarning": { - "message": "עקב מגבלות פלטפורמה, לא ניתן להשתמש בFIDO U2F בכל האפליקציות של Bitwarden. עליך לאפשר ספק כניסה דו-שלבית נוסף למקרה שבו הFIDO U2F שלך לא זמין. פלטפורמות נתמכות:" + "message": "עקב מגבלות פלטפורמה, לא ניתן להשתמש ב־FIDO U2F בכל היישומים של Bitwarden. עליך להגדיר ספק כניסה דו־שלבית אחר כך שתוכל לגשת לחשבון שלך כאשר לא ניתן להשתמש ב־FIDO U2F. פלטפורמות נתמכות:" }, "twoFactorU2fSupportWeb": { - "message": "כספת ברשת ותוספי אבטחה למחשב נייח\\נייד עם דפדפן תומך בU2F (כרום, אופרה, Vivaldi, או פיירפוקס עם תמיכה בFIDO U2F)." + "message": "כספת רשת והרחבות דפדפן במחשב נייח/נייד עם דפדפן התומך ב־U2F (Vivaldi, Opera, Chrome או Firefox עם FIDO U2F מופעל)." }, "twoFactorU2fWaiting": { "message": "ממתין ללחיצה על כפתור במפתח האבטחה שלך" }, "twoFactorU2fClickSave": { - "message": "לחץ על כפתור \"שמירה\" בכדי לאפשר כניסה דו-שלבית בעזרת מפתח אבטחה זה." + "message": "לחץ על הלחצן \"שמור\" למטה כדי להפעיל את מפתח האבטחה הזה עבור כניסה דו־שלבית." }, "twoFactorU2fProblemReadingTryAgain": { "message": "היתה בעיה בקריאת מפתח האבטחה. נסה בשנית." }, - "twoFactorWebAuthnWarning": { - "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used. Supported platforms:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Web vault and browser extensions on a desktop/laptop with a WebAuthn supported browser (Chrome, Opera, Vivaldi, or Firefox with FIDO U2F turned on)." + "twoFactorWebAuthnWarning1": { + "message": "עקב מגבלות פלטפורמה, לא ניתן להשתמש ב־WebAuthn בכל היישומים של Bitwarden. עליך להגדיר ספק כניסה דו־שלבית אחר כך שתוכל לגשת לחשבון שלך כאשר לא ניתן להשתמש ב־WebAuthn." }, "twoFactorRecoveryYourCode": { "message": "קוד השחזור שלך עבור כניסה דו שלבית לBitwarden" }, "twoFactorRecoveryNoCode": { - "message": "עדיין לא הוספת אף ספק לכניסה דו-שלבית. לאחר שתאפשר כניסה באמצעות ספק עם כניסה דו שלבית תוכל לבדוק כאן שוב ולראות את קוד השחזור שלך." + "message": "עדיין לא הגדרת אף ספק כניסה דו־שלבית. לאחר שתגדיר ספק כניסה דו־שלבית, תוכל לבדוק שוב כאן עבור קוד השחזור שלך." }, "printCode": { "message": "הדפס קוד", @@ -2316,24 +2445,24 @@ "message": "דוחות" }, "reportsDesc": { - "message": "Identify and close security gaps in your online accounts by clicking the reports below.", + "message": "זהה וסגור פערי אבטחה בחשבונות המקוונים שלך על ידי לחיצה על הדוחות למטה.", "description": "Vault health reports can be used to evaluate the security of your Bitwarden individual or organization vault." }, "orgsReportsDesc": { - "message": "Identify and close security gaps in your organization's accounts by clicking the reports below.", + "message": "זהה וסגור פערי אבטחה בחשבונות של הארגון שלך על ידי לחיצה על הדוחות למטה.", "description": "Vault health reports can be used to evaluate the security of your Bitwarden individual or organization vault." }, "unsecuredWebsitesReport": { - "message": "דוח אתרים לא מאובטחים" + "message": "אתרים לא מאובטחים" }, "unsecuredWebsitesReportDesc": { - "message": "שימוש באתרים לא מאובטחים שמתחילים בקידומת http:// יכול להיות מסוכן. אם האתר מאפשר זאת, תמיד נסה להשתמש בקידומת https:// כך שהחיבור יהיה מוצפן." + "message": "כתובות URL שמתחילות עם //:http אינן משתמשות בהצפנה הטובה ביותר שזמינה. שנה את הכתובות URI של הכניסות עבור החשבונות האלה כך שיתחילו עם //:https בשביל גלישה בטוחה יותר." }, "unsecuredWebsitesFound": { "message": "נמצאו אתרים לא מאובטחים" }, "unsecuredWebsitesFoundReportDesc": { - "message": "We found $COUNT$ items in your $VAULT$ with unsecured URIs. You should change their URI scheme to https:// if the website allows it.", + "message": "מצאנו $COUNT$ פריטים בכספת שלך עם כתובות URI לא מאובטחות. עליך לשנות את סכמת ה־URI שלהם ל־//:https אם האתר מאפשר זאת.", "placeholders": { "count": { "content": "$1", @@ -2349,16 +2478,16 @@ "message": "לא נמצאו פריטים בכספת המכילים כתובות לא מאובטחות." }, "inactive2faReport": { - "message": "דוח 2FA לא פעילים" + "message": "כניסה דו־שלבית לא פעילה" }, "inactive2faReportDesc": { - "message": "אימות דו-שלבי (2FA) היא הגדרת אבטחה חשובה שעוזרת לאבטח את החשבון שלך. אם האתר מאפשר זאת, מומלץ לאפשר את האימות הדו-שלבי." + "message": "כניסה דו-שלבית מוסיפה שכבת הגנה לחשבונות שלך. הגדר כניסה דו־שלבית באמצעות המאמת של Bitwarden עבור החשבונות האלה או השתמש בשיטה חלופית." }, "inactive2faFound": { - "message": "נמצאו פרטי כניסות שלא פעילה בהן אופציית 2FA" + "message": "נמצאו כניסות ללא כניסה דו־שלבית" }, "inactive2faFoundReportDesc": { - "message": "We found $COUNT$ website(s) in your $VAULT$ that may not be configured with two-step login (according to 2fa.directory). To further protect these accounts, you should set up two-step login.", + "message": "מצאנו $COUNT$ אתרים בכספת שלך שייתכן שלא הוגדרו עם כניסה דו־שלבית (על פי 2fa.directory). כדי להגן עוד יותר על החשבונות הללו, עליך להגדיר כניסה דו־שלבית.", "placeholders": { "count": { "content": "$1", @@ -2371,22 +2500,22 @@ } }, "noInactive2fa": { - "message": "לא נמצאו אתרים ללא אימות דו-שלבי בכספת שלך." + "message": "לא נמצאו אתרים בכספת שלך עם תצורת כניסה דו־שלבית חסרה." }, "instructions": { "message": "הוראות" }, "exposedPasswordsReport": { - "message": "דו\"ח סיסמאות שנחשפו" + "message": "סיסמאות חשופות" }, "exposedPasswordsReportDesc": { - "message": "Passwords exposed in a data breach are easy targets for attackers. Change these passwords to prevent potential break-ins." + "message": "סיסמאות חשופות בפרצת נתונים הן מטרות קלות עבור תוקפים. שנה סיסמאות אלה כדי למנוע פריצות פוטנציאליות." }, "exposedPasswordsFound": { - "message": "נמצאו סיסמאות שנחשפו" + "message": "נמצאו סיסמאות חשופות" }, "exposedPasswordsFoundReportDesc": { - "message": "We found $COUNT$ items in your $VAULT$ that have passwords that were exposed in known data breaches. You should change them to use a new password.", + "message": "מצאנו $COUNT$ פריטים בכספת שלך שיש להם סיסמאות שנחשפו בפרצות נתונים ידועות. עליך לשנות אותם כך שישתמשו בסיסמה חדשה.", "placeholders": { "count": { "content": "$1", @@ -2402,10 +2531,10 @@ "message": "לא נמצאו פריטים בכספת שלך שנחשפו בפריצות ידועות." }, "checkExposedPasswords": { - "message": "בדוק אם קיימות סיסמאות שנפרצו" + "message": "בדוק סיסמאות חשופות" }, "timesExposed": { - "message": "Times exposed" + "message": "פעמים נחשפו" }, "exposedXTimes": { "message": "נחשף $COUNT$ פעמים", @@ -2417,16 +2546,16 @@ } }, "weakPasswordsReport": { - "message": "דו\"ח סיסמאות חלשות" + "message": "סיסמאות חלשות" }, "weakPasswordsReportDesc": { - "message": "סיסמאות חלשות קלות לניחוש על ידי האקרים וכלים אוטומטיים לפריצת סיסמאות. מחולל הסיסמאות של Bitwarden יכול לעזור לך ליצור סיסמאות חזקות." + "message": "תוקפים יכולים לנחש סיסמאות חלשות בקלות. שנה את הסיסמאות הללו לסיסמאות חזקות באמצעות מחולל הסיסמאות." }, "weakPasswordsFound": { "message": "נמצאו סיסמאות חלשות" }, "weakPasswordsFoundReportDesc": { - "message": "We found $COUNT$ items in your $VAULT$ with passwords that are not strong. You should update them to use stronger passwords.", + "message": "מצאנו $COUNT$ פריטים בכספת שלך עם סיסמאות לא חזקות. עליך לעדכן אותם כך שישתמשו בסיסמאות חזקות יותר.", "placeholders": { "count": { "content": "$1", @@ -2442,19 +2571,19 @@ "message": "אין פריטים בכספת שלך עם סיסמאות חלשות." }, "weakness": { - "message": "Weakness" + "message": "חולשה" }, "reusedPasswordsReport": { - "message": "דו\"ח סיסמאות משומשות" + "message": "סיסמאות בשימוש חוזר" }, "reusedPasswordsReportDesc": { - "message": "אם שירות שהשתמשת בו נפרץ, שימוש באותה הסיסמה במקום אחר מאפשר להאקרים לקבל גישה לחשבונות נוספים שלך בקלות רבה. מומלץ מאוד להשתמש בסיסמה יחודית עבור כל חשבון או שירות." + "message": "שימוש חוזר של סיסמאות מקל על תוקפים לפרוץ לחשבונות מרובים. שנה את הסיסמאות הללו כך שכל אחת תהיה ייחודית." }, "reusedPasswordsFound": { - "message": "נמצאו סיסמאות משומשות" + "message": "נמצאו סיסמאות בשימוש חוזר" }, "reusedPasswordsFoundReportDesc": { - "message": "We found $COUNT$ passwords that are being reused in your $VAULT$. You should change them to a unique value.", + "message": "מצאנו $COUNT$ סיסמאות שנמצאות בשימוש חוזר בכספת שלך. עליך לשנות אותם לערך ייחודי.", "placeholders": { "count": { "content": "$1", @@ -2470,7 +2599,7 @@ "message": "אין פרטי התחברות בכספת שלך עם סיסמאות משומשות." }, "timesReused": { - "message": "Times reused" + "message": "פעמים בשימוש חוזר" }, "reusedXTimes": { "message": "היה בשימוש $COUNT$ פעמים", @@ -2482,16 +2611,16 @@ } }, "dataBreachReport": { - "message": "דו\"ח פריצת אבטחה" + "message": "פרצת נתונים" }, "breachDesc": { - "message": "אירוע \"דליפה\" הוא תקרית שבה המידע של האתר היה נגיש בצורה לא חוקית להאקרים והם הפיצו אותו באופן פומבי. עבור על המידע שנחשף (כתובות אימייל, סיסמאות, כרטיסי אשראי וכו') ובצע את הפעולות הנחוצות, לדוגמא - לשנות את הסיסמאות שפורסמו." + "message": "חשבונות שנפרצו יכולים לחשוף את המידע האישי שלך. אבטח חשבונות שנפרצו על ידי הפעלת אימות דו־גורמי (2FA) או יצירת סיסמה חזקה יותר." }, "breachCheckUsernameEmail": { "message": "בדוק את כל שמות המשתמשים או כתובות המייל שאתה משתמש בהם." }, "checkBreaches": { - "message": "בדוק פריצות אבטחה" + "message": "בדוק פרצות" }, "breachUsernameNotFound": { "message": "שם המשתמש $USERNAME$ לא נמצא בפריצות אבטחה ידועות.", @@ -2520,7 +2649,7 @@ } }, "breachFound": { - "message": "נמצאו חשבונות שדלפו" + "message": "נמצאו חשבונות שנפרצו" }, "compromisedData": { "message": "מידע שנחשף" @@ -2532,10 +2661,10 @@ "message": "משתמשים שהושפעו" }, "breachOccurred": { - "message": "פריצת אבטחה אירעה" + "message": "אירעה פרצה" }, "breachReported": { - "message": "פריצת אבטחה דווחה" + "message": "דווחה פרצה" }, "reportError": { "message": "אירעה שגיאה בטעינת הדו\"ח. נסה שוב" @@ -2544,21 +2673,21 @@ "message": "חיוב" }, "billingPlanLabel": { - "message": "Billing plan" + "message": "תוכנית חיוב" }, "paymentType": { - "message": "Payment type" + "message": "סוג תשלום" }, "accountCredit": { - "message": "מאזן החשבון", + "message": "אשראי חשבון", "description": "Financial term. In the case of Bitwarden, a positive balance means that you owe money, while a negative balance means that you have a credit (Bitwarden owes you money)." }, "accountBalance": { - "message": "יתרת חשבון", + "message": "מאזן חשבון", "description": "Financial term. In the case of Bitwarden, a positive balance means that you owe money, while a negative balance means that you have a credit (Bitwarden owes you money)." }, "addCredit": { - "message": "הוסף קרדיט", + "message": "הוסף אשראי", "description": "Add more credit to your account's balance." }, "amount": { @@ -2582,16 +2711,16 @@ "message": "שדרגת לפרימיום." }, "premiumUpgradeUnlockFeatures": { - "message": "שדרג את חשבונך לפרמיום כדי להשתמש ביכולות נהדרות נוספות." + "message": "שדרג את חשבונך לחברות פרמיום ופתח כמה תכונות נוספות נהדרות." }, "premiumSignUpStorage": { "message": "1 ג'יגה של מקום אחסון מוצפן עבור קבצים מצורפים." }, "premiumSignUpTwoStepOptions": { - "message": "Proprietary two-step login options such as YubiKey and Duo." + "message": "אפשרויות כניסה דו־שלבית קנייניות כגון YubiKey ו־Duo." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "גישת חירום" }, "premiumSignUpReports": { "message": "היגיינת סיסמאות, מצב בריאות החשבון, ודיווחים מעודכנים על פרצות חדשות בכדי לשמור על הכספת שלך בטוחה." @@ -2603,7 +2732,7 @@ "message": "קדימות בתמיכה הטכנית." }, "premiumSignUpFuture": { - "message": "כל יכולות הפרימיום העתידיות שנפתח. עוד יכולות מגיעות בקרוב!" + "message": "כל תכונות הפרימיום העתידיות. עוד מגיעות בקרוב!" }, "premiumPrice": { "message": "הכל רק ב-$PRICE$ לשנה!", @@ -2615,7 +2744,7 @@ } }, "premiumPriceWithFamilyPlan": { - "message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ", + "message": "עבור לפרימיום תמורת $PRICE$ /שנה בלבד, או קבל חשבונות פרימיום ל־$FAMILYPLANUSERCOUNT$ משתמשים ושיתוף משפחתי בלתי מוגבל עם ", "placeholders": { "price": { "content": "$1", @@ -2628,7 +2757,7 @@ } }, "bitwardenFamiliesPlan": { - "message": "Bitwarden Families plan." + "message": "תוכנית Bitwarden למשפחות." }, "addons": { "message": "תוספים" @@ -2637,7 +2766,7 @@ "message": "גישת פרימיום" }, "premiumAccessDesc": { - "message": "ניתן להוסיף גישת פרימיום לכל חברי הארגון שלך ב-$PRICE$ ל$INTERVAL$.", + "message": "אתה יכול להוסיף גישת פרימיום לכל חברי הארגון שלך עבור $PRICE$ /$INTERVAL$.", "placeholders": { "price": { "content": "$1", @@ -2650,7 +2779,7 @@ } }, "additionalStorageGb": { - "message": "מקום אחסון נוסף (בג'יגה)" + "message": "אחסון נוסף (GB)" }, "additionalStorageGbDesc": { "message": "# של ג'יגה בייט נוספים" @@ -2682,7 +2811,7 @@ "message": "שנה" }, "yr": { - "message": "yr" + "message": "שנה" }, "month": { "message": "חודש" @@ -2704,43 +2833,43 @@ } }, "paymentChargedWithUnpaidSubscription": { - "message": "Your payment method will be charged for any unpaid subscriptions." + "message": "שיטת התשלום שלך תחויב עבור כל מנוי שלא שולם." }, "paymentChargedWithTrial": { - "message": "התוכנית שבחרת מגיעה עם 7 ימי נסיון חינמי. שיטת התשלום שבחרת לא תחויב עד לתום תקופת הנסיון. ביצוע החשבון יתבצע על בסיס מתחדש בכל $INTERVAL$. באפשרותך לבטל בכל עת." + "message": "התוכנית שלך מגיעה עם 7 ימי ניסיון בחינם. שיטת התשלום שלך לא תחויב עד שהניסיון יסתיים. אתה רשאי לבטל בכל עת." }, "paymentInformation": { "message": "פרטי תשלום" }, "billingInformation": { - "message": "Billing information" + "message": "פרטי חיוב" }, "billingTrialSubLabel": { - "message": "Your payment method will not be charged during the 7 day free trial." + "message": "שיטת התשלום שלך לא תחויב במהלך 7 ימי הניסיון בחינם." }, "creditCard": { "message": "כרטיס אשראי" }, "paypalClickSubmit": { - "message": "לחץ על כפתור PayPal בכדי להכנס לחשבון PayPal שלך, ואז לחץ על כפתור התשלום כדי להמשיך." + "message": "בחר את הלחצן PayPal כדי להיכנס לחשבון PayPal שלך, ואז לחץ על הלחצן 'שלח' למטה כדי להמשיך." }, "cancelSubscription": { "message": "בטל מנוי" }, "subscriptionExpiration": { - "message": "Subscription expiration" + "message": "תפוגת מנוי" }, "subscriptionCanceled": { "message": "המנוי בוטל." }, "pendingCancellation": { - "message": "בקשת ביטול ממתינה" + "message": "ממתין לביטול" }, "subscriptionPendingCanceled": { "message": "המנוי סומן כמיועד לביטול בסיום תקופת החיוב הנוכחית." }, "reinstateSubscription": { - "message": "הפעל מחדש את המנוי" + "message": "החזר מנוי" }, "reinstateConfirmation": { "message": "האם אתה בטוח שברצונך להסיר את בקשת הביטול הממתינה ולהפעיל מחדש את חשבונך?" @@ -2752,10 +2881,10 @@ "message": "האם אתה בטוח שברצונך לבטל? ביטול המנוי יגרום לאיבוד כל האפשרויות השמורות למנויים בסיום מחזור החיוב הנוכחי." }, "canceledSubscription": { - "message": "המנוי בוטל." + "message": "המנוי בוטל" }, "neverExpires": { - "message": "ללא תאריך תפוגה" + "message": "לא פג תוקף לעולם" }, "status": { "message": "סטטוס" @@ -2770,16 +2899,16 @@ "message": "הורד רישיון" }, "viewBillingToken": { - "message": "View Billing Token" + "message": "הצג אסימון חיוב" }, "updateLicense": { "message": "עדכן רישיון" }, "manageSubscription": { - "message": "ניהול מנוי" + "message": "נהל מנוי" }, "launchCloudSubscription": { - "message": "Launch Cloud Subscription" + "message": "הפעל מנוי ענן" }, "storage": { "message": "אחסון" @@ -2819,10 +2948,10 @@ "message": "חשבוניות" }, "noUnpaidInvoices": { - "message": "No unpaid invoices." + "message": "אין חשבוניות לא משולמות." }, "noPaidInvoices": { - "message": "No paid invoices." + "message": "אין חשבוניות משולמות." }, "paid": { "message": "שולם", @@ -2878,10 +3007,10 @@ } }, "contactSupport": { - "message": "צור קשר עם התמיכה" + "message": "צור קשר עם תמיכת הלקוחות" }, "contactSupportShort": { - "message": "Contact Support" + "message": "פנה לתמיכה" }, "updatedPaymentMethod": { "message": "שיטת תשלום עודכנה." @@ -2902,7 +3031,7 @@ } }, "uploadLicenseFilePremium": { - "message": "כדי לשדרג את החשבון שלך לפרמיום עליך להעלות קובץ רשיון תקין." + "message": "כדי לשדרג את החשבון שלך לחברות פרימיום, אתה צריך להעלות קובץ רישיון חוקי." }, "uploadLicenseFileOrg": { "message": "ליצירת שרת on-premises בארגון לך עליך להעלות קובץ רשיון תקין." @@ -2923,7 +3052,7 @@ "message": "החשבון הזה נמצא בבעלות עסק." }, "billingEmail": { - "message": "מייל לחשבוניות" + "message": "דוא\"ל לחיוב" }, "businessName": { "message": "שם העסק" @@ -2935,10 +3064,10 @@ "message": "משתמשים" }, "userSeats": { - "message": "כסאות משתמשים" + "message": "מקומות למשתמשים" }, "additionalUserSeats": { - "message": "כסאות משתמשים נוספים" + "message": "מקומות למשתמשים נוספים" }, "userSeatsDesc": { "message": "כמות כסאות משתמשים" @@ -2985,7 +3114,7 @@ "message": "לעסקים וקבוצות ארגוניות." }, "planNameTeamsStarter": { - "message": "Teams Starter" + "message": "צוותים מתחילים" }, "planNameEnterprise": { "message": "ארגון" @@ -3048,7 +3177,7 @@ "message": "הוסף ושתף עם כמות בלתי מוגבלת של משתמשים" }, "createUnlimitedCollections": { - "message": "צור מספר בלתי מוגבל של אוספים" + "message": "צור אוספים ללא הגבלה" }, "gbEncryptedFileStorage": { "message": "גודל קובץ מוצפן: $SIZE$", @@ -3060,16 +3189,16 @@ } }, "onPremHostingOptional": { - "message": "אחסון שרת מקומי (אופציונאלי)" + "message": "אירוח מקומי (אופציונלי)" }, "usersGetPremium": { - "message": "המשתמשים יקבלו גישה ליכולות פרימיום" + "message": "המשתמשים יקבלו גישה לתכונות פרימיום" }, "controlAccessWithGroups": { "message": "שלוט על גישת משתמשים בעזרת קבוצות" }, "syncUsersFromDirectory": { - "message": "סנכרן את המשתמשים והקבוצות עם Active Directory" + "message": "סנכרן את המשתמשים והקבוצות שלך מתוך ספריה" }, "trackAuditLogs": { "message": "עקוב אחר פעולות המשתמשים בעזרת יומן ביקורת" @@ -3090,7 +3219,7 @@ } }, "trialThankYou": { - "message": "Thanks for signing up for Bitwarden for $PLAN$!", + "message": "תודה שנרשמת ל־Bitwarden עבור $PLAN$!", "placeholders": { "plan": { "content": "$1", @@ -3099,7 +3228,7 @@ } }, "trialSecretsManagerThankYou": { - "message": "Thanks for signing up for Bitwarden Secrets Manager for $PLAN$!", + "message": "תודה שנרשמת למנהל הסודות של Bitwarden עבור $PLAN$!", "placeholders": { "plan": { "content": "$1", @@ -3108,7 +3237,7 @@ } }, "trialPaidInfoMessage": { - "message": "Your $PLAN$ 7 day free trial will be converted to a paid subscription after 7 days.", + "message": "7 ימי הניסיון בחינם של ה־$PLAN$ שלך יומרו למנוי בתשלום לאחר 7 ימים.", "placeholders": { "plan": { "content": "$1", @@ -3117,7 +3246,7 @@ } }, "trialConfirmationEmail": { - "message": "We've sent a confirmation email to your team's billing email at " + "message": "שלחנו דוא\"ל אימות לדוא\"ל החיוב של הצוות שלך ב־" }, "monthly": { "message": "חודשי" @@ -3126,10 +3255,10 @@ "message": "שנתי" }, "annual": { - "message": "Annual" + "message": "שנתי" }, "basePrice": { - "message": "מחיר בסיסי" + "message": "מחיר בסיס" }, "organizationCreated": { "message": "הארגון נוצר" @@ -3138,7 +3267,7 @@ "message": "הארגון החדש שלך מוכן!" }, "organizationUpgraded": { - "message": "הארגון שלך שודרג." + "message": "הארגון שודרג" }, "leave": { "message": "יציאה" @@ -3147,7 +3276,7 @@ "message": "האם אתה בטוח שברצונך לצאת מהארגון?" }, "leftOrganization": { - "message": "יצאת מהארגון." + "message": "עזבת את הארגון" }, "defaultCollection": { "message": "אוסף ברירת מחדל" @@ -3156,13 +3285,13 @@ "message": "קבל עזרה" }, "getApps": { - "message": "הורד את האפליקציות" + "message": "הורד את היישומים" }, "loggedInAs": { "message": "מחובר בשם" }, "eventLogs": { - "message": "יומן אירועים" + "message": "יומני אירועים" }, "people": { "message": "אנשים" @@ -3171,7 +3300,7 @@ "message": "מדיניות" }, "singleSignOn": { - "message": "Single sign-on" + "message": "כניסה יחידה" }, "editPolicy": { "message": "ערוך מדיניות" @@ -3192,7 +3321,7 @@ "message": "האם אתה בטוח שברצונך למחוק קבוצה זו?" }, "deleteMultipleGroupsConfirmation": { - "message": "Are you sure you want to delete the following $QUANTITY$ group(s)?", + "message": "האם אתה בטח שברצונך להסיר את $QUANTITY$ הקבוצות הבאות?", "placeholders": { "quantity": { "content": "$1", @@ -3204,22 +3333,22 @@ "message": "האם אתה בטוח שברצונך להסיר משתמש זה?" }, "removeOrgUserConfirmation": { - "message": "When a member is 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." + "message": "כאשר חבר מוסר, אין לו יותר גישה לנתוני הארגון ופעולה זו היא בלתי הפיכה. כדי להוסיף את החבר בחזרה לארגון, יש להזמין ולקלוט אותו שוב." }, "revokeUserConfirmation": { - "message": "When a member is revoked, they no longer have access to organization data. To quickly restore member access, go to the Revoked tab." + "message": "כאשר חבר מבוטל, אין לו יותר גישה לנתוני הארגון. כדי לשחזר במהירות גישת חבר, עבור לכרטיסיה 'מבוטל'." }, "removeUserConfirmationKeyConnector": { - "message": "Warning! This user requires Key Connector to manage their encryption. Removing this user from your organization will permanently deactivate their account. This action cannot be undone. Do you want to proceed?" + "message": "אזהרה! משתמש זה דורש Key Connector כדי לנהל את ההצפנה שלו. הסרת משתמש זה מהארגון שלך תשבית לצמיתות את החשבון שלו. פעולה זו אינה ניתנת לביטול. האם ברצונך להמשיך?" }, "externalId": { "message": "מזהה חיצוני" }, "externalIdDesc": { - "message": "ניתן להשתמש במזהה החיצוני כקישור בין משאב זה למערכת חיצונית כמו לדוגמא תיקיית משתמש." + "message": "מזהה חיצוני הוא הפניה לא מוצפנת בשימוש על ידי מחבר הספריות וה־API של Bitwarden." }, "nestCollectionUnder": { - "message": "Nest collection under" + "message": "לקנן אוסף תחת" }, "accessControl": { "message": "בקרת גישה" @@ -3237,16 +3366,16 @@ "message": "ערוך אוסף" }, "collectionInfo": { - "message": "Collection info" + "message": "פרטי אוסף" }, "deleteCollectionConfirmation": { "message": "האם אתה בטוח שברצונך למחוק אוסף זה?" }, "editMember": { - "message": "Edit member" + "message": "ערוך חבר" }, "fieldOnTabRequiresAttention": { - "message": "A field on the '$TAB$' tab requires your attention.", + "message": "שדה בכרטיסיית ה־'$TAB$' דורש את תשומת לבך.", "placeholders": { "tab": { "content": "$1", @@ -3258,7 +3387,7 @@ "message": "הזמן משתמש חדש לארגון שלך על ידי הזנת כתובת האימייל שלהם שמשמשת אותם בחשבון Bitwarden. אם אין להם חשבון Bitwarden, הם יתבקשו ליצור חשבון." }, "inviteMultipleEmailDesc": { - "message": "באפשרותך להזמין עד $COUNT$ משתמשים בכל פעם על ידי הפרדת הכתובות בעזרת פסיק.", + "message": "הזן עד $COUNT$ כתובות דוא\"ל על ידי הפרדה עם פסיק.", "placeholders": { "count": { "content": "$1", @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "נותרה לך הזמנה 1." + }, + "inviteZeroEmailDesc": { + "message": "נותרו לך 0 הזמנות." + }, "userUsingTwoStep": { "message": "משתמש זה הפעיל כניסה דו שלבית כדי להגן על חשבונו." }, @@ -3279,37 +3414,37 @@ "message": "אושר" }, "clientOwnerEmail": { - "message": "Client owner email" + "message": "דוא\"ל בעל לקוח" }, "owner": { "message": "בעלים" }, "ownerDesc": { - "message": "החשבון בעל ההרשאות הגבוהות ביותר שיכול לנהל את כל ההיבטים של הארגון." + "message": "נהל את כל ההיבטים של הארגון שלך, כולל חיובים ומנויים" }, "clientOwnerDesc": { - "message": "This user should be independent of the Provider. If the Provider is disassociated with the organization, this user will maintain ownership of the organization." + "message": "על משתמש זה להיות עצמאי מהספק. אם הספק מנותק מהארגון, משתמש זה ישמור על הבעלות של הארגון." }, "admin": { "message": "מנהל" }, "adminDesc": { - "message": "מנהלים יכולים לגשת ולנהל את כל הפריטים, האוספים והמשתמשים שבארגונך." + "message": "נהל גישת ארגון, כל האוספים, חברים, דיווח, והגדרות אבטחה" }, "user": { "message": "משתמש" }, "userDesc": { - "message": "משתמש רגיל עם גישה לאוספים נבחרים בארגון שלך." + "message": "גישה והוספת פריטים לאוספים מוקצים" }, "all": { "message": "הכל" }, "addAccess": { - "message": "Add Access" + "message": "הוסף גישה" }, "addAccessFilter": { - "message": "Add Access Filter" + "message": "הוסף מסנן גישה" }, "refresh": { "message": "רענן" @@ -3345,25 +3480,25 @@ "message": "CLI" }, "bitWebVault": { - "message": "Bitwarden Web vault" + "message": "כספת הרשת של Bitwarden" }, "bitSecretsManager": { - "message": "Bitwarden Secrets Manager" + "message": "מנהל הסודות של Bitwarden" }, "loggedIn": { - "message": "מחובר." + "message": "מחובר" }, "changedPassword": { - "message": "סיסמת החשבון שונתה." + "message": "סיסמת החשבון שונתה" }, "enabledUpdated2fa": { - "message": "כניסה דו שלבית הופעלה\\עודכנה." + "message": "כניסה דו־שלבית נשמרה" }, "disabled2fa": { - "message": "בטל כניסה דו שלבית." + "message": "כניסה דו־שלבית כבויה" }, "recovered2fa": { - "message": "חשבון שוחזר מכניסה דו שלבית." + "message": "חשבון שוחזר מכניסה דו־שלבית." }, "failedLogin": { "message": "נסיון כניסה נכשל עם סיסמה שגויה." @@ -3372,23 +3507,23 @@ "message": "נסיונות כניסה עם אימות דו שלבי נכשלו." }, "incorrectPassword": { - "message": "Incorrect password" + "message": "סיסמה שגויה" }, "incorrectCode": { - "message": "Incorrect code" + "message": "קוד שגוי" }, "incorrectPin": { - "message": "Incorrect PIN" + "message": "PIN שגוי" }, "pin": { "message": "PIN", "description": "PIN code. Ex. The short code (often numeric) that you use to unlock a device." }, "exportedVault": { - "message": "יצוא כספת." + "message": "הכספת יוצאה" }, "exportedOrganizationVault": { - "message": "יצוא של תוכן הכספת הארגונית." + "message": "כספת הארגון יוצאה." }, "editedOrgSettings": { "message": "הגדרות הארגון נערכו." @@ -3421,7 +3556,7 @@ } }, "movedItemIdToOrg": { - "message": "Moved item $ID$ to an organization.", + "message": "העביר פריט $ID$ אל ארגון.", "placeholders": { "id": { "content": "$1", @@ -3430,10 +3565,10 @@ } }, "viewAllLogInOptions": { - "message": "View all log in options" + "message": "הצג את כל אפשרויות הכניסה" }, "viewAllLoginOptions": { - "message": "View all log in options" + "message": "הצג את כל אפשרויות הכניסה" }, "viewedItemId": { "message": "פריט שנצפה $ID$.", @@ -3463,7 +3598,7 @@ } }, "viewedCardNumberItemId": { - "message": "Viewed Card Number for item $ID$.", + "message": "צפה במספר כרטיס עבור פריט $ID$.", "placeholders": { "id": { "content": "$1", @@ -3481,7 +3616,7 @@ } }, "viewCollectionWithName": { - "message": "View collection - $NAME$", + "message": "הצג אוסף - $NAME$", "placeholders": { "name": { "content": "$1", @@ -3490,7 +3625,7 @@ } }, "editItemWithName": { - "message": "Edit item - $NAME$", + "message": "ערוך פריט - $NAME$", "placeholders": { "name": { "content": "$1", @@ -3553,7 +3688,7 @@ } }, "deletedCollections": { - "message": "Deleted collections" + "message": "אוספים שנמחקו" }, "deletedCollectionId": { "message": "אוסף שנמחק $ID$.", @@ -3601,7 +3736,7 @@ } }, "deletedManyGroups": { - "message": "Deleted $QUANTITY$ group(s).", + "message": "נמחקו $QUANTITY$ אוספ(ים).", "placeholders": { "quantity": { "content": "$1", @@ -3619,7 +3754,7 @@ } }, "removeUserIdAccess": { - "message": "Remove $ID$ access", + "message": "הסר את הגישה של $ID$", "placeholders": { "id": { "content": "$1", @@ -3628,7 +3763,7 @@ } }, "revokedUserId": { - "message": "Revoked organization access for $ID$.", + "message": "הגישה לארגון בוטלה עבור $ID$.", "placeholders": { "id": { "content": "$1", @@ -3637,7 +3772,7 @@ } }, "restoredUserId": { - "message": "Restored organization access for $ID$.", + "message": "הגישה לארגון שוחזרה עבור $ID$.", "placeholders": { "id": { "content": "$1", @@ -3646,7 +3781,7 @@ } }, "revokeUserId": { - "message": "Revoke $ID$ access", + "message": "בטל את הגישה של $ID$", "placeholders": { "id": { "content": "$1", @@ -3717,8 +3852,11 @@ } } }, + "unlinkedSso": { + "message": "SSO נותק." + }, "unlinkedSsoUser": { - "message": "Unlinked SSO for user $ID$.", + "message": "SSO נותק עבור משתמש $ID$.", "placeholders": { "id": { "content": "$1", @@ -3727,7 +3865,7 @@ } }, "createdOrganizationId": { - "message": "Created organization $ID$.", + "message": "נוצר ארגון $ID$.", "placeholders": { "id": { "content": "$1", @@ -3736,7 +3874,7 @@ } }, "addedOrganizationId": { - "message": "Added organization $ID$.", + "message": "נוסף ארגון $ID$.", "placeholders": { "id": { "content": "$1", @@ -3745,7 +3883,7 @@ } }, "removedOrganizationId": { - "message": "Removed organization $ID$.", + "message": "הוסר ארגון $ID$.", "placeholders": { "id": { "content": "$1", @@ -3754,7 +3892,7 @@ } }, "accessedClientVault": { - "message": "Accessed $ID$ organization vault.", + "message": "ניגש אל כספת הארגון של $ID$.", "placeholders": { "id": { "content": "$1", @@ -3765,26 +3903,96 @@ "device": { "message": "מכשיר" }, + "loginStatus": { + "message": "מצב כניסה" + }, + "firstLogin": { + "message": "כניסה ראשונה" + }, + "trusted": { + "message": "מהימן" + }, + "needsApproval": { + "message": "צריך אישור" + }, + "areYouTryingtoLogin": { + "message": "האם את/ה מנסה להיכנס?" + }, + "logInAttemptBy": { + "message": "ניסיון כניסה על ידי $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "סוג מכשיר" + }, + "ipAddress": { + "message": "‏כתובת IP" + }, + "confirmLogIn": { + "message": "אשר כניסה" + }, + "denyLogIn": { + "message": "דחה כניסה" + }, + "thisRequestIsNoLongerValid": { + "message": "בקשה זו אינה תקפה עוד." + }, + "logInConfirmedForEmailOnDevice": { + "message": "הכניסה אושרה עבור $EMAIL$ ב־$DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "דחית ניסיון כניסה ממכשיר אחר. אם זה באמת היית אתה, נסה להיכנס עם המכשיר שוב." + }, + "loginRequestHasAlreadyExpired": { + "message": "כבר פג תוקפה של בקשת הכניסה." + }, + "justNow": { + "message": "זה עתה" + }, + "requestedXMinutesAgo": { + "message": "התבקשה לפני $MINUTES$ דקות", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { - "message": "Creating account on" + "message": "יוצר חשבון ב־" }, "checkYourEmail": { - "message": "Check your email" + "message": "בדוק את הדוא\"ל שלך" }, "followTheLinkInTheEmailSentTo": { - "message": "Follow the link in the email sent to" + "message": "עקוב אחר הקישור בדוא\"ל הנשלח אל" }, "andContinueCreatingYourAccount": { - "message": "and continue creating your account." + "message": "והמשך ליצור את החשבון שלך." }, "noEmail": { - "message": "No email?" + "message": "אין דוא\"ל?" }, "goBack": { - "message": "Go back" + "message": "חזור" }, "toEditYourEmailAddress": { - "message": "to edit your email address." + "message": "כדי לערוך את כתובת הדוא\"ל שלך." }, "view": { "message": "צפה" @@ -3802,22 +4010,22 @@ "message": "סוג משתמש" }, "groupAccess": { - "message": "גישה לקבוצה" + "message": "גישה קבוצתית" }, "groupAccessUserDesc": { - "message": "ערוך את הקבוצות שמשתמש זה משויך אליהן." + "message": "הענק לחבר גישה לאוספים על ידי הוספתו לקבוצה אחת או יותר." }, "invitedUsers": { - "message": "משתמשים שהוזמנו." + "message": "משתמש(ים) הוזמנ(ו)" }, "resendInvitation": { "message": "שלח הזמנה מחדש" }, "resendEmail": { - "message": "שלח מייל בשנית" + "message": "שלח דוא\"ל מחדש" }, "hasBeenReinvited": { - "message": "$USER$ הוזמן מחדש.", + "message": "$USER$ הוזמן מחדש", "placeholders": { "user": { "content": "$1", @@ -3841,10 +4049,10 @@ } }, "confirmUsers": { - "message": "אשר משתמשים" + "message": "אשר חברים" }, "usersNeedConfirmed": { - "message": "ישנם משתמשים שקיבלו את הזמנתך, אך עדיין צריך לאשר אותם. למשתמשים אלו לא תהיה גישה לארגון עד שיאשרו אותם." + "message": "יש לך חברים שקיבלו את ההזמנה שלהם, אבל עדיין צריך לאשר אותם. לחברים לא תהיה גישה לארגון עד שיאושרו." }, "startDate": { "message": "תאריך התחלה" @@ -3853,7 +4061,7 @@ "message": "תאריך סיום" }, "verifyEmail": { - "message": "אמת כתובת אימייל" + "message": "אמת דוא\"ל" }, "verifyEmailDesc": { "message": "אמת את האימייל שלך בכדי לאפשר גישה לכל היכולות." @@ -3865,31 +4073,37 @@ "message": "בדוק אם קיבלת את קוד האימות באימייל." }, "emailVerified": { - "message": "כתובת האימייל שלך אומתה." + "message": "דוא\"ל החשבון אומת" }, "emailVerifiedV2": { - "message": "Email verified" + "message": "דוא\"ל אומת" }, "emailVerifiedFailed": { "message": "לא ניתן לאמת את האימייל שלך. נסה לשלוח מייל אימות חדש." }, "emailVerificationRequired": { - "message": "יש לאמת את כתובת האימייל" + "message": "נדרש אימות דוא\"ל" }, "emailVerificationRequiredDesc": { - "message": "נדרש אישור אימות בדוא\"ל כדי לאפשר שימוש בתכונה זו." + "message": "אתה מוכרח לאמת את הדוא\"ל שלך כדי להשתמש בתכונה זו." }, "updateBrowser": { "message": "עדכן דפדפן" }, "generatingRiskInsights": { - "message": "Generating your risk insights..." + "message": "יוצר את תובנות הסיכון שלך..." }, "updateBrowserDesc": { "message": "אתה משתמש בדפדפן אינטרנט שאיננו נתמך. כספת הרשת עלולה שלא לפעול כראוי." }, + "youHaveAPendingLoginRequest": { + "message": "יש לך בקשת לכניסה ממתינה ממכשיר אחר." + }, + "reviewLoginRequest": { + "message": "סקור בקשת כניסה" + }, "freeTrialEndPromptCount": { - "message": "Your free trial ends in $COUNT$ days.", + "message": "הניסיון החינמי שלך מסתיים בעוד $COUNT$ ימים.", "placeholders": { "count": { "content": "$1", @@ -3898,7 +4112,7 @@ } }, "freeTrialEndPromptMultipleDays": { - "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "message": "$ORGANIZATION$, הניסיון החינמי שלך מסתיים בעוד $COUNT$ ימים.", "placeholders": { "count": { "content": "$2", @@ -3911,7 +4125,7 @@ } }, "freeTrialEndPromptTomorrow": { - "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "message": "$ORGANIZATION$, הניסיון החינמי שלך מסתיים מחר.", "placeholders": { "organization": { "content": "$1", @@ -3920,10 +4134,10 @@ } }, "freeTrialEndPromptTomorrowNoOrgName": { - "message": "Your free trial ends tomorrow." + "message": "הניסיון החינמי שלך מסתיים מחר." }, "freeTrialEndPromptToday": { - "message": "$ORGANIZATION$, your free trial ends today.", + "message": "$ORGANIZATION$, הניסיון החינמי שלך מסתיים היום.", "placeholders": { "organization": { "content": "$1", @@ -3932,16 +4146,16 @@ } }, "freeTrialEndingTodayWithoutOrgName": { - "message": "Your free trial ends today." + "message": "הניסיון החינמי שלך מסתיים היום." }, "clickHereToAddPaymentMethod": { - "message": "Click here to add a payment method." + "message": "לחץ כאן כדי להוסיף שיטת תשלום." }, "joinOrganization": { "message": "הצטרף לארגון" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "הצטרף אל $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3953,7 +4167,7 @@ "message": "הוזמנת להצטרף לארגון הרשום לעיל. בכדי להסכים, עליך להתחבר או ליצור חשבון Bitwarden חדש." }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Finish joining this organization by setting a master password." + "message": "סיים להצטרף לארגון זה על ידי הגדרת סיסמה ראשית." }, "inviteAccepted": { "message": "ההזמנה התקבלה" @@ -3962,7 +4176,7 @@ "message": "תוכל לקבל גישה לארגון זה כשאחד המנהלים יאשר את החברות שלך. נשלח לך מייל כשזה יקרה." }, "inviteInitAcceptedDesc": { - "message": "You can now access this organization." + "message": "אתה יכול עכשיו לגשת אל ארגון זה." }, "inviteAcceptFailed": { "message": "לא ניתן לקבל את ההזמנה. בקש ממנהל הארגון שישלח הזמנה חדשה." @@ -3980,13 +4194,16 @@ "message": "זכור אימייל" }, "recoverAccountTwoStepDesc": { - "message": "אם אין באפשרות לגשת לחשבונך דרך השיטות הדו-שלביות הרגילות, תוכל להשתמש בקוד לשחזור האימות הדו שלבי בכדי לבטל את כל ספקי האימות הדו שלבי בחשבונך." + "message": "אם אינך יכול לגשת לחשבון שלך דרך שיטות הכניסה הדו־שלבית הרגילות שלך, אתה יכול להשתמש בקוד השחזור של הכניסה הדו־שלבית שלך כדי לכבות את כל הספקים הדו־שלביים בחשבונך." + }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "היכנס למטה באמצעות קוד השחזור החד־פעמי שלך. זה יכבה את כל הספקים הדו־שלביים בחשבון שלך." }, "recoverAccountTwoStep": { - "message": "שחזר כניסה דו שלבית לחשבון" + "message": "שחזר כניסה דו־שלבית לחשבון" }, "twoStepRecoverDisabled": { - "message": "כניסה דו שלבית בוטלה בחשבונך." + "message": "כניסה דו־שלבית כבויה בחשבונך." }, "learnMore": { "message": "למידע נוסף" @@ -3998,22 +4215,22 @@ "message": "אם החשבון שלך אכן קיים, שלחנו אליך מייל עם הוראות נוספות." }, "deleteRecoverConfirmDesc": { - "message": "ביקשת למחוק את חשבון ה-Bitwarden שלך. לחץ על הכפתור למטה בכדי לאשר זאת." + "message": "ביקשת למחוק את החשבון Bitwarden שלך. לחץ על הכפתור למטה כדי לאשר." }, "deleteRecoverOrgConfirmDesc": { - "message": "You have requested to delete your Bitwarden organization." + "message": "ביקשת למחוק את ארגון ה־Bitwarden שלך." }, "myOrganization": { "message": "הארגון שלי" }, "organizationInfo": { - "message": "Organization info" + "message": "מידע על הארגון" }, "deleteOrganization": { "message": "מחק ארגון" }, "deletingOrganizationContentWarning": { - "message": "Enter the master password to confirm deletion of $ORGANIZATION$ and all associated data. Vault data in $ORGANIZATION$ includes:", + "message": "הזן את הסיסמה הראשית כדי לאשר את מחיקה של $ORGANIZATION$ וכל הנתונים המשויכים. נתוני כספת ב־$ORGANIZATION$ כוללים:", "placeholders": { "organization": { "content": "$1", @@ -4022,10 +4239,10 @@ } }, "deletingOrganizationActiveUserAccountsWarning": { - "message": "User accounts will remain active after deletion but will no longer be associated to this organization." + "message": "חשבונות משתמשים יישארו פעילים לאחר המחיקה אבל לא יהיו משויכים יותר אל ארגון זה." }, "deletingOrganizationIsPermanentWarning": { - "message": "Deleting $ORGANIZATION$ is permanent and irreversible.", + "message": "מחיקת $ORGANIZATION$ היא לצמיתות ובלתי הפיכה.", "placeholders": { "organization": { "content": "$1", @@ -4043,7 +4260,7 @@ "message": "הארגון עודכן" }, "taxInformation": { - "message": "מידע מיסים" + "message": "פרטי מס" }, "taxInformationDesc": { "message": "עבור לקוחות בתוך ארצות הברית, יש לכתוב מיקוד לצורך דיווח מיסוי. עבור לקוחות ממדינות אחרות ניתן למלא מספר זיהוי מס (VAT/GST) ו\\או כתובת שתופיע על הקבלות שלך." @@ -4053,7 +4270,7 @@ "description": "A billing plan/package. For example: Families, Teams, Enterprise, etc." }, "changeBillingPlan": { - "message": "שנה תוכנית", + "message": "שדרג תוכנית", "description": "A billing plan/package. For example: Families, Teams, Enterprise, etc." }, "changeBillingPlanUpgrade": { @@ -4071,7 +4288,7 @@ } }, "viewInvoice": { - "message": "צפה בחשבונית" + "message": "הצג חשבונית" }, "downloadInvoice": { "message": "הורד חשבונית" @@ -4086,10 +4303,10 @@ "message": "אופציית תשלום באמצעות חשבון בנק זמינה אך ורק ללקוחות תושבי ארצות הברית. תצטרך לאמת את פרטי החשבון. אנו נבצע 2 מיקרו-הפקדות בתוך 1-2 ימי עסקים. הזן את הסכומים בעמוד פרטי הארגון המשלם בכדי לאמת את חשבון הבנק." }, "verifyBankAccountFailureWarning": { - "message": "בעיות באימות פרטי החשבון עלולות להסתיים בתשלומים ש'התפספסו' ויכולות לגרום למנוי שלך, להתבטל." + "message": "כשל באימות חשבון הבנק יגרום לפספוס תשלום ולהשעיית המנוי שלך." }, "verifiedBankAccount": { - "message": "חשבון בנק אומת." + "message": "חשבון הבנק אומת" }, "bankAccount": { "message": "חשבון בנק" @@ -4105,7 +4322,7 @@ } }, "routingNumber": { - "message": "מספר הניתוב", + "message": "מספר ניתוב", "description": "Bank account routing number" }, "accountNumber": { @@ -4127,31 +4344,31 @@ "message": "הכנס את מספר ההתקנה שלך" }, "limitSubscriptionDesc": { - "message": "Set a seat limit for your subscription. Once this limit is reached, you will not be able to invite new members." + "message": "הגדר מגבלת מקום עבור המנוי שלך. ברגע שמגבלה זו תושג, לא תוכל להזמין חברים חדשים." }, "limitSmSubscriptionDesc": { - "message": "Set a seat limit for your Secrets Manager subscription. Once this limit is reached, you will not be able to invite new members." + "message": "הגדר מגבלת מקום עבור המנוי של מנהל הסודות שלך. ברגע שמגבלה זו תושג, לא תוכל להזמין חברים חדשים." }, "maxSeatLimit": { - "message": "Seat Limit (optional)", + "message": "מגבלת מקום (אופציונלי)", "description": "Upper limit of seats to allow through autoscaling" }, "maxSeatCost": { - "message": "Max potential seat cost" + "message": "עלות מקום פוטנציאלית מרבית" }, "addSeats": { - "message": "הוסף כסאות", + "message": "הוסף מקומות", "description": "Seat = User Seat" }, "removeSeats": { - "message": "הסר כסאות", + "message": "הסר מקומות", "description": "Seat = User Seat" }, "subscriptionDesc": { - "message": "Adjustments to your subscription will result in prorated changes to your billing totals. If newly invited users exceed your subscription seats, you will immediately receive a prorated charge for the additional users." + "message": "התאמות למנוי שלך יגרמו לשינויים יחסיים לסך כל החיובים שלך. אם משתמשים חדשים שהוזמנו חורגים ממקומות המנוי שלך, תקבל באופן מיידי חיוב יחסי עבור המשתמשים הנוספים." }, "subscriptionUserSeats": { - "message": "המנוי שלך מתיר עד $COUNT$ משתמשים.", + "message": "המנוי שלך מאפשר בסך הכל $COUNT$ חברים.", "placeholders": { "count": { "content": "$1", @@ -4160,34 +4377,34 @@ } }, "limitSubscription": { - "message": "Limit subscription (optional)" + "message": "הגבל מנוי (אופציונלי)" }, "subscriptionSeats": { - "message": "Subscription seats" + "message": "מקומות מנוי" }, "subscriptionUpdated": { - "message": "Subscription updated" + "message": "המנוי עודכן" }, "subscribedToSecretsManager": { - "message": "Subscription updated. You now have access to Secrets Manager." + "message": "המנוי עודכן. עכשיו יש לך גישה למנהל הסודות." }, "additionalOptions": { - "message": "Additional options" + "message": "אפשרויות נוספות" }, "additionalOptionsDesc": { - "message": "For additional help in managing your subscription, please contact Customer Support." + "message": "לעזרה נוספת בניהול המנוי שלך, נא לפנות לתמיכת הלקוחות." }, "subscriptionUserSeatsUnlimitedAutoscale": { - "message": "Adjustments to your subscription will result in prorated changes to your billing totals. If newly invited members exceed your subscription seats, you will immediately receive a prorated charge for the additional members." + "message": "התאמות למנוי שלך יגרמו לשינויים יחסיים לסך כל החיובים שלך. אם חברים חדשים שהוזמנו חורגים ממקומות המנוי שלך, תקבל באופן מיידי חיוב יחסי עבור החברים הנוספים." }, "smStandaloneTrialSeatCountUpdateMessageFragment1": { - "message": "If you want to add additional" + "message": "אם אתה רוצה להוסיף מקומות נוספים של" }, "smStandaloneTrialSeatCountUpdateMessageFragment2": { - "message": "seats without the bundled offer, please contact" + "message": "ללא ההצעה המצורפת, נא לפנות אל" }, "subscriptionUserSeatsLimitedAutoscale": { - "message": "Adjustments to your subscription will result in prorated changes to your billing totals. If newly invited members exceed your subscription seats, you will immediately receive a prorated charge for the additional members until your $MAX$ seat limit is reached.", + "message": "התאמות למנוי שלך יגרמו לשינויים יחסיים לסך כל החיובים שלך. אם חברים חדשים שהוזמנו חורגים ממקומות המנוי שלך, תקבל באופן מיידי חיוב יחסי עבור החברים הנוספים עד שתושג מגבלת $MAX$ המקומות שלך.", "placeholders": { "max": { "content": "$1", @@ -4196,7 +4413,7 @@ } }, "subscriptionUserSeatsWithoutAdditionalSeatsOption": { - "message": "You can invite up to $COUNT$ members for no additional charge. Contact Customer Support to upgrade your plan and invite more members.", + "message": "אתה יכול להזמין עד $COUNT$ חברים ללא תשלום נוסף. פנה אל תמיכת לקוחות כדי לשדרג את התוכנית שלך ולהזמין עוד חברים.", "placeholders": { "count": { "content": "$1", @@ -4205,7 +4422,7 @@ } }, "subscriptionFreePlan": { - "message": "You cannot invite more than $COUNT$ members without upgrading your plan.", + "message": "אתה לא יכול להזמין יותר מ־$COUNT$ חברים מבלי לשדרג את התוכנית שלך.", "placeholders": { "count": { "content": "$1", @@ -4214,7 +4431,7 @@ } }, "subscriptionUpgrade": { - "message": "You cannot invite more than $COUNT$ members without upgrading your plan.", + "message": "אתה לא יכול להזמין יותר מ־$COUNT$ חברים מבלי לשדרג את התוכנית שלך.", "placeholders": { "count": { "content": "$1", @@ -4223,7 +4440,7 @@ } }, "subscriptionSponsoredFamiliesPlan": { - "message": "Your subscription allows for a total of $COUNT$ members. Your plan is sponsored and billed to an external organization.", + "message": "המנוי שלך מאפשר סך הכל $COUNT$ חברים. התוכנית שלך ממומנת ומחויבת לארגון חיצוני.", "placeholders": { "count": { "content": "$1", @@ -4232,7 +4449,7 @@ } }, "subscriptionMaxReached": { - "message": "Adjustments to your subscription will result in prorated changes to your billing totals. You cannot invite more than $COUNT$ members without increasing your subscription seats.", + "message": "התאמות למנוי שלך יגרמו לשינויים יחסיים לסך כל החיובים שלך. אתה לא יכול להזמין יותר מ־$COUNT$ חברים מבלי להגדיל את מקומות המנוי שלך.", "placeholders": { "count": { "content": "$1", @@ -4241,7 +4458,7 @@ } }, "subscriptionSeatMaxReached": { - "message": "You cannot invite more than $COUNT$ members without increasing your subscription seats.", + "message": "אתה לא יכול להזמין יותר מ־$COUNT$ חברים מבלי להגדיל את מקומות המנוי שלך.", "placeholders": { "count": { "content": "$1", @@ -4250,10 +4467,10 @@ } }, "seatsToAdd": { - "message": "כסאות להוספה" + "message": "מקומות להוספה" }, "seatsToRemove": { - "message": "כסאות להסרה" + "message": "מקומות להסרה" }, "seatsAddNote": { "message": "הוספת כסאות משתמשים משנה את העלויות. פעולה זו מחוייבת באופן מיידי לפי שיטת החיוב שלך. בנוסף, החיוב הבא יכלול את ההפרש היחסי ממחזור החיוב הנוכחי." @@ -4271,10 +4488,62 @@ } }, "encryptionKeyUpdateCannotProceed": { - "message": "Encryption key update cannot proceed" + "message": "עדכון מפתח הצפנה לא יכול להמשיך" + }, + "editFieldLabel": { + "message": "ערוך $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "סדר מחדש את $LABEL$. השתמש במקש חץ כדי להעביר את הפריט למעלה או למטה.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ עבר למעלה, מיקום $INDEX$ מתוך $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ עבר למטה, מיקום $INDEX$ מתוך $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } }, "keyUpdateFoldersFailed": { - "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." + "message": "בעת עדכון מפתח ההצפנה שלך, התיקיות שלך לא היה ניתנות לפענוח. כדי להמשיך עם העדכון, התיקיות שלך מוכרחות להימחק. לא יימחקו פריטי כספת אם תמשיך." }, "keyUpdated": { "message": "המפתח עודכן" @@ -4283,13 +4552,13 @@ "message": "עדכן מפתח הצפנה" }, "updateEncryptionSchemeDesc": { - "message": "We've changed the encryption scheme to provide better security. Update your encryption key now by entering your master password below." + "message": "שינינו את סכמת ההצפנה כדי לספק אבטחה טובה יותר. עדכן את מפתח ההצפנה שלך כעת על ידי הזנת הסיסמה הראשית שלך למטה." }, "updateEncryptionKeyWarning": { "message": "לאחר עדכון מפתחות ההצפנה שלך, תתבקש לצאת ולהכנס שוב בכל אפליקציות Bitwarden שאתה משתמש בהן (האפליקציה לפלאפון או ההרחבה לדפדפן). אם לא תצא ותכנס שוב (פעולת הכניסה מורידה את המפתח החדש), יתכן שתתקל במידע שגוי. אנו ננסה לגרום ליציאה אוטומטית, אך יתכן שהדבר לא יקרה מיידית." }, "updateEncryptionKeyExportWarning": { - "message": "Any encrypted exports that you have saved will also become invalid." + "message": "כל הייצואים המוצפנים ששמרת יהפכו גם הם ללא תקפים." }, "subscription": { "message": "מנוי" @@ -4307,7 +4576,7 @@ "message": "תכונה זו לא זמינה בתוכנית החינמיית עבור ארגונים. עבור לתוכנית בתשלום בכדי להשתמש בתכונות נוספות." }, "createOrganizationStep1": { - "message": "יצירת ארגון: צעד 1" + "message": "צור ארגון: שלב 1" }, "createOrganizationCreatePersonalAccount": { "message": "לפני יצירת הארגון, עליך ליצור חשבון אישי חינמי." @@ -4319,25 +4588,25 @@ "message": "לא בחרת כלום." }, "receiveMarketingEmailsV2": { - "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." + "message": "קבל עצות, הכרזות, והזדמנויות מחקר מאת Bitwarden בדואר הנכנס שלך." }, "unsubscribe": { - "message": "Unsubscribe" + "message": "בטל הרשמה" }, "atAnyTime": { - "message": "at any time." + "message": "בכל זמן." }, "byContinuingYouAgreeToThe": { - "message": "By continuing, you agree to the" + "message": "על ידי המשך, אתה מסכים ל" }, "and": { - "message": "and" + "message": "ו" }, "acceptPolicies": { "message": "סימון תיבה זו מהווה את הסכמתך לתנאים הבאים:" }, "acceptPoliciesRequired": { - "message": "Terms of Service and Privacy Policy have not been acknowledged." + "message": "תנאי השימוש ומדיניות הפרטיות לא הוכרו." }, "termsOfService": { "message": "תנאי שירות" @@ -4349,16 +4618,16 @@ "message": "מסננים" }, "vaultTimeout": { - "message": "משך זמן מירבי עבור חיבור לכספת" + "message": "פסק זמן לכספת" }, "vaultTimeout1": { - "message": "Timeout" + "message": "פסק זמן" }, "vaultTimeoutDesc": { - "message": "בחר כמה זמן יעבור כדי שהכספת תסגר לאחר חוסר פעילות ותבצע את הפעולה שנבחרה." + "message": "בחר מתי הכספת שלך תנקוט בפעולת פסק הזמן לכספת." }, "vaultTimeoutLogoutDesc": { - "message": "Choose when your vault will be logged out." + "message": "בחר מתי הכספת שלך תינעל." }, "oneMinute": { "message": "דקה אחת" @@ -4386,7 +4655,7 @@ "description": "ex. Date this item was updated" }, "dateCreated": { - "message": "Created", + "message": "נוצר", "description": "ex. Date this item was created" }, "datePasswordUpdated": { @@ -4394,22 +4663,22 @@ "description": "ex. Date this password was updated" }, "organizationIsDisabled": { - "message": "הארגון הושבת." + "message": "הארגון הושעה" }, "secretsAccessSuspended": { - "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." + "message": "לא ניתן לגשת אל ארגונים מושעים. נא לפנות לבעל הארגון שלך עבור סיוע." }, "secretsCannotCreate": { - "message": "Secrets cannot be created in suspended organizations. Please contact your organization owner for assistance." + "message": "לא ניתן ליצור סודות בארגונים מושעים. נא לפנות לבעל הארגון שלך עבור סיוע." }, "projectsCannotCreate": { - "message": "Projects cannot be created in suspended organizations. Please contact your organization owner for assistance." + "message": "לא ניתן ליצור פרויקטים בארגונים מושעים. נא לפנות אל בעל הארגון שלך עבור סיוע." }, "serviceAccountsCannotCreate": { - "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." + "message": "לא ניתן ליצור חשבונות שירות בארגונים מושעים. נא לפנות אל בעל הארגון שלך עבור סיוע." }, "disabledOrganizationFilterError": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + "message": "לא ניתן לגשת לפריטים בארגון מושעה. פנה אל בעל הארגון שלך עבור סיוע." }, "licenseIsExpired": { "message": "תוקף הרשיון הסתיים." @@ -4421,7 +4690,7 @@ "message": "נבחר\\ו" }, "recommended": { - "message": "Recommended" + "message": "מומלץ" }, "ownership": { "message": "בעלות" @@ -4449,13 +4718,13 @@ "message": "סיסמה ראשית חלשה" }, "weakMasterPasswordDesc": { - "message": "הסיסמה הראשית שבחרת חלשה מאוד. עליך לבחור סיסמה חזקה יותר (או להשתמש במשפט במקום מילה אחת) בכדי לאבטח את החשבון שלך. האם אתה בטוח שברצונך להשתמש בסיסמה ראשית זו?" + "message": "סיסמה חלשה זוהתה. השתמש בסיסמה חזקה כדי להגן על חשבונך. האם אתה בטוח שאתה רוצה להשתמש בסיסמה חלשה?" }, "rotateAccountEncKey": { "message": "כמו כן החלף את מפתח ההצפנה של החשבון שלי" }, "rotateEncKeyTitle": { - "message": "החלף מפתח הצפנה" + "message": "סובב מפתח הצפנה" }, "rotateEncKeyConfirmation": { "message": "האם אתה בטוח שברצונך להחליף (לבצע רוטציה) של מפתח ההצפנה בחשבונך?" @@ -4464,7 +4733,7 @@ "message": "לפריט זה יש קובץ מצורף שצריך תיקון." }, "attachmentFixDescription": { - "message": "This attachment uses outdated encryption. Select 'Fix' to download, re-encrypt, and re-upload the attachment." + "message": "צרופה זו משתמשת בהצפנה מיושנת. בחר 'תקן' כדי להוריד, להצפין מחדש, ולהעלות מחדש את הצרופה." }, "fix": { "message": "תקן", @@ -4482,17 +4751,17 @@ "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "fingerprintMatchInfo": { - "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." + "message": "נא לוודא שהכספת שלך פתוחה ושביטוי טביעת האצבע תואם את המכשיר האחר." }, "fingerprintPhraseHeader": { - "message": "Fingerprint phrase" + "message": "ביטוי טביעת אצבע" }, "dontAskFingerprintAgain": { - "message": "אל תבקש ממני לאמת את משפט טביעת האצבע יותר", + "message": "לעולם אל תנחה משתמשים מוזמנים לאמת ביטוי טביעת אצבע (לא מומלץ)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "youWillBeNotifiedOnceTheRequestIsApproved": { - "message": "You will be notified once the request is approved" + "message": "תקבל התראה כאשר הבקשה תאושר" }, "free": { "message": "חינם", @@ -4521,10 +4790,10 @@ "description": "'OAuth 2.0' is a programming protocol. It should probably not be translated." }, "viewApiKey": { - "message": "צפה במפתח API" + "message": "הצג מפתח API" }, "rotateApiKey": { - "message": "קבל מפתח API חדש" + "message": "סובב מפתח API" }, "selectOneCollection": { "message": "עליך לבחור לפחות אוסף אחד." @@ -4539,28 +4808,34 @@ "message": "שכפול" }, "masterPassPolicyTitle": { - "message": "Master password requirements" + "message": "דרישות סיסמה ראשית" }, "masterPassPolicyDesc": { - "message": "קבע דרישות מינימום עבור חוזק הסיסמה הראשית." + "message": "קבע דרישות עבור חוזק הסיסמה הראשית." + }, + "passwordStrengthScore": { + "message": "ציון חוזק סיסמה $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } }, "twoStepLoginPolicyTitle": { - "message": "Require two-step login" + "message": "דרוש כניסה דו-שלבית" }, "twoStepLoginPolicyDesc": { - "message": "דרוש מהמשתמשים להגדיר כניסה דו-שלבית בחשבונות האישיים שלהם." + "message": "דרוש מחברים להגדיר כניסה דו־שלבית." }, "twoStepLoginPolicyWarning": { - "message": "חברי ארגון ללא הגדרת כניסה דו-שלבית יוסרו מהארגון ויקבלו אימייל המסביר את השינוי." + "message": "חברי ארגון שאינם בעלים או מנהלים ואין להם כניסה דו־שלבית מוגדרת עבור חשבונם יוסרו מהארגון ויקבלו דוא\"ל המודיע להם על השינוי." }, "twoStepLoginPolicyUserWarning": { - "message": "הינך חבר בארגון המחייב כניסה דו-שלבית מוגדרת בחשבונך. אם תבטל את כל הספקים המאפשרים כניסה דו-שלבית, תוסר אוטומטית מהארגון." + "message": "הנך חבר בארגון אשר דורש שכניסה דו־שלבית תהיה מוגדרת בחשבון המשתמש שלך. אם תכבה את כל ספקי הכניסה הדו־שלבית, אתה תוסר באופן אוטומטי מהארגונים האלה." }, "passwordGeneratorPolicyDesc": { - "message": "הגדר דרישות מינימום במחולל הסיסמאות." - }, - "passwordGeneratorPolicyInEffect": { - "message": "מדיניות ארגונית אחת או יותר משפיעה על הגדרות המחולל שלך." + "message": "הגדר דרישות עבור מחולל הסיסמאות." }, "masterPasswordPolicyInEffect": { "message": "אחד או יותר מכללי מדיניות הארגון דורשים שסיסמתך הראשית תעמוד בדרישות הבאות:" @@ -4605,23 +4880,23 @@ "message": "הסיסמה הראשית החדשה שלך לא עומדת בדרישות המדיניות." }, "minimumNumberOfWords": { - "message": "מספר מינימאלי של מילים" + "message": "מספר מינימלי של מילים" }, "overridePasswordTypePolicy": { - "message": "Password Type", + "message": "סוג סיסמה", "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { - "message": "העדפות משתמש" + "message": "העדפת משתמש" }, "vaultTimeoutAction": { - "message": "פעולה לביצוע בכספת בתום זמן החיבור" + "message": "פעולת פסק זמן לכספת" }, "vaultTimeoutActionLockDesc": { - "message": "בכדי לקבל גישה לכספת נעולה, יש להזין את הסיסמה הראשית שוב." + "message": "נדרשת סיסמה ראשית או שיטת ביטול נעילה אחרת כדי לגשת לכספת שלך שוב." }, "vaultTimeoutActionLogOutDesc": { - "message": "בכדי לקבל גישה לכספת שיצאו ממנה, יש לבצע אימות מחדש." + "message": "נדרש אימות מחדש כדי לגשת לכספת שלך שוב." }, "lock": { "message": "נעילה", @@ -4632,25 +4907,25 @@ "description": "Noun: A special folder for holding deleted items that have not yet been permanently deleted" }, "searchTrash": { - "message": "חפש בסל המחזור" + "message": "חפש באשפה" }, "permanentlyDelete": { "message": "מחק לצמיתות" }, "permanentlyDeleteSelected": { - "message": "מחק לצמיתות פריטים שנבחרו" + "message": "מחק לצמיתות את מה שנבחר" }, "permanentlyDeleteItem": { - "message": "מחק לצמיתות פריט שנבחר" + "message": "מחק לצמיתות פריט" }, "permanentlyDeleteItemConfirmation": { "message": "האם אתה בטוח שברצונך למחוק את הפריט הזה?" }, "permanentlyDeletedItem": { - "message": "פריט שנמחק לצמיתות" + "message": "הפריט נמחק לצמיתות" }, "permanentlyDeletedItems": { - "message": "פריטים שנמחקו לצמיתות" + "message": "הפריטים נמחקו לצמיתות" }, "permanentlyDeleteSelectedItemsDesc": { "message": "בחרת $COUNT$ פריט(ים) למחיקה לצמיתות. האם אתה בטוח שברצונך למחוק את כולם?", @@ -4662,7 +4937,7 @@ } }, "permanentlyDeletedItemId": { - "message": "פריט שנמחק לצמיתות $ID$.", + "message": "הפריט $ID$ נמחק לצמיתות", "placeholders": { "id": { "content": "$1", @@ -4674,16 +4949,16 @@ "message": "שחזר" }, "restoreSelected": { - "message": "שחזר בחירה" + "message": "שחזר את מה שנבחר" }, "restoredItem": { - "message": "פריט ששוחזר" + "message": "הפריט שוחזר" }, "restoredItems": { - "message": "פריטים ששוחזרו" + "message": "הפריטים שוחזרו" }, "restoredItemId": { - "message": "פריט ששוחזר $ID$.", + "message": "הפריט $ID$ שוחזר", "placeholders": { "id": { "content": "$1", @@ -4695,7 +4970,7 @@ "message": "יציאה תגרום להסרת כל גישה שיש לך לכספת ודורשת אימות אונליין לאחר משך זמן מסויים. האם אתה בטוח שברצונך להשתמש באפשרות זו?" }, "vaultTimeoutLogOutConfirmationTitle": { - "message": "אישור פעולת אימות לאחר חוסר פעילות" + "message": "אישור פעולת פסק זמן" }, "hidePasswords": { "message": "הסתר סיסמאות" @@ -4713,7 +4988,7 @@ "message": "מידע מיסוי עודכן." }, "setMasterPassword": { - "message": "קבע סיסמה ראשית" + "message": "הגדר סיסמה ראשית" }, "identifier": { "message": "מזהה" @@ -4722,40 +4997,40 @@ "message": "מזהה ארגוני" }, "ssoLogInWithOrgIdentifier": { - "message": "הכנס באמצעות פורטל ההזדהות האחודה (SSO) הארגוני שלך. אנא הזן את המזהה הארגוני שלך כדי להתחיל." + "message": "כנס באמצעות הפורטל לכניסה יחידה של הארגון שלך. אנא הזן את מזהה ה־SSO של הארגון שלך כדי להתחיל." }, "singleSignOnEnterOrgIdentifier": { - "message": "Enter your organization's SSO identifier to begin" + "message": "הזן את מזהה ה־SSO של הארגון שלך כדי להתחיל" }, "singleSignOnEnterOrgIdentifierText": { - "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + "message": "כדי להיכנס ספק ה־SSO שלך, הזן את מזהה ה־SSO של הארגון שלך כדי להתחיל. ייתכן שתצטרך להזין את מזהה SSO זה כאשר אתה נכנס ממכשיר חדש." }, "enterpriseSingleSignOn": { - "message": "כניסה ארגונית אחודה" + "message": "כניסה יחידה ארגונית" }, "ssoHandOff": { "message": "ניתן לסגור את הטאב הנוכחי ולהמשיך את השימוש בתוסף." }, "youSuccessfullyLoggedIn": { - "message": "You successfully logged in" + "message": "נכנסת בהצלחה" }, "thisWindowWillCloseIn5Seconds": { - "message": "This window will automatically close in 5 seconds" + "message": "חלון זה ייסגר באופן אוטומטי בעוד 5 שניות" }, "youMayCloseThisWindow": { - "message": "You may close this window" + "message": "אתה רשאי לסגור חלון זה" }, "includeAllTeamsFeatures": { "message": "כל התכונות הקיימות ב\"צוות\", ובנוסף:" }, "includeAllTeamsStarterFeatures": { - "message": "All Teams Starter features, plus:" + "message": "כל התכונות של צוותים מתחילים, ובנוסף:" }, "chooseMonthlyOrAnnualBilling": { - "message": "Choose monthly or annual billing" + "message": "בחר חיוב חודשי או שנתי" }, "abilityToAddMoreThanNMembers": { - "message": "Ability to add more than $COUNT$ members", + "message": "יכולת להוסיף יותר מ־$COUNT$ חברים", "placeholders": { "count": { "content": "$1", @@ -4767,65 +5042,89 @@ "message": "אימות SSO דרך SAML2.0 וOpenID Connect" }, "includeEnterprisePolicies": { - "message": "מדיניות ארגונית" + "message": "פוליסות ארגוניות" }, "ssoValidationFailed": { "message": "אימות SSO נכשל" }, "ssoIdentifierRequired": { - "message": "מזהה הארגון נחוץ." + "message": "נדרש מזהה SSO של הארגון." }, "ssoIdentifier": { - "message": "SSO identifier" + "message": "מזהה SSO" }, "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", + "message": "ספק את המזהה הזה לחברים שלך כדי שיכנסו עם SSO. כדי לעקוף את השלב הזה, הגדר ", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" }, "unlinkSso": { "message": "נתק SSO" }, "unlinkSsoConfirmation": { - "message": "Are you sure you want to unlink SSO for this organization?" + "message": "האם אתה בטוח שברצונך לנתק SSO עבור ארגון זה?" }, "linkSso": { "message": "חבר SSO" }, "singleOrg": { - "message": "ארגון יחידני" + "message": "ארגון יחיד" }, "singleOrgDesc": { - "message": "מונע מהמשתמשים אפשרות צירוף לארגונים אחרים." + "message": "מנע מחברים מלהצטרף לארגונים אחרים." }, "singleOrgPolicyDesc": { - "message": "Restrict members from joining other organizations. This policy is required for organizations that have enabled domain verification." + "message": "הגבל משתמשים מלהצטרף לארגונים אחרים. מדיניות זו נדרשת עבור ארגונים שאפשרו אימות דומיין." }, "singleOrgBlockCreateMessage": { "message": "לפי מדיניות הארגון שלך, אין באפשרותך להצטרף ליותר מארגון אחד. אנא צור קשר עם מנהלי הארגון שלך, או לחלופין - צור חשבון Bitwarden נפרד." }, "singleOrgPolicyWarning": { - "message": "חברי ארגון שאינם הבעלים או המנהלים וכבר עכשיו הם חלק מארגון אחר - יוסרו מהארגון שלך." + "message": "חברי ארגון שאינם בעלים או מנהלים ושכבר חברים בארגון אחר יוסרו מהארגון שלך." }, "singleOrgPolicyMemberWarning": { - "message": "Non-compliant members will be placed in revoked status until they leave all other organizations. Administrators are exempt and can restore members once compliance is met." + "message": "חברים שאינם עומדים בדרישות במצב מבוטל עד שיעזבו את כל שאר הארגונים. מנהלים הם פטורים ויכולים לשחזר חברים ברגע שיעמדו בדרישות." }, "requireSso": { - "message": "אימות בעזרת כניסה אחודה" + "message": "דרוש אימות כניסה יחידה" }, "requireSsoPolicyDesc": { - "message": "מחייב את המשתמשים להשתמש בכניסה אחודה של הארגון." + "message": "דרוש מחברים להיכנס עם שיטת הכניסה היחידה הארגונית." }, "prerequisite": { "message": "תנאים מקדימים" }, "requireSsoPolicyReq": { - "message": "יש לסמן את מדיניות הארגון היחידני לפני הפעלת מדיניות זו." + "message": "יש להפעיל את המדיניות הארגונית של הארגון היחיד לפני הפעלת מדיניות זו." }, "requireSsoPolicyReqError": { - "message": "מדיניות ארגון יחידני לא הופעלה." + "message": "מדיניות ארגון יחיד לא הוגדרה." }, "requireSsoExemption": { - "message": "מנהלי ובעלי הארגון מוחרגים מאכיפת מדיניות זו." + "message": "מנהלי ובעלי הארגון פטורים מהאכיפה של מדיניות זו." + }, + "limitSendViews": { + "message": "הגבל צפיות" + }, + "limitSendViewsHint": { + "message": "אף אחד לא יכול לצפות בסֵנְד זה לאחר ההגעה למגבלה.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ צפיות נותרו", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "פרטי סֵנְד", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "טקסט לשיתוף" }, "sendTypeFile": { "message": "קובץ" @@ -4833,8 +5132,12 @@ "sendTypeText": { "message": "טקסט" }, + "sendPasswordDescV3": { + "message": "הוסף סיסמה אופציונלית עבור נמענים כדי לגשת לסֵנְד זה.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { - "message": "צור Send חדש", + "message": "סֵנְד חדש", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editSend": { @@ -4842,76 +5145,57 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSend": { - "message": "הSend נוצר בהצלחה", + "message": "סֵנְד נשמר", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { - "message": "הSend נערך", + "message": "סֵנְד נשמר", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletedSend": { - "message": "הSend נמחק", + "message": "סֵנְד נמחק", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deleteSend": { "message": "מחק Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "האם אתה בטוח שברצונך למחוק Send זה?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "מה סוג הSend?", + "deleteSendPermanentConfirmation": { + "message": "האם אתה בטוח שברצונך למחוק לצמיתות סֵנְד זה?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "תאריך מחיקה" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", + "deletionDateDescV2": { + "message": "סֵנְד זה יימחק לצמיתות בתאריך זה.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { "message": "תאריך תפוגה" }, "expirationDateDesc": { - "message": "If set, access to this Send will expire on the specified date and time.", + "message": "אם מוגדר, גישה לסֵנְד זה תפוג בתאריך ובשעה שצוינו.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "maxAccessCount": { - "message": "כמות גישות מקסימלית" - }, - "maxAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "כמות גישות נוכחית" - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + "message": "מספר גישות מרבי" }, "disabled": { "message": "מבוטל" }, "revoked": { - "message": "Revoked" + "message": "מבוטל" }, "sendLink": { - "message": "לינק לSend", + "message": "קישור סֵנְד", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "copyLink": { - "message": "Copy link" + "message": "העתק קישור" }, "copySendLink": { - "message": "העתק לינק לSend", + "message": "העתק קישור סֵנְד", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "removePassword": { @@ -4923,23 +5207,19 @@ "removePasswordConfirmation": { "message": "האם אתה בטוח שברצונך להסיר את הסיסמה?" }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, - "disableThisSend": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "כל הSendים" }, "maxAccessCountReached": { - "message": "Max access count reached", + "message": "מספר הגישות המרבי הושג", "description": "This text will be displayed after a Send has been accessed the maximum amount of times." }, "pendingDeletion": { "message": "ממתין להסרה" }, + "hideTextByDefault": { + "message": "הסתר טקסט כברירת מחדל" + }, "expired": { "message": "פג תוקף" }, @@ -4952,22 +5232,22 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendProtectedPasswordDontKnow": { - "message": "לא יודע מה הסיסמה? בקש מהשולח את הסיסמה עבור הSend.", + "message": "לא יודע את הסיסמה? בקש מהשולח את הסיסמה הדרושה עבור סֵנְד זה.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendHiddenByDefault": { - "message": "הSend הזה מוסתר כברירת מחדל. באפשרותך לשנות את מצב ההסתרה בעזרת הכפתור להלן.", + "message": "סֵנְד זה מוסתר כברירת מחדל. אתה יכול לשנות את מצב הנראות שלו באמצעות הלחצן למטה.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "downloadAttachments": { - "message": "Download attachments" + "message": "הורד צרופות" }, "sendAccessUnavailable": { - "message": "The Send you are trying to access does not exist or is no longer available.", + "message": "הסֵנְד שאליו אתה מנסה לגשת אינו קיים או לא זמין יותר.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "missingSendFile": { - "message": "The file associated with this Send could not be found.", + "message": "הקובץ המשויך עם סֵנְד זה לא נמצא.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "noSendsInList": { @@ -4975,64 +5255,64 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "emergencyAccess": { - "message": "Emergency access" + "message": "גישת חירום" }, "emergencyAccessDesc": { - "message": "Grant and manage emergency access for trusted contacts. Trusted contacts may request access to either View or Takeover your account in case of an emergency. Visit our help page for more information and details into how zero knowledge sharing works." + "message": "הענק ונהל גישת חירום עבור אנשי קשר מהימנים. ייתכן שאנשי קשר מהימנים ידרשו גישה כדי לצפות בחשבון שלך או להשתלט עליו במקרה של מקרה חירום. בקר בעמוד העזרה שלנו עבור מידע נוסף ופרטים על איך שיתוף באפס ידיעה עובד." }, "emergencyAccessOwnerWarning": { - "message": "You are an owner of one or more organizations. If you give takeover access to an emergency contact, they will be able to use all your permissions as owner after a takeover." + "message": "אתה הבעלים של ארגון אחד או יותר. אם תתן גישת השתלטות לאיש קשר לשעת חירום, הוא יוכל להשתמש בכל ההרשאות שלך כבעלים לאחר ההשתלטות." }, "trustedEmergencyContacts": { - "message": "Trusted emergency contacts" + "message": "אנשי קשר לשעת חירום מהימנים" }, "noTrustedContacts": { - "message": "You have not added any emergency contacts yet, invite a trusted contact to get started." + "message": "עדיין לא הוספת איש קשר לשעת חירום, הזמן איש קשר מהימן כדי להתחיל." }, "addEmergencyContact": { - "message": "Add emergency contact" + "message": "הוסף איש קשר לשעת חירום" }, "designatedEmergencyContacts": { - "message": "Designated as emergency contact" + "message": "מונה כאיש קשר לשעת חירום" }, "noGrantedAccess": { - "message": "You have not been designated as an emergency contact for anyone yet." + "message": "עדיין אף אחד לא מינה אותך כאיש קשר לשעת חירום." }, "inviteEmergencyContact": { - "message": "Invite emergency contact" + "message": "הזמן איש קשר לשעת חירום" }, "editEmergencyContact": { - "message": "Edit emergency contact" + "message": "ערוך איש קשר לשעת חירום" }, "inviteEmergencyContactDesc": { - "message": "Invite a new emergency contact by entering their Bitwarden account email address below. If they do not have a Bitwarden account already, they will be prompted to create a new account." + "message": "הזמן איש קשר לשעת חירום חדש על ידי הזנת כתובת הדוא\"ל של חשבון ה־Bitwarden שלו למטה. אם אין לו כבר חשבון Bitwarden, הוא יתבקש ליצור חשבון חדש." }, "emergencyAccessRecoveryInitiated": { - "message": "Emergency access initiated" + "message": "גישת חירום החלה" }, "emergencyAccessRecoveryApproved": { - "message": "Emergency access approved" + "message": "גישת חירום אושרה" }, "viewDesc": { - "message": "Can view all items in your own vault." + "message": "יכול לצפות בכל הפריטים בכספת שלך." }, "takeover": { - "message": "Takeover" + "message": "השתלטות" }, "takeoverDesc": { - "message": "Can reset your account with a new master password." + "message": "יכול לאפס את החשבון שלך עם סיסמה ראשית חדשה." }, "waitTime": { - "message": "Wait time" + "message": "זמן המתנה" }, "waitTimeDesc": { - "message": "Time required before automatically granting access." + "message": "הזמן הנדרש לפני הענקת גישה באופן אוטומטי." }, "oneDay": { - "message": "1 day" + "message": "יום 1" }, "days": { - "message": "$DAYS$ days", + "message": "$DAYS$ ימים", "placeholders": { "days": { "content": "$1", @@ -5041,16 +5321,16 @@ } }, "invitedUser": { - "message": "Invited user." + "message": "המשתמש הוזמן." }, "acceptEmergencyAccess": { - "message": "You've been invited to become an emergency contact for the user listed above. To accept the invitation, you need to log in or create a new Bitwarden account." + "message": "הוזמנת להפוך להיות איש קשר לשעת חירום עבור המשתמש הרשום לעיל. כדי לאשר את ההזמנה, אתה מוכרח להיכנס או ליצור חשבון Bitwarden חדש." }, "emergencyInviteAcceptFailed": { - "message": "Unable to accept invitation. Ask the user to send a new invitation." + "message": "לא ניתן לקבל הזמנה. בקש מהמשתמש לשלוח הזמנה חדשה." }, "emergencyInviteAcceptFailedShort": { - "message": "Unable to accept invitation. $DESCRIPTION$", + "message": "לא ניתן לקבל הזמנה. $DESCRIPTION$", "placeholders": { "description": { "content": "$1", @@ -5059,13 +5339,13 @@ } }, "emergencyInviteAcceptedDesc": { - "message": "You can access the emergency options for this user after your identity has been confirmed. We'll send you an email when that happens." + "message": "אתה יכול לגשת אל אפשרויות החירום עבור משתמש זה לאחר שהזהות שלך אושרה. נשלח לך דוא\"ל כשזה יקרה." }, "requestAccess": { - "message": "Request Access" + "message": "בקש גישה" }, "requestAccessConfirmation": { - "message": "Are you sure you want to request emergency access? You will be provided access after $WAITTIME$ day(s) or whenever the user manually approves the request.", + "message": "האם אתה בטוח שברצונך לבקש גישת חירום? תינתן לך גישה לאחר $WAITTIME$ ימים או מתי שהמשתמש מאשר את הבקשה באופן ידני.", "placeholders": { "waittime": { "content": "$1", @@ -5074,7 +5354,7 @@ } }, "requestSent": { - "message": "Emergency access requested for $USER$. We'll notify you by email when it's possible to continue.", + "message": "ביקשת גישת חירום עבור $USER$. נודיע לך באמצעות דוא\"ל כאשר אפשר להמשיך.", "placeholders": { "user": { "content": "$1", @@ -5083,13 +5363,13 @@ } }, "approve": { - "message": "Approve" + "message": "אשר" }, "reject": { - "message": "Reject" + "message": "דחה" }, "approveAccessConfirmation": { - "message": "Are you sure you want to approve emergency access? This will allow $USER$ to $ACTION$ your account.", + "message": "האם אתה בטוח שברצונך לאשר גישת חירום? זה יאפשר ל־$USER$ לבצע את הפעולה הבאה בחשבון שלך: $ACTION$.", "placeholders": { "user": { "content": "$1", @@ -5102,13 +5382,13 @@ } }, "emergencyApproved": { - "message": "Emergency access approved" + "message": "גישת חירום אושרה" }, "emergencyRejected": { - "message": "Emergency access rejected" + "message": "גישת חירום נדחתה" }, "passwordResetFor": { - "message": "Password reset for $USER$. You can now login using the new password.", + "message": "הסיסמה אופסה עבור $USER$. אתה יכול כעת להיכנס באמצעות הסיסמה החדשה.", "placeholders": { "user": { "content": "$1", @@ -5117,59 +5397,52 @@ } }, "personalOwnership": { - "message": "Remove individual vault" + "message": "הסר כספת אישית" }, "personalOwnershipPolicyDesc": { - "message": "Require members to save items to an organization by removing the individual vault option." + "message": "דרוש מחברים לשמור פריטים לארגון על ידי הסרת האפשרות של כספת אישית." }, "personalOwnershipExemption": { - "message": "Organization owners and administrators are exempt from this policy's enforcement." + "message": "בעלי ארגונים ומנהלים פטורים מהאכיפה של המדיניות הזאת." }, "personalOwnershipSubmitError": { - "message": "Due to an Enterprise policy, you are restricted from saving items to your individual vault. Change the ownership option to an organization and choose from available collections." + "message": "בשל מדיניות ארגונית, אתה מוגבל מלשמור פריטים לכספת האישית שלך. שנה את אפשרות הבעלות לארגון ובחר מאוספים זמינים." }, "disableSend": { - "message": "Remove Send" + "message": "הסר סֵנְד" }, "disableSendPolicyDesc": { - "message": "Do not allow members to create or edit Sends.", + "message": "אל תאפשר לחברים ליצור או לערוך סֵנְדים.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "disableSendExemption": { - "message": "Organization members that can manage the organization's policies are exempt from this policy's enforcement." + "message": "חברי ארגון שיכולים לנהל את הפוליסות של הארגון הם פטורים מהאכיפה של המדיניות הזאת." }, "sendDisabled": { - "message": "Send removed", + "message": "סֵנְד הוסר", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { - "message": "Due to an Enterprise policy, you are only able to delete an existing Send.", + "message": "בשל מדיניות ארגונית, אתה יכול למחוק רק סֵנְד קיים.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendOptions": { - "message": "Send options", + "message": "אפשרויות סֵנְד", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendOptionsPolicyDesc": { - "message": "Set options for creating and editing Sends.", + "message": "הגדר אפשרויות ליצירת ועריכת סֵנְדים.", "description": "'Sends' is a plural noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendOptionsExemption": { - "message": "Organization members that can manage the organization's policies are exempt from this policy's enforcement." + "message": "חברי ארגון שיכולים לנהל את הפוליסות של הארגון הם פטורים מהאכיפה של המדיניות הזאת." }, "disableHideEmail": { - "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." - }, - "sendOptionsPolicyInEffect": { - "message": "The following organization policies are currently in effect:" - }, - "sendDisableHideEmailInEffect": { - "message": "Users are not allowed to hide their email address from recipients when creating or editing a Send.", + "message": "הצג תמיד את כתובת הדוא\"ל של חבר מנמענים בעת יצירת או עריכת סֵנְד.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "modifiedPolicyId": { - "message": "Modified policy $ID$.", + "message": "מדיניות $ID$ שונתה.", "placeholders": { "id": { "content": "$1", @@ -5178,79 +5451,79 @@ } }, "planPrice": { - "message": "Plan price" + "message": "מחיר תוכנית" }, "estimatedTax": { - "message": "Estimated tax" + "message": "מס משוער" }, "custom": { - "message": "Custom" + "message": "מותאם אישית" }, "customDesc": { - "message": "Grant customized permissions to members" + "message": "הענק הרשאות מותאמות אישית לחברים" }, "customDescNonEnterpriseStart": { - "message": "Custom roles is an ", + "message": "תפקידים מותאמים אישית היא ", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" }, "customDescNonEnterpriseLink": { - "message": "enterprise feature", + "message": "תכונה ארגונית", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" }, "customDescNonEnterpriseEnd": { - "message": ". Contact our support team to upgrade your subscription", + "message": ". פנה אל צוות התמיכה שלנו כדי לשדרג את המנוי שלך", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" }, "customNonEnterpriseError": { - "message": "To enable custom permissions the organization must be on an Enterprise 2020 plan." + "message": "כדי לאפשר הרשאות מותאמות אישית, הארגון מוכרח להיות בתוכנית ארגונית 2020." }, "permissions": { - "message": "Permissions" + "message": "הרשאות" }, "permission": { - "message": "Permission" + "message": "הרשאה" }, "accessEventLogs": { - "message": "Access event logs" + "message": "גישת יומני אירועים" }, "accessImportExport": { - "message": "Access import/export" + "message": "גישת ייבוא/ייצוא" }, "accessReports": { - "message": "Access reports" + "message": "גישת דוחות" }, "missingPermissions": { - "message": "You lack the necessary permissions to perform this action." + "message": "חסרות לך ההרשאות הנחוצות כדי לבצע פעולה זו." }, "manageAllCollections": { - "message": "Manage all collections" + "message": "ניהול כל האוספים" }, "createNewCollections": { - "message": "Create new collections" + "message": "יצירת אוספים חדשים" }, "editAnyCollection": { - "message": "Edit any collection" + "message": "עריכת כל אוסף" }, "deleteAnyCollection": { - "message": "Delete any collection" + "message": "מחיקת כל אוסף" }, "manageGroups": { - "message": "Manage groups" + "message": "ניהול קבוצות" }, "managePolicies": { - "message": "Manage policies" + "message": "ניהול פוליסות" }, "manageSso": { - "message": "Manage SSO" + "message": "ניהול SSO" }, "manageUsers": { - "message": "Manage users" + "message": "ניהול משתמשים" }, "manageAccountRecovery": { - "message": "Manage account recovery" + "message": "ניהול שחזור חשבון" }, "disableRequiredError": { - "message": "You must manually turn the $POLICYNAME$ policy before this policy can be turned off.", + "message": "אתה מוכרח להפעיל באופן ידני את המדיניות $POLICYNAME$ לפני שיהיה ניתן לכבות את המדיניות הזאת.", "placeholders": { "policyName": { "content": "$1", @@ -5259,153 +5532,132 @@ } }, "personalOwnershipPolicyInEffect": { - "message": "An organization policy is affecting your ownership options." + "message": "מדיניות ארגון משפיעה על אפשרויות הבעלות שלך." }, "personalOwnershipPolicyInEffectImports": { - "message": "An organization policy has blocked importing items into your individual vault." + "message": "מדיניות ארגון חסמה ייבוא פריטים אל תוך הכספת האישית שלך." }, "personalOwnershipCheckboxDesc": { - "message": "Remove individual ownership for organization users" - }, - "textHiddenByDefault": { - "message": "When accessing the Send, hide the text by default", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to Send." - }, - "sendFileDesc": { - "message": "The file you want to Send." - }, - "copySendLinkOnSave": { - "message": "Copy the link to share this Send to my clipboard upon save." - }, - "sendLinkLabel": { - "message": "Send link", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + "message": "הסר בעלות אישית עבור משתמשי ארגון" }, "send": { - "message": "Send", + "message": "סֵנְד", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendAccessTaglineProductDesc": { - "message": "Bitwarden Send transmits sensitive, temporary information to others easily and securely.", + "message": "Bitwarden סֵנְד משדר מידע רגיש וזמני לאחרים באופן קל ומאובטח.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendAccessTaglineLearnMore": { - "message": "Learn more about", + "message": "למד עוד אודות", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**Learn more about** Bitwarden Send or sign up to try it today.'" }, "sendVaultCardProductDesc": { - "message": "Share text or files directly with anyone." + "message": "שתף קישור או טקסט ישירות עם כל אחד." }, "sendVaultCardLearnMore": { - "message": "Learn more", + "message": "למד עוד", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**Learn more**, see how it works, or try it now. '" }, "sendVaultCardSee": { - "message": "see", + "message": "ראה", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more, **see** how it works, or try it now.'" }, "sendVaultCardHowItWorks": { - "message": "how it works", + "message": "איך זה עובד", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more, see **how it works**, or try it now.'" }, "sendVaultCardOr": { - "message": "or", + "message": "או", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more, see how it works, **or** try it now.'" }, "developmentDevOpsAndITTeamsChooseBWSecret": { - "message": "Development, DevOps, and IT teams choose Bitwarden Secrets Manager to securely manage and deploy their infrastructure and machine secrets." + "message": "צוותי פיתוח, DevOps, וטכ\"מ בוחרים את מנהל הסודות של Bitwarden כדי לנהל ולפרוס באופן מאובטח את התשתיות וסודות מכונה שלהם." }, "centralizeSecretsManagement": { - "message": "Centralize secrets management." + "message": "רכז את ניהול הסודות." }, "centralizeSecretsManagementDescription": { - "message": "Securely store and manage secrets in one location to prevent secret sprawl across your organization." + "message": "אחסן ונהל באופן מאובטח סודות במקום אחד כדי למנוע התפשטות סודות ברחבי הארגון שלך." }, "preventSecretLeaks": { - "message": "Prevent secret leaks." + "message": "מנע דליפת סודות." }, "preventSecretLeaksDescription": { - "message": "Protect secrets with end-to-end encryption. No more hard coding secrets or sharing through .env files." + "message": "הגן על הסודות שלך עם הצפנה מקצה־לקצה. לא עוד תכנות נוקשה של סודות או שיתוף באמצעות קבצי env." }, "enhanceDeveloperProductivity": { - "message": "Enhance developer productivity." + "message": "שפר את פריון המפתחים." }, "enhanceDeveloperProductivityDescription": { - "message": "Programmatically retrieve and deploy secrets at runtime so developers can focus on what matters most, like improving code quality." + "message": "אחזר ופרוס סודות באופן תכנותי בזמן ריצה כך שמפתחים יכולים להתמקד במה שחשוב יותר מכל, כמו שיפור איכות הקוד." }, "strengthenBusinessSecurity": { - "message": "Strengthen business security." + "message": "חזק את אבטחת העסק." }, "strengthenBusinessSecurityDescription": { - "message": "Maintain tight control over machine and human access to secrets with SSO integrations, event logs, and access rotation." + "message": "שמור על שליטה הדוקה בגישת מכונות ובני אדם אל סודות עם אינטגרציות SSO, יומני אירועים, ורוטציית גישה." }, "tryItNow": { - "message": "Try it now" + "message": "נסה את זה עכשיו" }, "sendRequest": { - "message": "Send request" + "message": "שלח בקשה" }, "addANote": { - "message": "Add a note" + "message": "הוסף הערה" }, "bitwardenSecretsManager": { - "message": "Bitwarden Secrets Manager" + "message": "מנהל הסודות של Bitwarden" }, "moreProductsFromBitwarden": { - "message": "More products from Bitwarden" + "message": "עוד מוצרים מאת Bitwarden" }, "requestAccessToSecretsManager": { - "message": "Request access to Secrets Manager" + "message": "בקש גישה אל מנהל הסודות" }, "youNeedApprovalFromYourAdminToTrySecretsManager": { - "message": "You need approval from your administrator to try Secrets Manager." + "message": "אתה צריך אישור מהמנהל שלך כדי לנסות את מנהל הסודות." }, "smAccessRequestEmailSent": { - "message": "Access request for secrets manager email sent to admins." + "message": "דוא\"ל בקשת גישה אל מנהל הסודות נשלח למנהלים." }, "requestAccessSMDefaultEmailContent": { - "message": "Hi,\n\nI am requesting a subscription to Bitwarden Secrets Manager for our team. Your support would mean a great deal!\n\nBitwarden Secrets Manager is an end-to-end encrypted secrets management solution for securely storing, sharing, and deploying machine credentials like API keys, database passwords, and authentication certificates.\n\nSecrets Manager will help us to:\n\n- Improve security\n- Streamline operations\n- Prevent costly secret leaks\n\nTo request a free trial for our team, please reach out to Bitwarden.\n\nThank you for your help!" + "message": "היי,\n\nאני מבקש מנוי למנהל הסודות של Bitwarden עבור הצוות שלנו. התמיכה שלך תהיה משמעותית מאוד!\n\nמנהל הסיסמאות של Bitwarden הוא פתרון ניהול סודות בהצפנה מקצה־לקצה עבור אחסון, שיתוף, ופריסה באופן מאובטח של אישורי מכונה כמו מפתחות API, סיסמאות מסדי נתונים, ותעודות אימות.\n\nמנהל הסודות יכול לעזור לנו:\n\n- לשפר אבטחה\n- לייעל פעולות\n- למנוע דליפת סודות יקרה\n\nכדי לבקש ניסיון חינמי עבור הצוות שלנו, נא ליצור קשר עם Bitwarden.\n\nתודה לך על העזרה שלך!" }, "giveMembersAccess": { - "message": "Give members access:" + "message": "תן לחברים גישה:" }, "viewAndSelectTheMembers": { - "message": "view and select the members you want to give access to Secrets Manager." + "message": "הצג ובחר את החברים שברצונך להעניק להם גישה אל מנהל הסודות." }, "openYourOrganizations": { - "message": "Open your organization's" + "message": "פתח את של הארגון שלך" }, "usingTheMenuSelect": { - "message": "Using the menu, select" + "message": "באמצעות התפריט, בחר את" }, "toGrantAccessToSelectedMembers": { - "message": "to grant access to selected members." + "message": "כדי להעניק גישה לחברים שנבחרו." }, "sendVaultCardTryItNow": { - "message": "try it now", + "message": "נסה את זה עכשיו", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more, see how it works, or **try it now**.'" }, "sendAccessTaglineOr": { - "message": "or", + "message": "או", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more about Bitwarden Send **or** sign up to try it today.'" }, "sendAccessTaglineSignUp": { - "message": "sign up", + "message": "הירשם", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more about Bitwarden Send or **sign up** to try it today.'" }, "sendAccessTaglineTryToday": { - "message": "to try it today.", + "message": "כדי לנסות את זה היום.", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more about Bitwarden Send or sign up to **try it today.**'" }, "sendAccessCreatorIdentifier": { - "message": "Bitwarden member $USER_IDENTIFIER$ shared the following with you", + "message": "חבר Bitwarden עם המזהה $USER_IDENTIFIER$ שיתף איתך את הדברים הבאים", "placeholders": { "user_identifier": { "content": "$1", @@ -5414,60 +5666,69 @@ } }, "viewSend": { - "message": "View Send", + "message": "הצג סֵנְד", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "viewSendHiddenEmailWarning": { - "message": "The Bitwarden user who created this Send has chosen to hide their email address. You should ensure you trust the source of this link before using or downloading its content.", + "message": "משתמש ה־Bitwarden שיצר את סֵנְד זה בחר להסתיר את כתובת הדוא\"ל שלו. עליך לוודא שאתה בוטח במקור של קישור זה לפני שימוש או הורדה של התוכן שלו.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDateIsInvalid": { - "message": "The expiration date provided is not valid." + "message": "תאריך התפוגה שסופק אינו חוקי." }, "deletionDateIsInvalid": { - "message": "The deletion date provided is not valid." + "message": "תאריך המחיקה שסופק אינו חוקי." }, "expirationDateAndTimeRequired": { - "message": "An expiration date and time are required." + "message": "נדרשים תאריך וזמן תפוגה." }, "deletionDateAndTimeRequired": { - "message": "A deletion date and time are required." + "message": "נדרשים תאריך וזמן מחיקה." }, "dateParsingError": { - "message": "There was an error saving your deletion and expiration dates." + "message": "הייתה שגיאה בשמירת תאריכי המחיקה והתפוגה שלך." + }, + "hideYourEmail": { + "message": "הסתר את כתובת הדוא\"ל שלך מצופים." }, "webAuthnFallbackMsg": { - "message": "To verify your 2FA please click the button below." + "message": "כדי לאמת את האימות הדו־גורמי (2FA) שלך לחץ על הלחצן למטה." }, "webAuthnAuthenticate": { - "message": "Authenticate WebAuthn" + "message": "אמת WebAuthn" + }, + "readSecurityKey": { + "message": "קרא מפתח אבטחה" + }, + "awaitingSecurityKeyInteraction": { + "message": "ממתין לאינטראקציה עם מפתח אבטחה..." }, "webAuthnNotSupported": { - "message": "WebAuthn is not supported in this browser." + "message": "WebAuthn אינו נתמך בדפדפן זה." }, "webAuthnSuccess": { - "message": "WebAuthn verified successfully! You may close this tab." + "message": "WebAuthn אומת בהצלחה! אתה רשאי לסגור כרטיסיה זו." }, "hintEqualsPassword": { - "message": "Your password hint cannot be the same as your password." + "message": "רמז הסיסמה שלך לא יכול להיות אותו הדבר כמו הסיסמה שלך." }, "enrollAccountRecovery": { - "message": "Enroll in account recovery" + "message": "להירשם לשחזור חשבון" }, "enrolledAccountRecovery": { - "message": "Enrolled in account recovery" + "message": "נרשם לשחזור חשבון" }, "withdrawAccountRecovery": { - "message": "Withdraw from account recovery" + "message": "לסגת משחזור חשבון" }, "enrollPasswordResetSuccess": { - "message": "Enrollment success!" + "message": "הצלחת הרשמה!" }, "withdrawPasswordResetSuccess": { - "message": "Withdrawal success!" + "message": "הצלחת נסיגה!" }, "eventEnrollAccountRecovery": { - "message": "User $ID$ enrolled in account recovery.", + "message": "המשתמש $ID$ נרשם לשחזור חשבון.", "placeholders": { "id": { "content": "$1", @@ -5476,7 +5737,7 @@ } }, "eventWithdrawAccountRecovery": { - "message": "User $ID$ withdrew from account recovery.", + "message": "המשתמש $ID$ נסוג משחזור חשבון.", "placeholders": { "id": { "content": "$1", @@ -5485,7 +5746,7 @@ } }, "eventAdminPasswordReset": { - "message": "Master password reset for user $ID$.", + "message": "סיסמה ראשית אופסה עבור המשתמש $ID$.", "placeholders": { "id": { "content": "$1", @@ -5494,7 +5755,7 @@ } }, "eventResetSsoLink": { - "message": "Reset SSO link for user $ID$", + "message": "אפס קישור SSO עבור המשתמש $ID$", "placeholders": { "id": { "content": "$1", @@ -5503,7 +5764,7 @@ } }, "firstSsoLogin": { - "message": "$ID$ logged in using Sso for the first time", + "message": "$ID$ נכנס באמצעות SSO בפעם הראשונה", "placeholders": { "id": { "content": "$1", @@ -5512,10 +5773,10 @@ } }, "resetPassword": { - "message": "Reset password" + "message": "אפס סיסמה" }, "resetPasswordLoggedOutWarning": { - "message": "Proceeding will log $NAME$ out of their current session, requiring them to log back in. Active sessions on other devices may continue to remain active for up to one hour.", + "message": "המשך התהליך יוציא את $NAME$ מההפעלה הנוכחית שלו והוא יידרש להיכנס חזרה. הפעלות פעילות במכשירים אחרים עלולות להישאר פעילות למשך עד שעה אחת.", "placeholders": { "name": { "content": "$1", @@ -5524,215 +5785,229 @@ } }, "thisUser": { - "message": "this user" + "message": "משתמש זה" }, "resetPasswordMasterPasswordPolicyInEffect": { - "message": "One or more organization policies require the master password to meet the following requirements:" + "message": "מדיניות ארגון אחת או יותר דורשת שהסיסמה הראשית תעמוד בדרישות הבאות:" }, "resetPasswordSuccess": { - "message": "Password reset success!" + "message": "הצלחת איפוס סיסמה!" }, "resetPasswordEnrollmentWarning": { - "message": "Enrollment will allow organization administrators to change your master password" + "message": "הרשמה תאפשר למנהלי ארגון לשנות את הסיסמה הראשית שלך" }, "accountRecoveryPolicy": { - "message": "Account recovery administration" + "message": "ניהול שחזור חשבון" }, "accountRecoveryPolicyDesc": { - "message": "Based on the encryption method, recover accounts when master passwords or trusted devices are forgotten or lost." + "message": "בהתבסס על שיטת ההצפנה, שחזר חשבונות כאשר סיסמאות ראשיות או מכשירים מהימנים נשכחו או אבדו." }, "accountRecoveryPolicyWarning": { - "message": "Existing accounts with master passwords will require members to self-enroll before administrators can recover their accounts. Automatic enrollment will turn on account recovery for new members." + "message": "חברים בעלי חשבונות קיימים עם סיסמאות ראשיות יידרשו להירשם בעצמם לפני שמנהלים יוכלו לשחזר את החשבונות שלהם. הרשמה אוטומטית תפעיל שחזור חשבון עבור חברים חדשים." }, "accountRecoverySingleOrgRequirementDesc": { - "message": "The single organization Enterprise policy must be turned on before activating this policy." + "message": "יש להפעיל את המדיניות הארגונית של הארגון היחיד לפני הפעלת מדיניות זו." }, "resetPasswordPolicyAutoEnroll": { - "message": "Automatic enrollment" + "message": "הרשמה אוטומטית" }, "resetPasswordPolicyAutoEnrollCheckbox": { - "message": "Require new members to be enrolled automatically" + "message": "דרוש מחברים חדשים להיות רשומים באופן אוטומטי" }, "resetPasswordAutoEnrollInviteWarning": { - "message": "This organization has an Enterprise policy that will automatically enroll you in password reset. Enrollment will allow organization administrators to change your master password." + "message": "לארגון זה יש מדיניות ארגונית שתרשום אותך באופן אוטומטי לאיפוס סיסמה. הרישום יאפשר למנהלי הארגון לשנות את הסיסמה הראשית שלך." }, "resetPasswordOrgKeysError": { - "message": "Organization keys response is null" + "message": "תגובת מפתחות ארגון היא ריקה" }, "resetPasswordDetailsError": { - "message": "Reset password details response is null" + "message": "תגובת פרטי איפוס סיסמה היא ריקה" }, "trashCleanupWarning": { - "message": "Items that have been in trash more than 30 days will be automatically deleted." + "message": "פריטים שהיו באשפה יותר מ־30 יום יימחקו באופן אוטומטי." }, "trashCleanupWarningSelfHosted": { - "message": "Items that have been in trash for a while will be automatically deleted." + "message": "פריטים שהיו באשפה לזמן מה יימחקו באופן אוטומטי." }, "passwordPrompt": { - "message": "Master password re-prompt" + "message": "בקשה חוזרת של סיסמה ראשית" }, "passwordConfirmation": { - "message": "Master password confirmation" + "message": "אישור סיסמה ראשית" }, "passwordConfirmationDesc": { - "message": "This action is protected. To continue, please re-enter your master password to verify your identity." + "message": "פעולה זו מוגנת. כדי להמשיך, נא להזין שוב את הסיסמה הראשית שלך כדי לאמת את זהותך." }, "reinviteSelected": { - "message": "Resend invitations" + "message": "שלח מחדש הזמנות" }, "resendNotification": { - "message": "Resend notification" + "message": "שלח מחדש התראה" }, "noSelectedUsersApplicable": { - "message": "This action is not applicable to any of the selected users." + "message": "פעולה זו אינה ישימה לאף אחד מהמשתמשים שנבחרו." }, "removeUsersWarning": { - "message": "Are you sure you want to remove the following users? The process may take a few seconds to complete and cannot be interrupted or canceled." + "message": "האם אתה בטוח שברצונך להסיר את המשתמשים הבאים? התהליך עלול לקחת מספר שניות להשלמה ולא ניתן לקטוע או לבטל אותו." }, "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." + "message": "כאשר החברים מוסרים, אין להם יותר גישה אל נתוני הארגון ופעולה זו היא בלתי הפיכה. כדי להוסיף את החברים בחזרה לארגון, יש להזמין ולקלוט אותם שוב. התהליך עלול לקחת מספר שניות להשלמה ולא ניתן לקטוע או לבטל אותו." }, "revokeUsersWarning": { - "message": "When member(s) are revoked, they no longer have access to organization data. To quickly restore member access, go to the Revoked tab. The process may take a few seconds to complete and cannot be interrupted or canceled." + "message": "כאשר החברים מבוטלים, אין להם יותר גישה אל נתוני הארגון. כדי לשחזר במהירות גישת חבר, עבור לכרטיסיה 'מבוטל'. התהליך עלול לקחת מספר שניות להשלמה ולא ניתן לקטוע או לבטל אותו." }, "theme": { - "message": "Theme" + "message": "ערכת נושא" }, "themeDesc": { - "message": "Choose a theme for your web vault." + "message": "בחר ערכת נושא עבור כספת הרשת שלך." }, "themeSystem": { - "message": "Use system theme" + "message": "השתמש בערכת נושא של המערכת" }, "themeDark": { - "message": "Dark" + "message": "כהה" }, "themeLight": { - "message": "Light" + "message": "בהיר" }, "confirmSelected": { - "message": "Confirm selected" + "message": "אשר את שנבחר" }, "bulkConfirmStatus": { - "message": "Bulk action status" + "message": "מצב פעולה בכמות גדולה" }, "bulkConfirmMessage": { - "message": "Confirmed successfully" + "message": "אושרו בהצלחה" }, "bulkReinviteMessage": { - "message": "Reinvited successfully" + "message": "הוזמנו בהצלחה" }, "bulkRemovedMessage": { - "message": "Removed successfully" + "message": "הוסרו בהצלחה" }, "bulkRevokedMessage": { - "message": "Revoked organization access successfully" + "message": "הגישה לארגון בוטלה בהצלחה" }, "bulkRestoredMessage": { - "message": "Restored organization access successfully" + "message": "הגישה לארגון שוחזרה בהצלחה" }, "bulkFilteredMessage": { - "message": "Excluded, not applicable for this action" + "message": "לא נכללו, לא ישימים עבור פעולה זו" }, "nonCompliantMembersTitle": { - "message": "Non-compliant members" + "message": "חברים שאינם עומדים בדרישות" }, "nonCompliantMembersError": { - "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + "message": "חברים שאינם עומדים בדרישות מדיניות הארגון היחיד או הכניסה הדו־שלבית אינם ניתנים לשחזור עד שהם יעמדו בדרישות המדיניות" }, "fingerprint": { - "message": "Fingerprint" + "message": "טביעת אצבע" }, "removeUsers": { - "message": "Remove users" + "message": "הסר משתמשים" }, "revokeUsers": { - "message": "Revoke users" + "message": "בטל משתמשים" }, "restoreUsers": { - "message": "Restore users" + "message": "שחזר משתמשים" }, "error": { - "message": "Error" + "message": "שגיאה" + }, + "decryptionError": { + "message": "שגיאת פענוח" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden לא יכל לפענח את פריט(י) הכספת המפורט(ים) להלן." + }, + "contactCSToAvoidDataLossPart1": { + "message": "צור קשר עם הצלחת לקוחות", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "כדי למנוע אובדן נתונים נוסף.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "accountRecoveryManageUsers": { - "message": "Manage users must also be granted with the manage account recovery permission" + "message": "יש להעניק גם את הרשאת ניהול משתמשים עם ההרשאה לניהול שחזור חשבון" }, "setupProvider": { - "message": "Provider setup" + "message": "הגדרת ספק" }, "setupProviderLoginDesc": { - "message": "You've been invited to setup a new Provider. To continue, you need to log in or create a new Bitwarden account." + "message": "הוזמנת להגדיר ספק חדש. כדי להמשיך, תצטרך להיכנס או ליצור חשבון Bitwarden חדש." }, "setupProviderDesc": { - "message": "Please enter the details below to complete the Provider setup. Contact Customer Support if you have any questions." + "message": "נא להזין את הפרטים למטה כדי להשלים את הגדרת הספק. פנה אל תמיכת לקוחות אם יש לך שאלות כלשהן." }, "providerName": { - "message": "Provider name" + "message": "שם הספק" }, "providerSetup": { - "message": "Provider successfully set up" + "message": "הספק הוגדר בהצלחה" }, "clients": { - "message": "Clients" + "message": "לקוחות" }, "client": { - "message": "Client", + "message": "לקוח", "description": "This is used as a table header to describe which client application created an event log." }, "providerAdmin": { - "message": "Provider admin" + "message": "מנהל הספק" }, "providerAdminDesc": { - "message": "The highest access user that can manage all aspects of your Provider as well as access and manage client organizations." + "message": "המשתמש בעל הגישה הגבוהה ביותר שיכול לנהל את כל ההיבטים של הספק שלך כמו גם לגשת ולנהל ארגוני לקוחות." }, "serviceUser": { - "message": "Service user" + "message": "משתמש שירות" }, "serviceUserDesc": { - "message": "Service users can access and manage all client organizations." + "message": "משתמשי שירות יכולים לגשת אל ולנהל את כל ארגוני הלקוחות." }, "providerInviteUserDesc": { - "message": "Invite a new user to your Provider by entering their Bitwarden account email address below. If they do not have a Bitwarden account already, they will be prompted to create a new account." + "message": "הזמן משתמש חדש אל הספק שלך על ידי הזנת כתובת הדוא\"ל של חשבון ה־Bitwarden שלו למטה. אם אין לו כבר חשבון Bitwarden, הוא יתבקש ליצור חשבון חדש." }, "joinProvider": { - "message": "Join Provider" + "message": "הצטרף לספק" }, "joinProviderDesc": { - "message": "You've been invited to join the Provider listed above. To accept the invitation, you need to log in or create a new Bitwarden account." + "message": "הוזמנת להצטרף אל הספק הרשום לעיל. כדי לאשר את ההזמנה, אתה מוכרח להיכנס או ליצור חשבון Bitwarden חדש." }, "providerInviteAcceptFailed": { - "message": "Unable to accept invitation. Ask a Provider admin to send a new invitation." + "message": "לא ניתן לקבל הזמנה. בקש ממנהל ספק לשלוח הזמנה חדשה." }, "providerInviteAcceptedDesc": { - "message": "You can access this Provider once an administrator confirms your membership. We'll send you an email when that happens." + "message": "תוכל לגשת לספק הזה ברגע שמנהל יאשר את החברות שלך. נשלח לך דוא\"ל כשזה יקרה." }, "providerUsersNeedConfirmed": { - "message": "You have users that have accepted their invitation, but still need to be confirmed. Users will not have access to the Provider until they are confirmed." + "message": "יש לך משתמשים שקיבלו את ההזמנה שלהם, אבל עדיין צריך לאשר אותם. למשתמשים לא תהיה גישה אל הספק עד שהם יאושרו." }, "provider": { - "message": "Provider" + "message": "ספק" }, "newClientOrganization": { - "message": "New client organization" + "message": "ארגון לקוחות חדש" }, "newClientOrganizationDesc": { - "message": "Create a new client organization that will be associated with you as the Provider. You will be able to access and manage this organization." + "message": "צור ארגון לקוחות חדש שישויך אליך בתור הספק. תוכל לגשת לנהל את ארגון זה." }, "newClient": { - "message": "New client" + "message": "לקוח חדש" }, "addExistingOrganization": { - "message": "Add existing organization" + "message": "הוסף ארגון קיים" }, "addNewOrganization": { - "message": "Add new organization" + "message": "הוסף ארגון חדש" }, "myProvider": { - "message": "My Provider" + "message": "הספק שלי" }, "addOrganizationConfirmation": { - "message": "Are you sure you want to add $ORGANIZATION$ as a client to $PROVIDER$?", + "message": "האם אתה בטוח שברצונך להוסיף את $ORGANIZATION$ כלקוח אל $PROVIDER$?", "placeholders": { "organization": { "content": "$1", @@ -5745,10 +6020,10 @@ } }, "organizationJoinedProvider": { - "message": "Organization was successfully added to the Provider" + "message": "ארגון נוסף בהצלחה אל הספק" }, "accessingUsingProvider": { - "message": "Accessing organization using Provider $PROVIDER$", + "message": "ניגש לארגון באמצעות הספק $PROVIDER$", "placeholders": { "provider": { "content": "$1", @@ -5757,13 +6032,13 @@ } }, "providerIsDisabled": { - "message": "Provider suspended" + "message": "ספק מושעה" }, "providerUpdated": { - "message": "Provider saved" + "message": "ספק נשמר" }, "yourProviderIs": { - "message": "Your Provider is $PROVIDER$. They have administrative and billing privileges for your organization.", + "message": "הספק שלך הוא $PROVIDER$. יש לו הרשאות ניהול וחיוב עבור הארגון שלך.", "placeholders": { "provider": { "content": "$1", @@ -5772,7 +6047,7 @@ } }, "detachedOrganization": { - "message": "The organization $ORGANIZATION$ has been detached from your Provider.", + "message": "הארגון $ORGANIZATION$ נותק מהספק שלך.", "placeholders": { "organization": { "content": "$1", @@ -5781,61 +6056,61 @@ } }, "detachOrganizationConfirmation": { - "message": "Are you sure you want to detach this organization? The organization will continue to exist but will no longer be managed by the Provider." + "message": "האם אתה בטוח שברצונך לנתק ארגון זה? הארגון ימשיך להיות קיים אבל לא ינוהל יותר על ידי הספק." }, "add": { - "message": "Add" + "message": "הוסף" }, "updatedMasterPassword": { - "message": "Master password saved" + "message": "הסיסמה הראשית נשמרה" }, "updateMasterPassword": { - "message": "Update master password" + "message": "עדכן סיסמה ראשית" }, "updateMasterPasswordWarning": { - "message": "Your master password was recently changed by an administrator in your organization. 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": "הסיסמה הראשית שלך שונתה לאחרונה על ידי מנהל בארגון שלך. כדי לגשת אל הכספת, אתה מוכרח לעדכן את הסיסמה הראשית שלך עכשיו. המשך התהליך יוציא אותך מההפעלה הנוכחית שלך ותידרש להיכנס חזרה. הפעלות פעילות במכשירים אחרים עלולות להישאר פעילות למשך עד שעה אחת." }, "masterPasswordInvalidWarning": { - "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." + "message": "הסיסמה הראשית שלך אינה עומדת בדרישות המדיניות של ארגון זה. כדי להצטרף לארגון, אתה מוכרח לעדכן את הסיסמה הראשית שלך עכשיו. המשך התהליך יוציא אותך מההפעלה הנוכחית שלך ותידרש להיכנס חזרה. הפעלות פעילות במכשירים אחרים עלולות להישאר פעילות למשך עד שעה אחת." }, "updateWeakMasterPasswordWarning": { - "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." + "message": "הסיסמה הראשית שלך אינה עומדת באחת או יותר מפוליסות הארגון שלך. כדי לגשת לכספת, אתה מוכרח לעדכן את הסיסמה הראשית שלך עכשיו. בהמשך תנותק מההפעלה הנוכחית שלך, מה שידרוש ממך להיכנס חזרה. הפעלות פעילות במכשירים אחרים עלולות להישאר פעילות למשך עד שעה אחת." }, "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "message": "הכנס באופן אוטומטי משתמשים עבור יישומים מותרים" }, "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "message": "טפסי כניסה ימולאו ויוגשו באופן אוטומטי עבור יישומים שנפתחו מספק הזהות המוגדר שלך." }, "automaticAppLoginIdpHostLabel": { - "message": "Identity provider host" + "message": "מארח ספק זהות" }, "automaticAppLoginIdpHostDesc": { - "message": "Enter your identity provider host URL. Enter multiple URLs by separating with a comma." + "message": "הזן את כתובת ה־URL של מארח ספק הזהות שלך. הזן מספר כתובות URL על ידי הפרדה עם פסיק." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has updated your decryption options. Please set a master password to access your vault." + "message": "הארגון שלך עדכן את אפשרויות הפענוח שלך. נא להגדיר סיסמה ראשית כדי לגשת לכספת שלך." }, "maximumVaultTimeout": { - "message": "Vault timeout" + "message": "פסק זמן לכספת" }, "maximumVaultTimeoutDesc": { - "message": "Set a maximum vault timeout for members." + "message": "הגדר פסק זמן מרבי לכספת עבור חברים." }, "maximumVaultTimeoutLabel": { - "message": "Maximum vault timeout" + "message": "פסק זמן מרבי לכספת" }, "invalidMaximumVaultTimeout": { - "message": "Invalid maximum vault timeout." + "message": "פסק זמן מרבי לכספת לא חוקי." }, "hours": { - "message": "Hours" + "message": "שעות" }, "minutes": { - "message": "Minutes" + "message": "דקות" }, "vaultTimeoutPolicyInEffect": { - "message": "Your organization policies have set your maximum allowed vault timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "message": "פוליסות הארגון שלך הגדירו את פסק הזמן לכספת המרבי שלך ל־$HOURS$ שעות ו־$MINUTES$ דקות.", "placeholders": { "hours": { "content": "$1", @@ -5848,7 +6123,7 @@ } }, "vaultTimeoutPolicyInEffect1": { - "message": "$HOURS$ hour(s) and $MINUTES$ minute(s) maximum.", + "message": "$HOURS$ שעות ו־$MINUTES$ דקות לכל היותר.", "placeholders": { "hours": { "content": "$1", @@ -5861,7 +6136,7 @@ } }, "vaultTimeoutPolicyWithActionInEffect": { - "message": "Your organization policies are affecting your vault timeout. Maximum allowed vault timeout is $HOURS$ hour(s) and $MINUTES$ minute(s). Your vault timeout action is set to $ACTION$.", + "message": "פוליסות הארגון שלך משפיעות על פסק הזמן לכספת שלך. פסק זמן מרבי המותר הוא $HOURS$ שעות ו־$MINUTES$ דקות. פעולת פסק הזמן לכספת שלך מוגדרת ל$ACTION$.", "placeholders": { "hours": { "content": "$1", @@ -5878,7 +6153,7 @@ } }, "vaultTimeoutActionPolicyInEffect": { - "message": "Your organization policies have set your vault timeout action to $ACTION$.", + "message": "פוליסות הארגון שלך הגדירו את פעולת פסק הזמן לכספת שלך ל$ACTION$.", "placeholders": { "action": { "content": "$1", @@ -5887,208 +6162,208 @@ } }, "vaultTimeoutToLarge": { - "message": "Your vault timeout exceeds the restriction set by your organization." + "message": "פסק הזמן לכספת שלך חורג מהמגבלה שנקבעה על ידי הארגון שלך." }, "vaultCustomTimeoutMinimum": { - "message": "Minimum custom timeout is 1 minute." + "message": "פסק זמן מותאם אישית מינימלי הוא דקה 1." }, "vaultTimeoutRangeError": { - "message": "Vault timeout is not within allowed range." + "message": "פסק זמן לכספת אינו בטווח המותר." }, "disablePersonalVaultExport": { - "message": "Remove individual vault export" + "message": "הסר ייצוא כספת אישית" }, "disablePersonalVaultExportDescription": { - "message": "Do not allow members to export data from their individual vault." + "message": "אל תאפשר לחברים לייצא נתונים מהכספת האישית שלהם." }, "vaultExportDisabled": { - "message": "Vault export removed" + "message": "ייצוא כספת הוסר" }, "personalVaultExportPolicyInEffect": { - "message": "One or more organization policies prevents you from exporting your individual vault." + "message": "מדיניות ארגון אחת או יותר מונעת ממך מלייצא את הכספת האישית שלך." }, "activateAutofill": { - "message": "Activate auto-fill" + "message": "הפעל מילוי אוטומטי" }, "activateAutofillPolicyDesc": { - "message": "Activate the auto-fill on page load setting on the browser extension for all existing and new members." + "message": "הפעל את הגדרת המילוי האוטומטי בעת טעינת עמוד בהרחבת הדפדפן עבור כל החברים הקיימים והחדשים." }, "experimentalFeature": { - "message": "Compromised or untrusted websites can exploit auto-fill on page load." + "message": "אתרים פרוצים או לא מהימנים יכולים לנצל מילוי אוטומטי בעת טעינת עמוד." }, "learnMoreAboutAutofill": { - "message": "Learn more about auto-fill" + "message": "למד עוד על מילוי אוטומטי" }, "selectType": { - "message": "Select SSO type" + "message": "בחר סוג SSO" }, "type": { - "message": "Type" + "message": "סוג" }, "openIdConnectConfig": { - "message": "OpenID connect configuration" + "message": "תצורת OpenID Connect" }, "samlSpConfig": { - "message": "SAML service provider configuration" + "message": "תצורת ספק שירות SAML" }, "samlIdpConfig": { - "message": "SAML identity provider configuration" + "message": "תצורת ספק זהות SAML" }, "callbackPath": { - "message": "Callback path" + "message": "נתיב התקשרות חזרה" }, "signedOutCallbackPath": { - "message": "Signed out callback path" + "message": "נתיב התקשרות חזרה מנותק" }, "authority": { - "message": "Authority" + "message": "רשות" }, "clientId": { - "message": "Client ID" + "message": "מזהה לקוח" }, "clientSecret": { - "message": "Client secret" + "message": "סוג לקוח" }, "metadataAddress": { - "message": "Metadata address" + "message": "כתובת מטא־נתונים" }, "oidcRedirectBehavior": { - "message": "OIDC redirect behavior" + "message": "התנהגות OIDC בעת ניתוב מחדש" }, "getClaimsFromUserInfoEndpoint": { - "message": "Get claims from user info endpoint" + "message": "קבל דרישות מנקודת הקצה של פרטי המשתמש" }, "additionalScopes": { - "message": "Custom scopes" + "message": "תחום מותאם אישית" }, "additionalUserIdClaimTypes": { - "message": "Custom user ID claim types" + "message": "סוגי דרישות מזהה משתמש מותאם אישית" }, "additionalEmailClaimTypes": { - "message": "Email claim types" + "message": "סוגי דרישות דוא\"ל" }, "additionalNameClaimTypes": { - "message": "Custom name claim types" + "message": "סוגי דרישות שם מותאם אישית" }, "acrValues": { - "message": "Requested authentication context class reference values" + "message": "ערכי ייחוס של מחלקת הקשר אימות מבוקשים" }, "expectedReturnAcrValue": { - "message": "Expected \"acr\" claim value in response" + "message": "ציפה לערך דרישת \"acr\" בתגובה" }, "spEntityId": { - "message": "SP entity ID" + "message": "מזהה ישות ספק שרות (SP)" }, "spMetadataUrl": { - "message": "SAML 2.0 metadata URL" + "message": "כתובת URL של מטא־נתוני SAML 2.0" }, "spAcsUrl": { - "message": "Assertion consumer service (ACS) URL" + "message": "כתובת URL של קביעת שירות צרכן (ACS)" }, "spNameIdFormat": { - "message": "Name ID format" + "message": "תבנית מזהה שם" }, "spOutboundSigningAlgorithm": { - "message": "Outbound signing algorithm" + "message": "אלגוריתם חתימה יוצאת" }, "spSigningBehavior": { - "message": "Signing behavior" + "message": "התנהגות חתימה" }, "spMinIncomingSigningAlgorithm": { - "message": "Minimum incoming signing algorithm" + "message": "אלגוריתם חתימה נכנסת מינימלי" }, "spWantAssertionsSigned": { - "message": "Expect signed assertions" + "message": "צפה לקביעות חתומות" }, "spValidateCertificates": { - "message": "Validate certificates" + "message": "אמת אישורים" }, "spUniqueEntityId": { - "message": "Set a unique SP entity ID" + "message": "הגדר מזהה ישות ספק שירות (SP) ייחודי" }, "spUniqueEntityIdDesc": { - "message": "Generate an identifier that is unique to your organization" + "message": "צור מזהה שהוא ייחודי לארגון שלך" }, "idpEntityId": { - "message": "Entity ID" + "message": "מזהה ישות" }, "idpBindingType": { - "message": "Binding type" + "message": "סוג קשירה" }, "idpSingleSignOnServiceUrl": { - "message": "Single sign-on service URL" + "message": "כתובת URL של שירות כניסה יחידה" }, "idpSingleLogoutServiceUrl": { - "message": "Single log-out service URL" + "message": "כתובת URL של שירות יציאה יחידה" }, "idpX509PublicCert": { - "message": "X509 public certificate" + "message": "תעודת X509 ציבורית" }, "idpOutboundSigningAlgorithm": { - "message": "Outbound signing algorithm" + "message": "אלגוריתם חתימה יוצאת" }, "idpAllowUnsolicitedAuthnResponse": { - "message": "Allow unsolicited authentication response" + "message": "אפשר תגובת אימות לא רצויה" }, "idpAllowOutboundLogoutRequests": { - "message": "Allow outbound logout requests" + "message": "אפשר בקשות יציאה יוצאות" }, "idpSignAuthenticationRequests": { - "message": "Sign authentication requests" + "message": "חתום בקשות אימות" }, "ssoSettingsSaved": { - "message": "Single sign-on configuration saved" + "message": "תצורת כניסה יחידה נשמרה" }, "sponsoredFamilies": { - "message": "Free Bitwarden Families" + "message": "Bitwarden למשפחות בחינם" }, "sponsoredFamiliesEligible": { - "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." + "message": "אתה והמשפחה שלך זכאים ל־Bitwarden למשפחות בחינם. ממש עם הדוא\"ל האישי שלך כדי לשמור על אבטחת הנתונים שלך אפילו כשאתה לא בעבודה." }, "sponsoredFamiliesEligibleCard": { - "message": "Redeem your Free Bitwarden for Families plan today to keep your data secure even when you are not at work." + "message": "ממש את תוכנית Bitwarden למשפחות בחינם שלך היום כדי לשמור על אבטחת הנתונים שלך אפילו כשאתה לא בעבודה." }, "sponsoredFamiliesInclude": { - "message": "The Bitwarden for Families plan include" + "message": "התוכנית Bitwarden למשפחות כוללת" }, "sponsoredFamiliesPremiumAccess": { - "message": "Premium access for up to 6 users" + "message": "גישת פרימיום עד 6 משתמשים" }, "sponsoredFamiliesSharedCollections": { - "message": "Shared collections for Family secrets" + "message": "אוספים משותפים עבור סודות משפחה" }, "badToken": { - "message": "The link is no longer valid. Please have the sponsor resend the offer." + "message": "הקישור אינו חוקי עוד. אנא בקש מנותן החסות לשלוח שוב את ההצעה." }, "reclaimedFreePlan": { - "message": "Reclaimed free plan" + "message": "החזרת את התוכנית החינמית" }, "redeem": { - "message": "Redeem" + "message": "מימוש" }, "sponsoredFamiliesSelectOffer": { - "message": "Select the organization you would like sponsored" + "message": "בחר את הארגון שברצונך להעניק לו חסות" }, "familiesSponsoringOrgSelect": { - "message": "Which Free Families offer would you like to redeem?" + "message": "איזו הצעה למשפחות בחינם היית רוצה לממש?" }, "sponsoredFamiliesEmail": { - "message": "Enter your personal email to redeem Bitwarden Families" + "message": "הזן את הדוא\"ל האישי שלך כדי לממש את Bitwarden למשפחות" }, "sponsoredFamiliesLeaveCopy": { - "message": "If you remove an offer or are removed from the sponsoring organization, your Families sponsorship will expire at the next renewal date." + "message": "אם תסיר הצעה או שהוסרת מהארגון המממן, החסות למשפחות שלך תפוג בתאריך החידוש הבא." }, "acceptBitwardenFamiliesHelp": { - "message": "Accept offer for an existing organization or create a new Families organization." + "message": "קבל הצעה עבור ארגון קיים או צור ארגון משפחות חדש." }, "setupSponsoredFamiliesLoginDesc": { - "message": "You've been offered a free Bitwarden Families plan organization. To continue, you need to log in to the account that received the offer." + "message": "הציעו לך ארגון תוכנית Bitwarden למשפחות בחינם. כדי להמשיך, תצטרך להיכנס לחשבון שקיבל את ההצעה." }, "sponsoredFamiliesAcceptFailed": { - "message": "Unable to accept offer. Please resend the offer email from your Enterprise account and try again." + "message": "לא ניתן לקבל הצעה. נא לשלוח מחדש את דוא\"ל ההצעה מהחשבון הארגוני שלך ולנסות שוב." }, "sponsoredFamiliesAcceptFailedShort": { - "message": "Unable to accept offer. $DESCRIPTION$", + "message": "לא ניתן לקבל הצעה: $DESCRIPTION$", "placeholders": { "description": { "content": "$1", @@ -6097,19 +6372,19 @@ } }, "sponsoredFamiliesOffer": { - "message": "Accept Free Bitwarden Families" + "message": "קבל Bitwarden למשפחות בחינם" }, "sponsoredFamiliesOfferRedeemed": { - "message": "Free Bitwarden Families offer successfully redeemed" + "message": "הצעת Bitwarden למשפחות בחינם מומשה בהצלחה" }, "redeemed": { - "message": "Redeemed" + "message": "מומש" }, "redeemedAccount": { - "message": "Account redeemed" + "message": "החשבון מומש" }, "revokeAccount": { - "message": "Revoke account $NAME$", + "message": "בטל חשבון $NAME$", "placeholders": { "name": { "content": "$1", @@ -6118,7 +6393,7 @@ } }, "resendEmailLabel": { - "message": "Resend sponsorship email to $NAME$ sponsorship", + "message": "שלח מחדש דוא\"ל חסות לנותן החסות $NAME$", "placeholders": { "name": { "content": "$1", @@ -6127,61 +6402,61 @@ } }, "freeFamiliesPlan": { - "message": "Free Families plan" + "message": "תוכנית למשפחות בחינם" }, "redeemNow": { - "message": "Redeem now" + "message": "ממש עכשיו" }, "recipient": { - "message": "Recipient" + "message": "נמען" }, "removeSponsorship": { - "message": "Remove sponsorship" + "message": "הסר חסות" }, "removeSponsorshipConfirmation": { - "message": "After removing a sponsorship, you will be responsible for this subscription and related invoices. Are you sure you want to continue?" + "message": "לאחר הסרת חסות, אתה תהיה אחראי למנוי זה ולחשבוניות קשורות. האם אתה בטוח שברצונך להמשיך?" }, "sponsorshipCreated": { - "message": "Sponsorship created" + "message": "החסות נוצרה" }, "emailSent": { - "message": "Email sent" + "message": "הדוא\"ל נשלח" }, "removeSponsorshipSuccess": { - "message": "Sponsorship removed" + "message": "החסות הוסרה" }, "ssoKeyConnectorError": { - "message": "Key Connector error: make sure Key Connector is available and working correctly." + "message": "שגיאת Key Connector: וודא שה־Key Connector זמין ופועל כראוי." }, "keyConnectorUrl": { - "message": "Key Connector URL" + "message": "כתובת URL של Key Connector" }, "sendVerificationCode": { - "message": "Send a verification code to your email" + "message": "שלח קוד אימות לדוא\"ל שלח" }, "sendCode": { - "message": "Send code" + "message": "שלח קוד" }, "codeSent": { - "message": "Code sent" + "message": "קוד נשלח" }, "verificationCode": { - "message": "Verification code" + "message": "קוד אימות" }, "confirmIdentity": { - "message": "Confirm your identity to continue." + "message": "אשר את זהותך כדי להמשיך." }, "verificationCodeRequired": { - "message": "Verification code is required." + "message": "נדרש קוד אימות." }, "webauthnCancelOrTimeout": { - "message": "The authentication was cancelled or took too long. Please try again." + "message": "האימות בוטל או לקח זמן רב מדי. נא לנסות שוב." }, "invalidVerificationCode": { - "message": "Invalid verification code" + "message": "קוד אימות שגוי" }, "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", + "message": "$ORGANIZATION$ משתמש/ת ב־SSO עם שרת מפתחות באירוח עצמי. סיסמה ראשית כבר לא נדרשת כדי להיכנס עבור חברים של ארגון זה.", "placeholders": { "organization": { "content": "$1", @@ -6190,205 +6465,205 @@ } }, "leaveOrganization": { - "message": "Leave organization" + "message": "עזוב ארגון" }, "removeMasterPassword": { - "message": "Remove master password" + "message": "הסר סיסמה ראשית" }, "removedMasterPassword": { - "message": "Master password removed" + "message": "הסיסמה הראשית הוסרה" }, "allowSso": { - "message": "Allow SSO authentication" + "message": "אפשר אימות SSO" }, "allowSsoDesc": { - "message": "Once set up, your configuration will be saved and members will be able to authenticate using their Identity Provider credentials." + "message": "לאחר ההגדרה, התצורה שלך תשמר וחברים יוכלו לאמת באמצעות אישורי ספק הזהות שלהם." }, "ssoPolicyHelpStart": { - "message": "Use the", + "message": "השתמש ב", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Use the require single-sign-on authentication policy to require all members to log in with SSO.'" }, "ssoPolicyHelpAnchor": { - "message": "require single sign-on authentication policy", + "message": "מדיניות דרוש אימות כניסה יחידה", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Use the require single-sign-on authentication policy to require all members to log in with SSO.'" }, "ssoPolicyHelpEnd": { - "message": "to require all members to log in with SSO.", + "message": "כדי לדרוש מכל החברים להיכנס עם SSO.", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Use the require single-sign-on authentication policy to require all members to log in with SSO.'" }, "memberDecryptionOption": { - "message": "Member decryption options" + "message": "אפשרויות פענוח חברים" }, "memberDecryptionPassDesc": { - "message": "Once authenticated, members will decrypt vault data using their master passwords." + "message": "ברגע שאומתו, חברים יפענחו נתוני כספת באמצעות הסיסמאות הראשיות שלהם." }, "keyConnector": { "message": "Key Connector" }, "memberDecryptionKeyConnectorDescStart": { - "message": "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", + "message": "חבר כניסה עם SSO למפתח הפענוח של השרת באירוח עצמי שלך. אם תשתמש באפשרות זו, חברים לא יצטרכו להשתמש בסיסמאות הראשיות שלהם כדי לפענח את נתוני הכספת.", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Connect login with SSO to your self-hosted decryption key server. Using this option, members won’t need to use their master passwords to decrypt vault data. The require SSO authentication and single organization policies are required to set up Key Connector decryption. Contact Bitwarden Support for set up assistance.'" }, "memberDecryptionKeyConnectorDescLink": { - "message": "require SSO authentication and single organization policies", + "message": "הפוליסות דרוש אימות SSO וארגון יחיד", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Connect login with SSO to your self-hosted decryption key server. Using this option, members won’t need to use their master passwords to decrypt vault data. The require SSO authentication and single organization policies are required to set up Key Connector decryption. Contact Bitwarden Support for set up assistance.'" }, "memberDecryptionKeyConnectorDescEnd": { - "message": "are required to set up Key Connector decryption. Contact Bitwarden Support for set up assistance.", + "message": "נדרשות עבור הגדרת פענוח Key Connector. פנה אל תמיכת Bitwarden עבור סיוע בהגדרה.", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Connect login with SSO to your self-hosted decryption key server. Using this option, members won’t need to use their master passwords to decrypt vault data. The require SSO authentication and single organization policies are required to set up Key Connector decryption. Contact Bitwarden Support for set up assistance.'" }, "keyConnectorPolicyRestriction": { - "message": "\"Login with SSO and Key Connector Decryption\" is activated. This policy will only apply to owners and admins." + "message": "\"כניסה עם SSO ופענוח Key Connector\" מופעל. המדיניות תחול רק על בעלים ומנהלים." }, "enabledSso": { - "message": "SSO turned on" + "message": "SSO מופעל" }, "disabledSso": { - "message": "SSO turned on" + "message": "SSO כבוי" }, "enabledKeyConnector": { - "message": "Key Connector activated" + "message": "Key Connector הופעל" }, "disabledKeyConnector": { - "message": "Key Connector deactivated" + "message": "Key Connector הושבת" }, "keyConnectorWarning": { - "message": "Once members begin using Key Connector, your organization cannot revert to master password decryption. Proceed only if you are comfortable deploying and managing a key server." + "message": "ברגע שמחברים יתחילו להשתמש ב־Key Connector, הארגון שלך לא יוכל לחזור לפענוח סיסמה ראשית. המשך רק אם נוח לך לפרוס ולנהל שרת מפתחות." }, "migratedKeyConnector": { - "message": "Migrated to Key Connector" + "message": "העובר ל־Key Connector" }, "paymentSponsored": { - "message": "Please provide a payment method to associate with the organization. Don't worry, we won't charge you anything unless you select additional features or your sponsorship expires. " + "message": "נא לספק שיטת תשלום לשיוך עם הארגון. אל דאגה, אנחנו לא נחייב אותך בכלום אלא אם תבחר תכונות נוספות או שהחסות שלך תפוג. " }, "orgCreatedSponsorshipInvalid": { - "message": "The sponsorship offer has expired. You may delete the organization you created to avoid a charge at the end of your 7 day trial. Otherwise you may close this prompt to keep the organization and assume billing responsibility." + "message": "פג תוקפה של הצעת החסות. אתה יכול למחוק את הארגון שיצרת כדי להימנע מחיוב בסוף הניסיון בן 7 הימים שלך. אחרת אתה יכול לסגור הנחיה זו כדי להשאיר את הארגון ולקחת אחריות על החיוב." }, "newFamiliesOrganization": { - "message": "New Families organization" + "message": "ארגון משפחות חדש" }, "acceptOffer": { - "message": "Accept offer" + "message": "קבל הצעה" }, "sponsoringOrg": { - "message": "Sponsoring organization" + "message": "ארגון נותן חסות" }, "keyConnectorTest": { - "message": "Test" + "message": "בדיקה" }, "keyConnectorTestSuccess": { - "message": "Success! Key Connector reached." + "message": "הצלחה! Key Connector הושג." }, "keyConnectorTestFail": { - "message": "Cannot reach Key Connector. Check URL." + "message": "לא ניתן להשיג Key Connector. בדוק URL." }, "sponsorshipTokenHasExpired": { - "message": "The sponsorship offer has expired." + "message": "פג תוקפה של הצעת החסות." }, "freeWithSponsorship": { - "message": "FREE with sponsorship" + "message": "חינם עם חסות" }, "viewBillingSyncToken": { - "message": "View billing sync token" + "message": "הצג אסימון סנכרון חיוב" }, "generateBillingToken": { - "message": "Generate billing token" + "message": "צור אסימון חיוב" }, "copyPasteBillingSync": { - "message": "Copy and paste this token into the billing sync settings of your self-hosted organization." + "message": "העתק והדבק את האסימון הזה לתוך הגדרות סנכרון החיוב של הארגון באירוח עצמי שלך." }, "billingSyncCanAccess": { - "message": "Your billing sync token can access and edit this organization's subscription settings." + "message": "אסימון סנכרון החיוב שלך יכול לגשת אל ולערוך את הגדרות המנוי של ארגון זה." }, "manageBillingTokenSync": { - "message": "Manage Billing Token" + "message": "נהל אסימון חיוב" }, "setUpBillingSync": { - "message": "Set up billing sync" + "message": "הגדר סנכרון חיוב" }, "generateToken": { - "message": "Generate token" + "message": "צור אסימון" }, "rotateToken": { - "message": "Rotate token" + "message": "סובב אסימון" }, "rotateBillingSyncTokenWarning": { - "message": "If you proceed, you will need to re-setup billing sync on your self-hosted server." + "message": "אם תמשיך, תצטרך להגדיר מחדש סנכרון חיוב בשרת באירוח עצמי שלך." }, "rotateBillingSyncTokenTitle": { - "message": "Rotating the billing sync token will invalidate the previous token." + "message": "סיבוב של אסימון סנכרון החיוב יבטל את התוקף של האסימון הקודם." }, "selfHostedServer": { - "message": "self-hosted" + "message": "אירוח עצמי" }, "customEnvironment": { - "message": "Custom environment" + "message": "סביבה מותאמת אישית" }, "selfHostedBaseUrlHint": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" + "message": "ציין את בסיס ה־URL של התקנת Bitwarden באירוח מקומי שלך. דוגמה: https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { - "message": "For advanced configuration, you can specify the base URL of each service independently." + "message": "עבור תצורה מתקדמת, באפשרותך לציין את בסיס ה־URL של כל שירות בנפרד." }, "selfHostedEnvFormInvalid": { - "message": "You must add either the base Server URL or at least one custom environment." + "message": "אתה מוכרח להוסיף או את בסיס ה־URL של השרת או לפחות סביבה מותאמת אישית אחת." }, "apiUrl": { - "message": "API server URL" + "message": "URL של שרת ה־API" }, "webVaultUrl": { - "message": "Web vault server URL" + "message": "URL של שרת כספת הרשת" }, "identityUrl": { - "message": "Identity server URL" + "message": "URL של שרת הזהות" }, "notificationsUrl": { - "message": "Notifications server URL" + "message": "URL של שרת ההודעות" }, "iconsUrl": { - "message": "Icons server URL" + "message": "URL של שרת הסמלים" }, "environmentSaved": { - "message": "Environment URLs saved" + "message": "כתובות URL של הסביבה נשמרו" }, "selfHostingTitle": { - "message": "Self-hosting" + "message": "אירוח עצמי" }, "selfHostingEnterpriseOrganizationSectionCopy": { - "message": "To set-up your organization on your own server, you will need to upload your license file. To support Free Families plans and advanced billing capabilities for your self-hosted organization, you will need to set up billing sync." + "message": "כדי להגדיר את הארגון שלך בשרת שלך, תצטרך להעלות את קובץ הרישיון שלך. כדי לתמוך בתוכניות למשפחות בחינם וביכולות חיוב מתקדמות עבור הארגון באירוח עצמי שלך, תצטרך להגדיר סנכרון חיוב." }, "billingSyncApiKeyRotated": { - "message": "Token rotated" + "message": "האסימון סובב" }, "billingSyncKeyDesc": { - "message": "A billing sync token from your cloud organization's subscription settings is required to complete this form." + "message": "נדרש אסימון סנכרון חיוב מהגדרות המנוי של הארגון בענן שלך כדי להשלים טופס זה." }, "billingSyncKey": { - "message": "Billing sync token" + "message": "אסימון סנכרון חיוב" }, "automaticBillingSyncDesc": { - "message": "Automatic sync unlocks Families sponsorships and allows you to sync your license without uploading a file. After making updates in the Bitwarden cloud server, select Sync License to apply changes." + "message": "סנכרון אוטומטי פותח חסויות למשפחות ומאפשר לך לסנכרן את הרישיון שלך מבלי להעלות קובץ. לאחר ביצוע עדכונים בשרת הענן של Bitwarden, בחר 'סנכרן רישיון' כדי להחיל שינויים." }, "active": { - "message": "Active" + "message": "פעיל" }, "inactive": { - "message": "Inactive" + "message": "לא פעיל" }, "sentAwaitingSync": { - "message": "Sent (awaiting sync)" + "message": "נשלח (ממתין לסנכרון)" }, "sent": { - "message": "Sent" + "message": "נשלח" }, "requestRemoved": { - "message": "Removed (awaiting sync)" + "message": "הוסר (ממתין לסנכרון)" }, "requested": { - "message": "Requested" + "message": "נדרש" }, "formErrorSummaryPlural": { - "message": "$COUNT$ fields above need your attention.", + "message": "$COUNT$ שדות למעלה צריכים את תשומת לבך.", "placeholders": { "count": { "content": "$1", @@ -6397,10 +6672,10 @@ } }, "formErrorSummarySingle": { - "message": "1 field above needs your attention." + "message": "שדה 1 למעלה צריך את תשומת לבך." }, "fieldRequiredError": { - "message": "$FIELDNAME$ is required.", + "message": "$FIELDNAME$ נדרש/ת.", "placeholders": { "fieldname": { "content": "$1", @@ -6409,10 +6684,10 @@ } }, "required": { - "message": "required" + "message": "נדרש" }, "charactersCurrentAndMaximum": { - "message": "$CURRENT$/$MAX$ character maximum", + "message": "$CURRENT$/$MAX$ תווים מקסימום", "placeholders": { "current": { "content": "$1", @@ -6425,7 +6700,7 @@ } }, "characterMaximum": { - "message": "$MAX$ character maximum", + "message": "$MAX$ תווים מקסימום", "placeholders": { "max": { "content": "$1", @@ -6434,31 +6709,31 @@ } }, "idpSingleSignOnServiceUrlRequired": { - "message": "Required if Entity ID is not a URL." + "message": "נדרש אם מזהה הישות אינו URL." }, "offerNoLongerValid": { - "message": "This offer is no longer valid. Contact your organization administrators for more information." + "message": "הצעה זו אינה תקפה עוד. צור קשר עם מנהלי הארגון שלך למידע נוסף." }, "openIdOptionalCustomizations": { - "message": "Optional customizations" + "message": "התאמות אישיות אופציונליות" }, "openIdAuthorityRequired": { - "message": "Required if Authority is not valid." + "message": "נדרשת אם הרשות אינה חוקית." }, "separateMultipleWithComma": { - "message": "Separate multiple with a comma." + "message": "הפרד מרובים עם פסיק." }, "sessionTimeout": { - "message": "Your session has timed out. Please go back and try logging in again." + "message": "זמן ההפעלה שלך תם. נא לחזור ולנסות להיכנס שוב." }, "exportingPersonalVaultTitle": { - "message": "Exporting individual vault" + "message": "מייצא כספת אישית" }, "exportingOrganizationVaultTitle": { - "message": "Exporting organization vault" + "message": "מייצא כספת ארגון" }, "exportingIndividualVaultDescription": { - "message": "Only the individual vault items associated with $EMAIL$ will be exported. Organization vault items will not be included. Only vault item information will be exported and will not include associated attachments.", + "message": "רק פריטי הכספת האישית המשויכת עם $EMAIL$ ייוצאו. פריטי כספת ארגון לא יכללו. רק פרטי פריט כספת ייוצאו ולא יכללו צרופות משויכות.", "placeholders": { "email": { "content": "$1", @@ -6467,7 +6742,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": "רק כספת הארגון המשויכת עם $ORGANIZATION$ תיוצא. פריטים בכספת אישית או ארגונים אחרים לא יכללו.", "placeholders": { "organization": { "content": "$1", @@ -6476,32 +6751,32 @@ } }, "accessDenied": { - "message": "Access denied. You do not have permission to view this page." + "message": "גישה נדחתה. אין לך הרשאות כדי לצפות בעמוד זה." }, "masterPassword": { - "message": "Master password" + "message": "סיסמה ראשית" }, "security": { - "message": "Security" + "message": "אבטחה" }, "keys": { - "message": "Keys" + "message": "מפתחות" }, "billingHistory": { - "message": "Billing history" + "message": "היסטוריית חיובים" }, "backToReports": { - "message": "Back to reports" + "message": "חזרה לדוחות" }, "organizationPicker": { - "message": "Organization picker" + "message": "בוחר ארגון" }, "currentOrganization": { - "message": "Current organization", + "message": "ארגון נוכחי", "description": "This is used by screen readers to indicate the organization that is currently being shown to the user." }, "accountLoggedInAsName": { - "message": "Account: Logged in as $NAME$", + "message": "חשבון: מחובר בתור $NAME$", "placeholders": { "name": { "content": "$1", @@ -6510,29 +6785,20 @@ } }, "accountSettings": { - "message": "Account settings" + "message": "הגדרות חשבון" }, "generator": { - "message": "Generator", + "message": "מחולל", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, - "regenerateUsername": { - "message": "Regenerate username" - }, "generateUsername": { - "message": "Generate username" + "message": "צור שם משתמש" }, "generateEmail": { - "message": "Generate email" + "message": "צור דוא\"ל" }, "spinboxBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$.", + "message": "הערך חייב להיות בין $MIN$ ל־$MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6546,7 +6812,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "message": " השתמש ב־$RECOMMENDED$ תווים או יותר כדי ליצור סיסמה חזקה.", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -6556,7 +6822,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "message": " השתמש ב־$RECOMMENDED$ מילים או יותר כדי ליצור ביטוי סיסמה חזק.", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -6565,60 +6831,60 @@ } } }, - "usernameType": { - "message": "Username type" - }, "plusAddressedEmail": { - "message": "Plus addressed email", + "message": "דוא\"ל ממוען בפלוס", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" }, "plusAddressedEmailDesc": { - "message": "Use your email provider's sub-addressing capabilities." + "message": "השתמש ביכולות מיעון משנה של ספק הדוא\"ל שלך." }, "catchallEmail": { - "message": "Catch-all email" + "message": "דוא\"ל תופס־כל" }, "catchallEmailDesc": { - "message": "Use your domain's configured catch-all inbox." + "message": "השתמש בתיבת דואר תפוס־כל המוגדרת בדומיין שלך." + }, + "useThisEmail": { + "message": "השתמש בדוא\"ל זה" }, "random": { - "message": "Random", + "message": "אקראי", "description": "Generates domain-based username using random letters" }, "randomWord": { - "message": "Random word" + "message": "מילה אקראית" }, "usernameGenerator": { - "message": "Username generator" + "message": "מחולל שם משתמש" }, "useThisPassword": { - "message": "Use this password" + "message": "השתמש בסיסמה זו" }, "useThisUsername": { - "message": "Use this username" + "message": "השתמש בשם משתמש זה" }, "securePasswordGenerated": { - "message": "Secure password generated! Don't forget to also update your password on the website." + "message": "סיסמה מאובטחת נוצרה! אל תשכח גם לעדכן את הסיסמה שלך באתר האינטרנט." }, "useGeneratorHelpTextPartOne": { - "message": "Use the generator", + "message": "השתמש במחולל", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "useGeneratorHelpTextPartTwo": { - "message": "to create a strong unique password", + "message": "כדי ליצור סיסמה חזקה וייחודית", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "service": { - "message": "Service" + "message": "שירות" }, "unknownCipher": { - "message": "Unknown item, you may need to request permission to access this item." + "message": "פריט לא ידוע, ייתכן שאתה צריך לבקש הרשאה כדי לגשת אל פריט זה." }, "cannotSponsorSelf": { - "message": "You cannot redeem for the active account. Enter a different email." + "message": "אינך יכול לממש עבור החשבון הפעיל. הזן דוא\"ל אחר." }, "revokeWhenExpired": { - "message": "Expires $DATE$", + "message": "יפוג $DATE$", "placeholders": { "date": { "content": "$1", @@ -6627,7 +6893,7 @@ } }, "awaitingSyncSingular": { - "message": "Token rotated $DAYS$ day ago. Update the billing sync token in your self-hosted organization settings.", + "message": "האסימון סובב לפני $DAYS$ יום. עדכן את אסימון סנכרון החיוב בהגדרות הארגון באירוח עצמי שלך.", "placeholders": { "days": { "content": "$1", @@ -6636,7 +6902,7 @@ } }, "awaitingSyncPlural": { - "message": "Token rotated $DAYS$ days ago. Update the billing sync token in your self-hosted organization settings.", + "message": "האסימון סובב לפני $DAYS$ יום. עדכן את אסימון סנכרון החיוב בהגדרות הארגון באירוח עצמי שלך.", "placeholders": { "days": { "content": "$1", @@ -6645,14 +6911,14 @@ } }, "lastSync": { - "message": "Last sync", + "message": "סנכרון אחרון", "description": "Used as a prefix to indicate the last time a sync occurred. Example \"Last sync 1968-11-16 00:00:00\"" }, "sponsorshipsSynced": { - "message": "Self-hosted sponsorships synced." + "message": "חסויות של אירוח עצמי סונכרנו." }, "billingManagedByProvider": { - "message": "Managed by $PROVIDER$", + "message": "מנוהל על ידי $PROVIDER$", "placeholders": { "provider": { "content": "$1", @@ -6661,25 +6927,25 @@ } }, "billingContactProviderForAssistance": { - "message": "Please reach out to them for further assistance", + "message": "נא ליצור איתם קשר עבור סיוע נוסף", "description": "This text is displayed if an organization's billing is managed by a Provider. It tells the user to contact the Provider for assistance." }, "forwardedEmail": { - "message": "Forwarded email alias" + "message": "כינוי דוא\"ל מועבר" }, "forwardedEmailDesc": { - "message": "Generate an email alias with an external forwarding service." + "message": "צור כינוי דוא\"ל עם שירות העברה חיצוני." }, "forwarderDomainName": { - "message": "Email domain", + "message": "דומיין דוא\"ל", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Choose a domain that is supported by the selected service", + "message": "בחר דומיין שנתמך על ידי השירות הנבחר", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { - "message": "$SERVICENAME$ error: $ERRORMESSAGE$", + "message": "$SERVICENAME$ שגיאה: $ERRORMESSAGE$", "description": "Reports an error returned by a forwarding service to the user.", "placeholders": { "servicename": { @@ -6693,11 +6959,11 @@ } }, "forwarderGeneratedBy": { - "message": "Generated by Bitwarden.", + "message": "נוצר על ידי Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "אתר: $WEBSITE$. נוצר על ידי Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -6707,7 +6973,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "אסימון API של $SERVICENAME$ לא חוקי", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -6717,7 +6983,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "אסימון API של $SERVICENAME$ לא חוקי: $ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -6730,8 +6996,32 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ סירב לבקשה שלך. נא ליצור קשר עם נותן השירות שלך עבור סיוע.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ סירב לבקשה שלך: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { - "message": "Unable to obtain $SERVICENAME$ masked email account ID.", + "message": "לא ניתן להשיג מזהה חשבון דוא\"ל מסווה של $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -6741,7 +7031,7 @@ } }, "forwarderNoDomain": { - "message": "Invalid $SERVICENAME$ domain.", + "message": "דומיין $SERVICENAME$ לא חוקי.", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -6751,7 +7041,7 @@ } }, "forwarderNoUrl": { - "message": "Invalid $SERVICENAME$ url.", + "message": "כתובת url של $SERVICENAME$ לא חוקית.", "description": "Displayed when the url of the forwarding service wasn't supplied.", "placeholders": { "servicename": { @@ -6761,7 +7051,7 @@ } }, "forwarderUnknownError": { - "message": "Unknown $SERVICENAME$ error occurred.", + "message": "התרחשה שגיאת $SERVICENAME$ לא ידועה.", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -6771,7 +7061,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Unknown forwarder: '$SERVICENAME$'.", + "message": "משלח לא ידוע: '$SERVICENAME$'.", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -6781,26 +7071,23 @@ } }, "hostname": { - "message": "Hostname", + "message": "שם מארח", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API access token" - }, "deviceVerification": { - "message": "Device verification" + "message": "אימות מכשיר" }, "enableDeviceVerification": { - "message": "Turn on device verification" + "message": "הפעל אימות מכשיר" }, "deviceVerificationDesc": { - "message": "Verification codes are sent to your email address when logging in from an unrecognized device" + "message": "קודי אימות נשלחים לכתובת הדוא\"ל שלך בעת כניסה ממכשיר לא מזוהה" }, "updatedDeviceVerification": { - "message": "Updated device verification" + "message": "אימות מכשיר מעודכן" }, "areYouSureYouWantToEnableDeviceVerificationTheVerificationCodeEmailsWillArriveAtX": { - "message": "Are you sure you want to turn on device verification? The verification code emails will arrive at: $EMAIL$", + "message": "האם אתה בטוח שברצונך להפעיל אימות מכשיר? הודעות דוא\"ל עם קוד אימות יגיעו אל: $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -6809,52 +7096,52 @@ } }, "premiumSubcriptionRequired": { - "message": "Premium subscription required" + "message": "נדרש מנוי פרימיום" }, "scim": { - "message": "SCIM provisioning", + "message": "הקצאת SCIM", "description": "The text, 'SCIM', is an acronym and should not be translated." }, "scimDescription": { - "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning", + "message": "הקצה באופן אוטומטי משתמשים וקבוצות עם ספקי הזהות המועדפים עליך באמצעות הקצאת SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." }, "scimIntegrationDescription": { - "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "message": "הקצה באופן אוטומטי משתמשים וקבוצות עם ספקי הזהות המועדפים עליך באמצעות הקצאת SCIM. מצא שילובים נתמכים", "description": "the text, 'SCIM', is an acronym and should not be translated." }, "scimEnabledCheckboxDesc": { - "message": "Enable SCIM", + "message": "הפעל SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." }, "scimEnabledCheckboxDescHelpText": { - "message": "Set up your preferred identity provider by configuring the URL and SCIM API Key", + "message": "הגדר את ספק הזהות המועדף עליך באמצעות הגדרת כתובת ה־URL ומפתח ה־API של SCIM", "description": "the text, 'SCIM', is an acronym and should not be translated." }, "scimApiKeyHelperText": { - "message": "This API key has access to manage users within your organization. It should be kept secret." + "message": "למפתח API זה יש גישה לנהל משתמשים בתוך הארגון שלך. יש לשמור עליו בסוד." }, "copyScimKey": { - "message": "Copy the SCIM API key to your clipboard", + "message": "העתק את מפתח ה־API של SCIM ללוח ההעתקה שלך", "description": "the text, 'SCIM' and 'API', are acronyms and should not be translated." }, "rotateScimKey": { - "message": "Rotate the SCIM API key", + "message": "סובב את מפתח ה־API של SCIM", "description": "the text, 'SCIM' and 'API', are acronyms and should not be translated." }, "rotateScimKeyWarning": { - "message": "Are you sure you want to rotate the SCIM API Key? The current key will no longer work for any existing integrations.", + "message": "האם אתה בטוח שברצונך לסובב את מפתח ה־API של SCIM? המפתח הנוכחי לא יעבוד יותר עם שילובים קיימים כלשהם.", "description": "the text, 'SCIM' and 'API', are acronyms and should not be translated." }, "rotateKey": { - "message": "Rotate key" + "message": "סובב מפתח" }, "scimApiKey": { - "message": "SCIM API key", + "message": "מפתח API של SCIM", "description": "the text, 'SCIM' and 'API', are acronyms and should not be translated." }, "copyScimUrl": { - "message": "Copy the SCIM endpoint URL to your clipboard", + "message": "העתק את כתובת ה־URL של נקודת קצה SCIM ללוח ההעתקה שלך", "description": "the text, 'SCIM' and 'URL', are acronyms and should not be translated." }, "scimUrl": { @@ -6862,21 +7149,21 @@ "description": "the text, 'SCIM' and 'URL', are acronyms and should not be translated." }, "scimApiKeyRotated": { - "message": "SCIM API key successfully rotated", + "message": "מפתח API של SCIM סובב בהצלחה", "description": "the text, 'SCIM' and 'API', are acronyms and should not be translated." }, "scimSettingsSaved": { - "message": "SCIM settings saved", + "message": "הגדרות SCIM נשמרו", "description": "the text, 'SCIM', is an acronym and should not be translated." }, "inputRequired": { - "message": "Input is required." + "message": "נדרש קלט." }, "inputEmail": { - "message": "Input is not an email address." + "message": "קלט הוא לא כתובת דוא\"ל." }, "inputMinLength": { - "message": "Input must be at least $COUNT$ characters long.", + "message": "אורך הקלט חייב להיות לפחות $COUNT$ תווים.", "placeholders": { "count": { "content": "$1", @@ -6885,7 +7172,7 @@ } }, "inputMaxLength": { - "message": "Input must not exceed $COUNT$ characters in length.", + "message": "אורך הקלט לא יעלה על $COUNT$ תווים.", "placeholders": { "count": { "content": "$1", @@ -6894,7 +7181,7 @@ } }, "inputForbiddenCharacters": { - "message": "The following characters are not allowed: $CHARACTERS$", + "message": "התווים הבאים אינם מותרים: $CHARACTERS$", "placeholders": { "characters": { "content": "$1", @@ -6903,7 +7190,7 @@ } }, "inputMinValue": { - "message": "Input value must be at least $MIN$.", + "message": "ערך הקלט חייב להיות לפחות $MIN$.", "placeholders": { "min": { "content": "$1", @@ -6912,7 +7199,7 @@ } }, "inputMaxValue": { - "message": "Input value must not exceed $MAX$.", + "message": "ערך הקלט לא יעלה על $MAX$.", "placeholders": { "max": { "content": "$1", @@ -6921,10 +7208,10 @@ } }, "multipleInputEmails": { - "message": "1 or more emails are invalid" + "message": "כתובת דוא\"ל 1 או יותר אינה חוקית" }, "tooManyEmails": { - "message": "You can only submit up to $COUNT$ emails at a time", + "message": "אתה יכול לשלוח רק עד $COUNT$ הודעות דוא\"ל בו זמנית", "placeholders": { "count": { "content": "$1", @@ -6933,7 +7220,7 @@ } }, "fieldsNeedAttention": { - "message": "$COUNT$ field(s) above need your attention.", + "message": "$COUNT$ שדות למעלה צריכים את תשומת לבך.", "placeholders": { "count": { "content": "$1", @@ -6942,10 +7229,10 @@ } }, "singleFieldNeedsAttention": { - "message": "1 field needs your attention." + "message": "שדה 1 צריך את תשומת לבך." }, "multipleFieldsNeedAttention": { - "message": "$COUNT$ fields need your attention.", + "message": "$COUNT$ שדות צריכים את תשומת לבך.", "placeholders": { "count": { "content": "$1", @@ -6954,249 +7241,249 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + "message": "שגיאה בהתחברות עם השירות Duo. השתמש בשיטת כניסה דו־שלבית אחרת או פנה אל Duo לסיוע." }, "launchDuoAndFollowStepsToFinishLoggingIn": { - "message": "Launch Duo and follow the steps to finish logging in." + "message": "פתח את Duo ועקוב אחר השלבים כדי לסיים להיכנס." }, "duoRequiredByOrgForAccount": { - "message": "Duo two-step login is required for your account." + "message": "נדרשת כניסה דו־שלבית של Duo עבור החשבון שלך." + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "נדרשת כניסה דו־שלבית של Duo עבור החשבון שלך. עקוב אחר השלבים למטה כדי לסיים להיכנס." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "עקוב אחר השלבים למטה כדי לסיים להיכנס." }, "launchDuo": { - "message": "Launch Duo" + "message": "פתח את Duo" }, "turnOn": { - "message": "Turn on" + "message": "הפעל" }, "on": { - "message": "On" + "message": "מופעל" }, "off": { - "message": "Off" + "message": "כבוי" }, "members": { - "message": "Members" + "message": "חברים" }, "reporting": { - "message": "Reporting" + "message": "מדווח" }, "numberOfUsers": { - "message": "Number of users" - }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" + "message": "מספר משתמשים" }, "pickAnAvatarColor": { - "message": "Pick an avatar color" + "message": "בחר צבע יצגן" }, "customizeAvatar": { - "message": "Customize avatar" + "message": "התאם אישית יצגן" }, "avatarUpdated": { - "message": "Avatar updated" + "message": "יצגן עודכן" }, "brightBlue": { - "message": "Bright Blue" + "message": "כחול בהיר" }, "green": { - "message": "Green" + "message": "ירוק" }, "orange": { - "message": "Orange" + "message": "כתום" }, "lavender": { - "message": "Lavender" + "message": "לבנדר" }, "yellow": { - "message": "Yellow" + "message": "צהוב" }, "indigo": { - "message": "Indigo" + "message": "אינדיגו" }, "teal": { - "message": "Teal" + "message": "ירוק כחלחל" }, "salmon": { - "message": "Salmon" + "message": "סלמון" }, "pink": { - "message": "Pink" + "message": "ורוד" }, "customColor": { - "message": "Custom Color" + "message": "צבע מותאם אישית" }, "selectPlaceholder": { - "message": "-- Select --" + "message": "-- בחר --" }, "multiSelectPlaceholder": { - "message": "-- Type to filter --" + "message": "-- הקלד כדי לסנן --" }, "multiSelectLoading": { - "message": "Retrieving options..." + "message": "מאחזר אפשרויות..." }, "multiSelectNotFound": { - "message": "No items found" + "message": "לא נמצאו פריטים" }, "multiSelectClearAll": { - "message": "Clear all" + "message": "נקה הכל" }, "toggleCharacterCount": { - "message": "Toggle character count", + "message": "החלף מצב מונה תווים", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, "passwordCharacterCount": { - "message": "Password character count", + "message": "מונה תווי סיסמה", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, "hide": { - "message": "Hide" + "message": "הסתר" }, "projects": { - "message": "Projects", + "message": "פרויקטים", "description": "Description for the Projects field." }, "lastEdited": { - "message": "Last edited", + "message": "נערך לאחרונה", "description": "The label for the date and time when a item was last edited." }, "editSecret": { - "message": "Edit secret", + "message": "ערוך סוד", "description": "Action to modify an existing secret." }, "addSecret": { - "message": "Add secret", + "message": "הוסף סוד", "description": "Action to create a new secret." }, "copySecretName": { - "message": "Copy secret name", + "message": "העתק שם סוד", "description": "Action to copy the name of a secret to the system's clipboard." }, "copySecretValue": { - "message": "Copy secret value", + "message": "העתק ערך סוד", "description": "Action to copy the value of a secret to the system's clipboard." }, "deleteSecret": { - "message": "Delete secret", + "message": "מחק סוד", "description": "Action to delete a single secret from the system." }, "deleteSecrets": { - "message": "Delete secrets", + "message": "מחק סודות", "description": "The action to delete multiple secrets from the system." }, "hardDeleteSecret": { - "message": "Permanently delete secret" + "message": "מחק סוד לצמיתות" }, "hardDeleteSecrets": { - "message": "Permanently delete secrets" + "message": "מחק סודות לצמיתות" }, "secretProjectAssociationDescription": { - "message": "Select projects that the secret will be associated with. Only organization users with access to these projects will be able to see the secret.", + "message": "בחר פרויקטים שאליהם הסוד ישויך. רק משתמשי ארגון עם גישה לפרויקטים האלה יוכלו לראות את הסוד.", "description": "A prompt explaining how secrets can be associated with projects." }, "selectProjects": { - "message": "Select projects", + "message": "בחירת פרויקטים", "description": "A label for a type-to-filter input field to choose projects." }, "searchProjects": { - "message": "Search projects", + "message": "חפש פרויקטים", "description": "Label for the search bar used to search projects." }, "project": { - "message": "Project", + "message": "פרויקט", "description": "Similar to collections, projects can be used to group secrets." }, "editProject": { - "message": "Edit project", + "message": "ערוך פרויקט", "description": "The action to modify an existing project." }, "viewProject": { - "message": "View project", + "message": "הצג פרויקט", "description": "The action to view details of a project." }, "deleteProject": { - "message": "Delete project", + "message": "מחק פרויקט", "description": "The action to delete a project from the system." }, "deleteProjects": { - "message": "Delete projects", + "message": "מחק פרויקטים", "description": "The action to delete multiple projects from the system." }, "secret": { - "message": "Secret", + "message": "סוד", "description": "Label for a secret (key/value pair)" }, "serviceAccount": { - "message": "Service account", + "message": "חשבון שירות", "description": "A machine user which can be used to automate processes and access secrets in the system." }, "serviceAccounts": { - "message": "Service accounts", + "message": "חשבונות שירות", "description": "The title for the section that deals with service accounts." }, "secrets": { - "message": "Secrets", + "message": "סודות", "description": "The title for the section of the application that deals with secrets." }, "nameValuePair": { - "message": "Name/Value pair", + "message": "צמד שם/ערך", "description": "Title for a name/ value pair. Secrets typically consist of a name and value pair." }, "secretEdited": { - "message": "Secret edited", + "message": "סוד נערך", "description": "Notification for the successful editing of a secret." }, "secretCreated": { - "message": "Secret created", + "message": "הסוד נוצר", "description": "Notification for the successful creation of a secret." }, "newSecret": { - "message": "New secret", + "message": "סוד חדש", "description": "Title for creating a new secret." }, "newServiceAccount": { - "message": "New service account", + "message": "חשבון שירות חדש", "description": "Title for creating a new service account." }, "secretsNoItemsTitle": { - "message": "No secrets to show", + "message": "אין סודות להצגה", "description": "Empty state to indicate that there are no secrets to display." }, "secretsNoItemsMessage": { - "message": "To get started, add a new secret or import secrets.", + "message": "כדי להתחיל, הוסף סוד חדש או יבא סודות.", "description": "Message to encourage the user to start adding secrets." }, "secretsTrashNoItemsMessage": { - "message": "There are no secrets in the trash." + "message": "אין סודות באשפה." }, "serviceAccountsNoItemsMessage": { - "message": "Create a new service account to get started automating secret access.", + "message": "צור חשבון שירות חדש כדי להתחיל לאטמט גישת סודות.", "description": "Message to encourage the user to start creating service accounts." }, "serviceAccountsNoItemsTitle": { - "message": "Nothing to show yet", + "message": "אין מה להראות עדיין", "description": "Title to indicate that there are no service accounts to display." }, "searchSecrets": { - "message": "Search secrets", + "message": "חפש סודות", "description": "Placeholder text for searching secrets." }, "deleteServiceAccounts": { - "message": "Delete service accounts", + "message": "מחק חשבונות שירות", "description": "Title for the action to delete one or multiple service accounts." }, "deleteServiceAccount": { - "message": "Delete service account", + "message": "מחק חשבון שירות", "description": "Title for the action to delete a single service account." }, "viewServiceAccount": { - "message": "View service account", + "message": "הצג חשבון שירות", "description": "Action to view the details of a service account." }, "deleteServiceAccountDialogMessage": { - "message": "Deleting service account $SERVICE_ACCOUNT$ is permanent and irreversible.", + "message": "מחיקת חשבון שירות $SERVICE_ACCOUNT$ היא לצמיתות ובלתי הפיכה.", "placeholders": { "service_account": { "content": "$1", @@ -7205,10 +7492,10 @@ } }, "deleteServiceAccountsDialogMessage": { - "message": "Deleting service accounts is permanent and irreversible." + "message": "מחיקת חשבונות שירות היא לצמיתות ובלתי הפיכה." }, "deleteServiceAccountsConfirmMessage": { - "message": "Delete $COUNT$ service accounts", + "message": "מחק $COUNT$ חשבונות שירות", "placeholders": { "count": { "content": "$1", @@ -7217,98 +7504,98 @@ } }, "deleteServiceAccountToast": { - "message": "Service account deleted" + "message": "חשבון השירות נמחק" }, "deleteServiceAccountsToast": { - "message": "Service accounts deleted" + "message": "חשבונות השירות נמחקו" }, "searchServiceAccounts": { - "message": "Search service accounts", + "message": "חפש חשבונות שירות", "description": "Placeholder text for searching service accounts." }, "editServiceAccount": { - "message": "Edit service account", + "message": "ערוך חשבון שירות", "description": "Title for editing a service account." }, "addProject": { - "message": "Add project", + "message": "הוסף פרויקט", "description": "Title for creating a new project." }, "projectEdited": { - "message": "Project edited", + "message": "הפרויקט נערך", "description": "Notification for the successful editing of a project." }, "projectSaved": { - "message": "Project saved", + "message": "הפרויקט נשמר", "description": "Notification for the successful saving of a project." }, "projectCreated": { - "message": "Project created", + "message": "הפרויקט נוצר", "description": "Notification for the successful creation of a project." }, "projectName": { - "message": "Project name", + "message": "שם הפרויקט", "description": "Label for entering the name of a project." }, "newProject": { - "message": "New project", + "message": "פרויקט חדש", "description": "Title for creating a new project." }, "softDeleteSecretWarning": { - "message": "Deleting secrets can affect existing integrations.", + "message": "מחיקת סודות עשויה להשפיע על שילובים קיימים.", "description": "Warns that deleting secrets can have consequences on integrations" }, "softDeletesSuccessToast": { - "message": "Secrets sent to trash", + "message": "סודות נשלחו לאשפה", "description": "Notifies that the selected secrets have been moved to the trash" }, "hardDeleteSecretConfirmation": { - "message": "Are you sure you want to permanently delete this secret?" + "message": "האם אתה בטוח שברצונך למחוק לצמיתות את הסוד הזה?" }, "hardDeleteSecretsConfirmation": { - "message": "Are you sure you want to permanently delete these secrets?" + "message": "האם אתה בטוח שברצונך למחוק לצמיתות את הסודות האלה?" }, "hardDeletesSuccessToast": { - "message": "Secrets permanently deleted" + "message": "הסודות נמחקו לצמיתות" }, "smAccess": { - "message": "Access", + "message": "גישה", "description": "Title indicating what permissions a service account has" }, "projectCommaSecret": { - "message": "Project, Secret", + "message": "פרויקט, סוד", "description": "" }, "serviceAccountName": { - "message": "Service account name", + "message": "שם חשבון שירות", "description": "Label for the name of a service account" }, "serviceAccountCreated": { - "message": "Service account created", + "message": "חשבון השירות נוצר", "description": "Notifies that a new service account has been created" }, "serviceAccountUpdated": { - "message": "Service account updated", + "message": "חשבון השירות עודכן", "description": "Notifies that a service account has been updated" }, "newSaSelectAccess": { - "message": "Type or select projects or secrets", + "message": "הקלד או בחר פרויקטים או סודות", "description": "Instructions for selecting projects or secrets for a new service account" }, "newSaTypeToFilter": { - "message": "Type to filter", + "message": "הקלד כדי לסנן", "description": "Instructions for filtering a list of projects or secrets" }, "deleteProjectsToast": { - "message": "Projects deleted", + "message": "הפרויקטים נמחקו", "description": "Notifies that the selected projects have been deleted" }, "deleteProjectToast": { - "message": "Project deleted", + "message": "הפרויקט נמחק", "description": "Notifies that a project has been deleted" }, "deleteProjectDialogMessage": { - "message": "Deleting project $PROJECT$ is permanent and irreversible.", + "message": "מחיקת הפרויקט $PROJECT$ היא לצמיתות ובלתי הפיכה.", "description": "Informs users that projects are hard deleted and not sent to trash", "placeholders": { "project": { @@ -7318,7 +7605,7 @@ } }, "deleteProjectInputLabel": { - "message": "Type \"$CONFIRM$\" to continue", + "message": "הקלד \"$CONFIRM$\" כדי להמשיך", "description": "Users are prompted to type 'confirm' to delete a project", "placeholders": { "confirm": { @@ -7328,7 +7615,7 @@ } }, "deleteProjectConfirmMessage": { - "message": "Delete $PROJECT$", + "message": "מחק $PROJECT$", "description": "Confirmation prompt to delete a specific project, where '$PROJECT$' is a placeholder for the name of the project.", "placeholders": { "project": { @@ -7338,7 +7625,7 @@ } }, "deleteProjectsConfirmMessage": { - "message": "Delete $COUNT$ Projects", + "message": "מחק $COUNT$ פרויקטים", "description": "Confirmation prompt to delete multiple projects, where '$COUNT$' is a placeholder for the number of projects to be deleted.", "placeholders": { "count": { @@ -7348,119 +7635,119 @@ } }, "deleteProjectsDialogMessage": { - "message": "Deleting projects is permanent and irreversible.", + "message": "מחיקת פרויקטים היא לצמיתות ובלתי הפיכה.", "description": "This message is displayed in a dialog box as a warning before proceeding with project deletion." }, "projectsNoItemsTitle": { - "message": "No projects to display", + "message": "אין פרויקטים להצגה", "description": "Empty state to be displayed when there are no projects to display in the list." }, "projectsNoItemsMessage": { - "message": "Add a new project to get started organizing secrets.", + "message": "הוסף פרויקט חדש כדי להתחיל לארגן סודות.", "description": "Message to be displayed when there are no projects to display in the list." }, "smConfirmationRequired": { - "message": "Confirmation required", + "message": "נדרש אישור", "description": "Indicates that user confirmation is required for an action to proceed." }, "bulkDeleteProjectsErrorMessage": { - "message": "The following projects could not be deleted:", + "message": "לא היה ניתן למחוק את הפרויקטים הבאים:", "description": "Message to be displayed when there is an error during bulk project deletion." }, "softDeleteSuccessToast": { - "message": "Secret sent to trash", + "message": "הסוד נשלח לאשפה", "description": "Notification to be displayed when a secret is successfully sent to the trash." }, "hardDeleteSuccessToast": { - "message": "Secret permanently deleted" + "message": "הסוד נמחק לצמיתות" }, "accessTokens": { - "message": "Access tokens", + "message": "אסימוני גישה", "description": "Title for the section displaying access tokens." }, "newAccessToken": { - "message": "New access token", + "message": "אסימון גישה חדש", "description": "Button label for creating a new access token." }, "expires": { - "message": "Expires", + "message": "יפוג", "description": "Label for the expiration date of an access token." }, "canRead": { - "message": "Can read", + "message": "יכול/ה לקרוא", "description": "Label for the access level of an access token (Read only)." }, "accessTokensNoItemsTitle": { - "message": "No access tokens to show", + "message": "אין אסימוני גישה להציג", "description": "Title to be displayed when there are no access tokens to display in the list." }, "accessTokensNoItemsDesc": { - "message": "To get started, create an access token", + "message": "כדי להתחיל, צור אסימון גישה", "description": "Message to be displayed when there are no access tokens to display in the list." }, "downloadAccessToken": { - "message": "Download or copy before closing.", + "message": "הורד או העתק לפני שתסגור.", "description": "Message to be displayed before closing an access token, reminding the user to download or copy it." }, "expiresOnAccessToken": { - "message": "Expires on:", + "message": "יפוג ב:", "description": "Label for the expiration date of an access token." }, "accessTokenCallOutTitle": { - "message": "Access tokens are not stored and cannot be retrieved", + "message": "אסימוני גישה אינם מאוחסנים ולא ניתן לאחזר אותם", "description": "Notification to inform the user that access tokens are only displayed once and cannot be retrieved again." }, "copyToken": { - "message": "Copy token", + "message": "העתק אסימון", "description": "Copies the generated access token to the user's clipboard." }, "accessToken": { - "message": "Access token", + "message": "אסימון גישה", "description": "A unique string that gives a client application (eg. CLI) access to a secret or set of secrets." }, "accessTokenExpirationRequired": { - "message": "Expiration date required", + "message": "נדרש תאריך תפוגה", "description": "Error message indicating that an expiration date for the access token must be set." }, "accessTokenCreatedAndCopied": { - "message": "Access token created and copied to clipboard", + "message": "אסימון גישה נוצר והועתק ללוח", "description": "Notification to inform the user that the access token has been created and copied to the clipboard." }, "revokeAccessToken": { - "message": "Revoke access token", + "message": "בטל אסימון גישה", "description": "Invalidates / cancels an access token and as such removes access to secrets for the client application." }, "revokeAccessTokens": { - "message": "Revoke access tokens" + "message": "בטל אסימוני גישה" }, "revokeAccessTokenDesc": { - "message": "Revoking access tokens is permanent and irreversible." + "message": "ביטול אסימוני גישה הוא לצמיתות ובלתי הפיך." }, "accessTokenRevoked": { - "message": "Access tokens revoked", + "message": "אסימוני הגישה בוטלו", "description": "Toast message after deleting one or multiple access tokens." }, "noAccessTokenSelected": { - "message": "No access token selected to revoke", + "message": "לא נבחר אסימון גישה לביטול", "description": "Toast error message after trying to delete access tokens but not selecting any access tokens." }, "submenu": { - "message": "Submenu" + "message": "תפריט משנה" }, "from": { - "message": "From" + "message": "מאת" }, "to": { - "message": "To" + "message": "אל" }, "member": { - "message": "Member" + "message": "חבר" }, "update": { - "message": "Update" + "message": "עדכן" }, "plusNMore": { - "message": "+ $QUANTITY$ more", + "message": "+ עוד $QUANTITY$", "placeholders": { "quantity": { "content": "$1", @@ -7469,118 +7756,106 @@ } }, "groupInfo": { - "message": "Group info" + "message": "פרטי קבוצה" }, "editGroupMembersDesc": { - "message": "Grant members access to the group's assigned collections." + "message": "הענק לחברים גישה לאוספים המוקצים של הקבוצה." }, "editGroupCollectionsDesc": { - "message": "Grant access to collections by adding them to this group." + "message": "העתק גישה לאוספים בכך שתוסיף אותם לקבוצה זו." }, "restrictedCollectionAssignmentDesc": { - "message": "You can only assign collections you manage." + "message": "אתה יכול רק להקצות אוספים שאתה מנהל." }, "selectMembers": { - "message": "Select members" + "message": "בחר חברים" }, "selectCollections": { - "message": "Select collections" + "message": "בחר אוספים" }, "role": { - "message": "Role" + "message": "תפקיד" }, "removeMember": { - "message": "Remove member" + "message": "הסר חבר" }, "collection": { - "message": "Collection" + "message": "אוסף" }, "noCollection": { - "message": "No collection" - }, - "canView": { - "message": "Can view" - }, - "canViewExceptPass": { - "message": "Can view, except passwords" - }, - "canEdit": { - "message": "Can edit" - }, - "canEditExceptPass": { - "message": "Can edit, except passwords" + "message": "אין אוסף" }, "noCollectionsAdded": { - "message": "No collections added" + "message": "לא נוספו אוספים" }, "noMembersAdded": { - "message": "No members added" + "message": "לא נוספו חברים" }, "noGroupsAdded": { - "message": "No groups added" + "message": "לא נוספו קבוצות" }, "group": { - "message": "Group" + "message": "קבוצה" }, "domainVerification": { - "message": "Domain verification" + "message": "אימות דומיין" }, "newDomain": { - "message": "New domain" + "message": "דומיין חדש" }, "noDomains": { - "message": "No domains" + "message": "אין דומיינים" }, "noDomainsSubText": { - "message": "Connecting a domain allows members to skip the SSO identifier field during Login with SSO." + "message": "חיבור דומיין מאפשר לחברים לדלג על שדה מזהה SSO במהלך כניסה עם SSO." }, "verifyDomain": { - "message": "Verify domain" + "message": "אמת דומיין" }, "reverifyDomain": { - "message": "Reverify domain" + "message": "אמת מחדש דומיין" }, "copyDnsTxtRecord": { - "message": "Copy DNS TXT record" + "message": "העתק רשומת DNS TXT" }, "dnsTxtRecord": { - "message": "DNS TXT record" + "message": "רשומת DNS TXT" }, "dnsTxtRecordInputHint": { - "message": "Copy and paste the TXT record into your DNS Provider." + "message": "העתק והדבק את רשומת ה־TXT אל ספק ה־DNS שלך." }, "domainNameInputHint": { - "message": "Example: mydomain.com. Subdomains require separate entries to be verified." + "message": "דוגמה: mydomain.com. תת-דומיינים דורשים שרשומות נפרדות יאומתו." }, "automaticDomainVerification": { - "message": "Automatic Domain Verification" + "message": "אימות דומיין אוטומטי" }, "automaticDomainVerificationProcess": { - "message": "Bitwarden will attempt to verify the domain 3 times during the first 72 hours. If the domain can’t be verified, check the DNS record in your host and manually verify. The domain will be removed from your organization in 7 days if it is not verified" + "message": "Bitwarden ינסה לאמת את הדומיין 3 פעמים במהלך 72 השעות הראשונות. אם הדומיין אינו ניתן לאימות, בדוק את רשומת ה־DNS במארח שלך ואמת באופן ידני. הדומיין יוסר מהארגון שלך תוך 7 ימים אם הוא לא מאומת" }, "invalidDomainNameMessage": { - "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be verified." + "message": "הקלט אינו בפורמט תקין. פורמט: mydomain.com. תת-דומיינים דורשים שרשומות נפרדות יאומתו." }, "removeDomain": { - "message": "Remove domain" + "message": "הסר דומיין" }, "removeDomainWarning": { - "message": "Removing a domain cannot be undone. Are you sure you want to continue?" + "message": "הסרת דומיין אינה ניתנת לביטול. האם אתה בטוח שברצונך להמשיך?" }, "domainRemoved": { - "message": "Domain removed" + "message": "דומיין הוסר" }, "domainSaved": { - "message": "Domain saved" + "message": "דומיין נשמר" }, "domainVerified": { - "message": "Domain verified" + "message": "דומיין אומת" }, "duplicateDomainError": { - "message": "You can't claim the same domain twice." + "message": "אתה לא יכול לדרוש את אותו הדומיין פעמיים." }, "domainNotAvailable": { - "message": "Someone else is using $DOMAIN$. Use a different domain to continue.", + "message": "מישהו אחר משתמש ב־$DOMAIN$. השתמש בדומיין אחר כדי להמשיך.", "placeholders": { "DOMAIN": { "content": "$1", @@ -7589,7 +7864,7 @@ } }, "domainNotVerified": { - "message": "$DOMAIN$ not verified. Check your DNS record.", + "message": "$DOMAIN$ אינו מאומת. בדוק את רשומת ה־DNS שלך.", "placeholders": { "DOMAIN": { "content": "$1", @@ -7598,28 +7873,28 @@ } }, "domainStatusVerified": { - "message": "Verified" + "message": "מאומת" }, "domainStatusUnverified": { - "message": "Unverified" + "message": "לא מאומת" }, "domainNameTh": { - "message": "Name" + "message": "שם" }, "domainStatusTh": { - "message": "Status" + "message": "מצב" }, "lastChecked": { - "message": "Last checked" + "message": "נבדק לאחרונה" }, "editDomain": { - "message": "Edit domain" + "message": "ערוך דומיין" }, "domainFormInvalid": { - "message": "There are form errors that need your attention" + "message": "ישנן שגיאות טופס שדורשות את תשומת לבך" }, "addedDomain": { - "message": "Added domain $DOMAIN$", + "message": "דומיין $DOMAIN$ נוסף", "placeholders": { "DOMAIN": { "content": "$1", @@ -7628,7 +7903,7 @@ } }, "removedDomain": { - "message": "Removed domain $DOMAIN$", + "message": "דומיין $DOMAIN$ הוסר", "placeholders": { "DOMAIN": { "content": "$1", @@ -7637,7 +7912,7 @@ } }, "domainVerifiedEvent": { - "message": "$DOMAIN$ verified", + "message": "$DOMAIN$ מאומת", "placeholders": { "DOMAIN": { "content": "$1", @@ -7646,7 +7921,7 @@ } }, "domainNotVerifiedEvent": { - "message": "$DOMAIN$ not verified", + "message": "$DOMAIN$ אינו מאומת", "placeholders": { "DOMAIN": { "content": "$1", @@ -7655,79 +7930,79 @@ } }, "verificationRequiredForActionSetPinToContinue": { - "message": "Verification required for this action. Set a PIN to continue." + "message": "נדרש אימות עבור פעולה זו. הגדר PIN כדי להמשיך." }, "setPin": { - "message": "Set PIN" + "message": "הגדר PIN" }, "verifyWithBiometrics": { - "message": "Verify with biometrics" + "message": "אמת עם זיהוי ביומטרי" }, "awaitingConfirmation": { - "message": "Awaiting confirmation" + "message": "ממתין לאישור" }, "couldNotCompleteBiometrics": { - "message": "Could not complete biometrics." + "message": "לא היה ניתן להשלים את הזיהוי הביומטרי." }, "needADifferentMethod": { - "message": "Need a different method?" + "message": "זקוק לשיטה אחרת?" }, "useMasterPassword": { - "message": "Use master password" + "message": "השתמש בסיסמה ראשית" }, "usePin": { - "message": "Use PIN" + "message": "השתמש ב־PIN" }, "useBiometrics": { - "message": "Use biometrics" + "message": "השתמש בזיהוי ביומטרי" }, "enterVerificationCodeSentToEmail": { - "message": "Enter the verification code that was sent to your email." + "message": "הזן את קוד האימות שנשלח לדוא\"ל שלך." }, "resendCode": { - "message": "Resend code" + "message": "שלח קוד מחדש" }, "memberColumnHeader": { - "message": "Member" + "message": "חבר" }, "groupSlashMemberColumnHeader": { - "message": "Group/Member" + "message": "קבוצה/חבר" }, "selectGroupsAndMembers": { - "message": "Select groups and members" + "message": "בחר קבוצות וחברים" }, "selectGroups": { - "message": "Select groups" + "message": "בחר קבוצות" }, "userPermissionOverrideHelperDesc": { - "message": "Permissions set for a member will replace permissions set by that member's group." + "message": "הרשאות שהוגדרו עבור חבר יחליפו הרשאות שהוגדרו על ידי הקבוצה של אותו חבר." }, "noMembersOrGroupsAdded": { - "message": "No members or groups added" + "message": "לא נוספו חברים או קבוצות" }, "deleted": { - "message": "Deleted" + "message": "נמחקו" }, "memberStatusFilter": { - "message": "Member status filter" + "message": "מסנן מצב חבר" }, "inviteMember": { - "message": "Invite member" + "message": "הזמן חבר" }, "needsConfirmation": { - "message": "Needs confirmation" + "message": "צריך אישור" }, "memberRole": { - "message": "Member role" + "message": "תפקיד חבר" }, "moreFromBitwarden": { - "message": "More from Bitwarden" + "message": "עוד מאת Bitwarden" }, "switchProducts": { - "message": "Switch products" + "message": "החלף מוצרים" }, "freeOrgInvLimitReachedManageBilling": { - "message": "Free organizations may have up to $SEATCOUNT$ members. Upgrade to a paid plan to invite more members.", + "message": "לארגונים חינמיים יכולים להיות עד $SEATCOUNT$ חברים. שדרג לתוכנית בתשלום כדי להזמין חברים נוספים.", "placeholders": { "seatcount": { "content": "$1", @@ -7736,7 +8011,7 @@ } }, "freeOrgInvLimitReachedNoManageBilling": { - "message": "Free organizations may have up to $SEATCOUNT$ members. Contact your organization owner to upgrade.", + "message": "לארגונים חינמיים יכולים להיות עד $SEATCOUNT$ חברים. צור קשר עם בעלי הארגון שלך כדי לשדרג.", "placeholders": { "seatcount": { "content": "$1", @@ -7745,7 +8020,7 @@ } }, "teamsStarterPlanInvLimitReachedManageBilling": { - "message": "Teams Starter plans may have up to $SEATCOUNT$ members. Upgrade to your plan to invite more members.", + "message": "לתוכניות צוותים מתחילים יכולים להיות עד $SEATCOUNT$ חברים. שדרג את התוכנית שלך כדי להזמין עוד חברים.", "placeholders": { "seatcount": { "content": "$1", @@ -7754,7 +8029,7 @@ } }, "teamsStarterPlanInvLimitReachedNoManageBilling": { - "message": "Teams Starter plans may have up to $SEATCOUNT$ members. Contact your organization owner to upgrade your plan and invite more members.", + "message": "לתוכניות צוותים מתחילים יכולים להיות עד $SEATCOUNT$ חברים. צור קשר עם בעלי הארגון שלך כדי לשדרג את התוכנית שלך ולהזמין עוד חברים.", "placeholders": { "seatcount": { "content": "$1", @@ -7763,7 +8038,7 @@ } }, "freeOrgMaxCollectionReachedManageBilling": { - "message": "Free organizations may have up to $COLLECTIONCOUNT$ collections. Upgrade to a paid plan to add more collections.", + "message": "לארגונים חינמיים יכולים להיות עד $COLLECTIONCOUNT$ אוספים. שדרג לתוכנית בתשלום כדי להוסיף עוד אוספים.", "placeholders": { "COLLECTIONCOUNT": { "content": "$1", @@ -7772,7 +8047,7 @@ } }, "freeOrgMaxCollectionReachedNoManageBilling": { - "message": "Free organizations may have up to $COLLECTIONCOUNT$ collections. Contact your organization owner to upgrade.", + "message": "לארגונים חינמיים יכולים להיות עד $COLLECTIONCOUNT$ אוספים. צור קשר עם בעלי הארגון שלך כדי לשדרג.", "placeholders": { "COLLECTIONCOUNT": { "content": "$1", @@ -7781,16 +8056,16 @@ } }, "server": { - "message": "Server" + "message": "שרת" }, "exportData": { - "message": "Export data" + "message": "ייצא נתונים" }, "exportingOrganizationSecretDataTitle": { - "message": "Exporting Organization Secret Data" + "message": "ייצוא נתונים ארגון סודיים" }, "exportingOrganizationSecretDataDescription": { - "message": "Only the Secrets Manager data associated with $ORGANIZATION$ will be exported. Items in other products or from other organizations will not be included.", + "message": "רק נתוני מנהל הסודות המשויכים עם $ORGANIZATION$ ייוצאו. פריטים במוצרים אחרים או מארגונים אחרים לא יכללו.", "placeholders": { "ORGANIZATION": { "content": "$1", @@ -7799,61 +8074,61 @@ } }, "fileUpload": { - "message": "File upload" + "message": "העלאת קובץ" }, "upload": { - "message": "Upload" + "message": "העלה" }, "acceptedFormats": { - "message": "Accepted Formats:" + "message": "פורמטים מקובלים:" }, "copyPasteImportContents": { - "message": "Copy & paste import contents:" + "message": "העתק & הדבק תוכן ייבוא:" }, "or": { - "message": "or" + "message": "או" }, "unlockWithBiometrics": { - "message": "Unlock with biometrics" + "message": "בטל נעילה עם זיהוי ביומטרי" }, "unlockWithPin": { - "message": "Unlock with PIN" + "message": "בטל נעילה עם PIN" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "בטל נעילה עם סיסמה ראשית" }, "licenseAndBillingManagement": { - "message": "License and billing management" + "message": "ניהול רישיון וחיובים" }, "automaticSync": { - "message": "Automatic sync" + "message": "סנכרון אוטומטי" }, "manualUpload": { - "message": "Manual upload" + "message": "העלאה ידנית" }, "manualBillingTokenUploadDesc": { - "message": "If you do not want to opt into billing sync, manually upload your license here. This will not automatically unlock Families sponsorships." + "message": "אם אתה לא רוצה להסכים לסנכרון חיובים, העלה ידנית את הרישיון שלך כאן. זה לא יפתח באופן אוטומטי חסויות למשפחות." }, "syncLicense": { - "message": "Sync License" + "message": "סנכרן רישיון" }, "licenseSyncSuccess": { - "message": "Successfully synced license" + "message": "הרישיון סונכרן בהצלחה" }, "licenseUploadSuccess": { - "message": "Successfully uploaded license" + "message": "הרישיון הועלה בהצלחה" }, "lastLicenseSync": { - "message": "Last license sync" + "message": "סנכרון רישיון אחרון" }, "billingSyncHelp": { - "message": "Billing Sync help" + "message": "עזרה עם סנכרון חיובים" }, "licensePaidFeaturesHelp": { - "message": "License paid features help" + "message": "עזרה עם תכונות בתשלום של רישיון" }, "selfHostGracePeriodHelp": { - "message": "After your subscription expires, you have 60 days to apply an updated license file to your organization. Grace period ends $GRACE_PERIOD_END_DATE$.", + "message": "לאחר שהמנוי שלך יפוג, יש לך 60 ימים להחיל קובץ רישיון מעודכן לארגון שלך. תקופת חסד מסתיימת $GRACE_PERIOD_END_DATE$.", "placeholders": { "GRACE_PERIOD_END_DATE": { "content": "$1", @@ -7862,67 +8137,67 @@ } }, "uploadLicense": { - "message": "Upload license" + "message": "העלה רישיון" }, "projectPeopleDescription": { - "message": "Grant groups or people access to this project." + "message": "הענק לקבוצות או אנשים גישה לפרויקט זה." }, "projectPeopleSelectHint": { - "message": "Type or select people or groups" + "message": "הקלד או בחר אנשים או קבוצות" }, "projectServiceAccountsDescription": { - "message": "Grant service accounts access to this project." + "message": "הענק לחשבונות שירות גישה לפרויקט הזה." }, "projectServiceAccountsSelectHint": { - "message": "Type or select service accounts" + "message": "הקלד או בחר חשבונות שירות" }, "projectEmptyPeopleAccessPolicies": { - "message": "Add people or groups to start collaborating" + "message": "הוסף אנשים או קבוצות כדי להתחיל לשתף פעולה" }, "projectEmptyServiceAccountAccessPolicies": { - "message": "Add service accounts to grant access" + "message": "הוסף חשבונות שירות כדי להעניק גישה" }, "serviceAccountPeopleDescription": { - "message": "Grant groups or people access to this service account." + "message": "הענק לקבוצות או אנשים גישה לחשבון שירות זה." }, "serviceAccountProjectsDescription": { - "message": "Assign projects to this service account. " + "message": "הקצה פרויקטים לחשבון השירות הזה: " }, "serviceAccountEmptyProjectAccesspolicies": { - "message": "Add projects to grant access" + "message": "הוסף פרויקטים כדי להעניק גישה" }, "canReadWrite": { - "message": "Can read, write" + "message": "יכול/ה לקרוא, לכתוב" }, "groupSlashUser": { - "message": "Group/User" + "message": "קבוצה/משתמש" }, "lowKdfIterations": { - "message": "Low KDF Iterations" + "message": "חזרות KDF נמוכות" }, "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." + "message": "שדרג את הגדרות ההצפנה שלך כדי לעמוד בהמלצות אבטחה חדשות ולשפר את הגנת החשבון." }, "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": "המשך התהליך יוציא אותך מכל ההפעלות הפעילות שלך. תידרש להיכנס חזרה כדי להמשיך כניסה דו-שלבית, אם ישנה. אנו ממליצים על ייצוא הכספת שלך לפני שינוי הגדרות ההצפנה שלך כדי למנוע איבוד נתונים." }, "secretsManager": { - "message": "Secrets Manager" + "message": "מנהל הסודות" }, "secretsManagerAccessDescription": { - "message": "Activate user access to Secrets Manager." + "message": "הפעל גישת משתמש אל מנהל הסודות." }, "userAccessSecretsManagerGA": { - "message": "This user can access Secrets Manager" + "message": "משתמש זה יכול לגשת למנהל הסודות" }, "important": { - "message": "Important:" + "message": "חשוב:" }, "viewAll": { - "message": "View all" + "message": "הצג הכל" }, "showingPortionOfTotal": { - "message": "Showing $PORTION$ of $TOTAL$", + "message": "מראה $PORTION$ מתוך $TOTAL$", "placeholders": { "portion": { "content": "$1", @@ -7935,16 +8210,16 @@ } }, "resolveTheErrorsBelowAndTryAgain": { - "message": "Resolve the errors below and try again." + "message": "פתור את השגיאות למטה ונסה שוב." }, "description": { - "message": "Description" + "message": "תיאור" }, "errorReadingImportFile": { - "message": "An error occurred when trying to read the import file" + "message": "אירעה שגיאה בעת ניסיון לקרוא את קובץ הייבוא" }, "accessedSecret": { - "message": "Accessed secret $SECRET_ID$.", + "message": "ניגש אל סוד $SECRET_ID$.", "placeholders": { "secret_id": { "content": "$1", @@ -7957,32 +8232,32 @@ "description": "Software Development Kit" }, "createAnAccount": { - "message": "Create an account" + "message": "צור חשבון" }, "createSecret": { - "message": "Create a secret" + "message": "צור סוד" }, "createProject": { - "message": "Create a project" + "message": "צור פרויקט" }, "createServiceAccount": { - "message": "Create a service account" + "message": "צור חשבון שירות" }, "downloadThe": { - "message": "Download the", + "message": "הורד את", "description": "Link to a downloadable resource. This will be used as part of a larger phrase. Example: Download the Secrets Manager CLI" }, "smCLI": { - "message": "Secrets Manager CLI" + "message": "מנהל הסודות CLI" }, "importSecrets": { - "message": "Import secrets" + "message": "ייבא סודות" }, "getStarted": { - "message": "Get started" + "message": "התחל" }, "complete": { - "message": "$COMPLETED$/$TOTAL$ Complete", + "message": "$COMPLETED$ מתוך $TOTAL$ הושלמו", "placeholders": { "COMPLETED": { "content": "$1", @@ -7995,64 +8270,64 @@ } }, "restoreSecret": { - "message": "Restore secret" + "message": "שחזר סוד" }, "restoreSecrets": { - "message": "Restore secrets" + "message": "שחזר סודות" }, "restoreSecretPrompt": { - "message": "Are you sure you want to restore this secret?" + "message": "האם אתה בטוח שברצונך לשחזר את הסוד הזה?" }, "restoreSecretsPrompt": { - "message": "Are you sure you want to restore these secrets?" + "message": "האם אתה בטוח שברצונך לשחזר את הסודות האלה?" }, "secretRestoredSuccessToast": { - "message": "Secret restored" + "message": "הסוד שוחזר" }, "secretsRestoredSuccessToast": { - "message": "Secrets restored" + "message": "הסודות שוחזרו" }, "selectionIsRequired": { - "message": "Selection is required." + "message": "נדרשת בחירה." }, "saPeopleWarningTitle": { - "message": "Access tokens still available" + "message": "אסימוני גישה עדיין זמינים" }, "saPeopleWarningMessage": { - "message": "Removing people from a service account does not remove the access tokens they created. For security best practice, it is recommended to revoke access tokens created by people removed from a service account." + "message": "הסרת אנשים מחשבון שירות אינה מסירה את אסימוני הגישה שהם יצרו. עבור גישת האבטחה הטובה ביותר, מומלץ לבטל אסימוני גישה שנוצרו על ידי אנשים שהוסרו מחשבון שירות." }, "smAccessRemovalWarningProjectTitle": { - "message": "Remove access to this project" + "message": "הסר גישה לפרויקט זה" }, "smAccessRemovalWarningProjectMessage": { - "message": "This action will remove your access to the project." + "message": "פעולה זו תסיר את הגישה שלך לפרויקט זה." }, "smAccessRemovalWarningSaTitle": { - "message": "Remove access to this service account" + "message": "הסר גישה לחשבון שירות זה" }, "smAccessRemovalWarningSaMessage": { - "message": "This action will remove your access to the service account." + "message": "פעולה זו תסיר את הגישה שלך לחשבון השירות." }, "removeAccess": { - "message": "Remove access" + "message": "הסר גישה" }, "checkForBreaches": { - "message": "Check known data breaches for this password" + "message": "בדוק פרצות נתונים ידועות עבור סיסמה זו" }, "exposedMasterPassword": { - "message": "Exposed Master Password" + "message": "סיסמה ראשית חשופה" }, "exposedMasterPasswordDesc": { - "message": "Password found in a data breach. Use a unique password to protect your account. Are you sure you want to use an exposed password?" + "message": "הסיסמה נמצאה בפרצת נתונים. השתמש בסיסמה ייחודית כדי להגן על חשבונך. האם אתה בטוח שברצונך להשתמש בסיסמה חשופה?" }, "weakAndExposedMasterPassword": { - "message": "Weak and Exposed Master Password" + "message": "סיסמה ראשית חלשה וחשופה" }, "weakAndBreachedMasterPasswordDesc": { - "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" + "message": "סיסמה חלשה זוהתה ונמצאה בפרצת נתונים. השתמש בסיסמה חזקה וייחודית כדי להגן על חשבונך. האם אתה בטוח שאתה רוצה להשתמש בסיסמה זו?" }, "characterMinimum": { - "message": "$LENGTH$ character minimum", + "message": "$LENGTH$ תווים לכל הפחות", "placeholders": { "length": { "content": "$1", @@ -8061,7 +8336,7 @@ } }, "masterPasswordMinimumlength": { - "message": "Master password must be at least $LENGTH$ characters long.", + "message": "סיסמה ראשית חייבת להיות לפחות באורך $LENGTH$ תווים.", "placeholders": { "length": { "content": "$1", @@ -8070,107 +8345,104 @@ } }, "inputTrimValidator": { - "message": "Input must not contain only whitespace.", + "message": "אסור שקלט יכיל רק רווח לבן.", "description": "Notification to inform the user that a form's input can't contain only whitespace." }, "dismiss": { - "message": "Dismiss" + "message": "התעלם" }, "notAvailableForFreeOrganization": { - "message": "This feature is not available for free organizations. Contact your organization owner to upgrade." + "message": "תכונה זו אינה זמינה עבור ארגונים חינמיים. צור קשר עם בעלי הארגון שלך כדי לשדרג." }, "smProjectSecretsNoItemsNoAccess": { - "message": "Contact your organization's admin to manage secrets for this project.", + "message": "צור קשר עם מנהל הארגון שלך כדי לנהל סודות עבור פרויקט זה.", "description": "The message shown to the user under a project's secrets tab when the user only has read access to the project." }, "enforceOnLoginDesc": { - "message": "Require existing members to change their passwords" + "message": "דרוש מחברים קיימים לשנות את הסיסמאות שלהם" }, "smProjectDeleteAccessRestricted": { - "message": "You don't have permissions to delete this project", + "message": "אין לך הרשאות למחוק את הפרויקט הזה", "description": "The individual description shown to the user when the user doesn't have access to delete a project." }, "smProjectsDeleteBulkConfirmation": { - "message": "The following projects can not be deleted. Would you like to continue?", + "message": "הפרויקטים הבאים אינם ניתנים למחיקה. האם ברצונך להמשיך?", "description": "The message shown to the user when bulk deleting projects and the user doesn't have access to some projects." }, "updateKdfSettings": { - "message": "Update KDF settings" + "message": "עדכן הגדרות KDF" }, "loginInitiated": { - "message": "Login initiated" + "message": "הכניסה החלה" }, "rememberThisDeviceToMakeFutureLoginsSeamless": { - "message": "Remember this device to make future logins seamless" + "message": "זכור מכשיר זה כדי להפוך כניסות עתידיות לחלקות" }, "deviceApprovalRequired": { - "message": "Device approval required. Select an approval option below:" + "message": "נדרש אישור מכשיר. בחר אפשרות אישור למטה:" }, "deviceApprovalRequiredV2": { - "message": "Device approval required" + "message": "נדרש אישור מכשיר" }, "selectAnApprovalOptionBelow": { - "message": "Select an approval option below" + "message": "בחר אפשרות אישור למטה" }, "rememberThisDevice": { - "message": "Remember this device" + "message": "זכור מכשיר זה" }, "uncheckIfPublicDevice": { - "message": "Uncheck if using a public device" + "message": "בטל את הסימון אם אתה משתמש במכשיר ציבורי" }, "approveFromYourOtherDevice": { - "message": "Approve from your other device" + "message": "אשר מהמכשיר האחר שלך" }, "requestAdminApproval": { - "message": "Request admin approval" - }, - "approveWithMasterPassword": { - "message": "Approve with master password" + "message": "בקש אישור מנהל" }, "trustedDeviceEncryption": { - "message": "Trusted device encryption" + "message": "הצפנת מכשיר מהימן" }, "trustedDevices": { - "message": "Trusted devices" + "message": "מכשירים מהימנים" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "חברים לא יצטרכו סיסמה ראשית בעת כניסה עם SSO. סיסמה ראשית מוחלפת עם מפתח הצפנה המאוחסן במכשיר, מה שהופך את המכשיר הזה למהימן. המכשיר הראשון בו חבר יוצר את החשבון שלו ונכנס אליו יהיה מהימן. מכשירים חדשים יצטרכו להיות מאושרים על ידי מכשיר מהימן קיים או על ידי מנהל.", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "מדיניות דרוש SSO", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "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.'" }, "orgPermissionsUpdatedMustSetPassword": { - "message": "Your organization permissions were updated, requiring you to set a master password.", + "message": "הרשאות הארגון שלך עודכנו, מה שמאלץ אותך להגדיר סיסמה ראשית.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "orgRequiresYouToSetPassword": { - "message": "Your organization requires you to set a master password.", + "message": "הארגון שלך דורש ממך להגדיר סיסמה ראשית.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "מתוך $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -8179,7 +8451,7 @@ } }, "notFound": { - "message": "$RESOURCE$ not found", + "message": "$RESOURCE$ לא נמצא", "placeholders": { "resource": { "content": "$1", @@ -8188,74 +8460,86 @@ } }, "verificationRequired": { - "message": "Verification required", + "message": "נדרש אימות", "description": "Default title for the user verification dialog." }, "recoverAccount": { - "message": "Recover account" + "message": "שחזר חשבון" }, "updatedTempPassword": { - "message": "User updated a password issued through account recovery." + "message": "משתמש עדכן סיסמה שהונפקה באמצעות שחזור חשבון." }, "activatedAccessToSecretsManager": { - "message": "Activated access to Secrets Manager", + "message": "הופעלה גישה למנהל הסודות", "description": "Confirmation message that one or more users gained access to Secrets Manager" }, "activateAccess": { - "message": "Activate access" + "message": "הפעל גישה" }, "bulkEnableSecretsManagerDescription": { - "message": "Grant the following members access to Secrets Manager. The role granted in the Password Manager will apply to Secrets Manager.", + "message": "הענק לחברים הבאים גישה למנהל הסודות. התפקיד שהוענק במנהל הסיסמאות יחול על מנהל הסודות.", "description": "This description is shown to an admin when they are attempting to add more users to Secrets Manager." }, "activateSecretsManager": { - "message": "Activate Secrets Manager" + "message": "הפעל את מנהל הסודות" }, "yourOrganizationsFingerprint": { - "message": "Your organization's fingerprint phrase", + "message": "ביטוי טביעת האצבע של ארגונך", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their organization's public key with another user, for the purposes of sharing." }, "deviceApprovals": { - "message": "Device approvals" + "message": "אישורי מכשירים" }, "deviceApprovalsDesc": { - "message": "Approve login requests below to allow the requesting member to finish logging in. Unapproved requests expire after 1 week. Verify the member’s information before approving." + "message": "אשר בקשות כניסה למטה כדי לאפשר לחבר המבקש לסיים להיכנס. בקשות לא מאושרות יפוגו לאחר שבוע אחד. אמת את המידע של החבר לפני שתאשר." }, "deviceInfo": { - "message": "Device info" + "message": "מידע על המכשיר" }, "time": { - "message": "Time" + "message": "זמן" }, "denyAllRequests": { - "message": "Deny all requests" + "message": "דחה את כל הבקשות" }, "denyRequest": { - "message": "Deny request" + "message": "דחה בקשה" }, "approveRequest": { - "message": "Approve request" + "message": "אשר בקשה" + }, + "deviceApproved": { + "message": "המכשיר אושר" + }, + "deviceRemoved": { + "message": "המכשיר הוסר" + }, + "removeDevice": { + "message": "הסר מכשיר" + }, + "removeDeviceConfirmation": { + "message": "האם אתה בטוח שברצונך להסיר מכשיר זה?" }, "noDeviceRequests": { - "message": "No device requests" + "message": "אין בקשות של מכשירים" }, "noDeviceRequestsDesc": { - "message": "Member device approval requests will appear here" + "message": "בקשות לאישור מכשיר של חבר יופיעו כאן" }, "loginRequestDenied": { - "message": "Login request denied" + "message": "בקשת כניסה נדחתה" }, "allLoginRequestsDenied": { - "message": "All login requests denied" + "message": "כל בקשות הכניסה נדחו" }, "loginRequestApproved": { - "message": "Login request approved" + "message": "בקשת כניסה אושרה" }, "removeOrgUserNoMasterPasswordTitle": { - "message": "Account does not have master password" + "message": "לחשבון אין סיסמה ראשית" }, "removeOrgUserNoMasterPasswordDesc": { - "message": "Removing $USER$ without setting a master password for them may restrict access to their full account. Are you sure you want to continue?", + "message": "הסרת $USER$ מבלי להגדיר סיסמה ראשית עבורו עשויה להגביל גישה לחשבון המלא שלו. האם אתה בטוח שברצונך להמשיך?", "placeholders": { "user": { "content": "$1", @@ -8264,13 +8548,13 @@ } }, "noMasterPassword": { - "message": "No master password" + "message": "אין סיסמה ראשית" }, "removeMembersWithoutMasterPasswordWarning": { - "message": "Removing members who do not have master passwords without setting one for them may restrict access to their full account." + "message": "הסרת חברים שאין להם סיסמאות ראשיות מבלי להגדיר אחת עבורם עשויה להגביל גישה לחשבון המלא שלהם." }, "approvedAuthRequest": { - "message": "Approved device for $ID$.", + "message": "מכשיר אושר עבור $ID$.", "placeholders": { "id": { "content": "$1", @@ -8279,7 +8563,7 @@ } }, "rejectedAuthRequest": { - "message": "Denied device for $ID$.", + "message": "מכשיר נדחה עבור $ID$.", "placeholders": { "id": { "content": "$1", @@ -8288,13 +8572,13 @@ } }, "requestedDeviceApproval": { - "message": "Requested device approval." + "message": "התבקש אישור מכשיר." }, "tdeOffboardingPasswordSet": { - "message": "User set a master password during TDE offboarding." + "message": "המשתמש הגדיר סיסמה ראשית במהלך תהליך יציאת TDE." }, "startYour7DayFreeTrialOfBitwardenFor": { - "message": "Start your 7-Day free trial of Bitwarden for $ORG$", + "message": "התחל את תקופת הניסיון בחינם למשך 7 יום של Bitwarden עבור $ORG$", "placeholders": { "org": { "content": "$1", @@ -8303,7 +8587,7 @@ } }, "startYour7DayFreeTrialOfBitwardenSecretsManagerFor": { - "message": "Start your 7-Day free trial of Bitwarden Secrets Manager for $ORG$", + "message": "התחל את תקופת הניסיון בחינם למשך 7 יום של מנהל הסודות של Bitwarden עבור $ORG$", "placeholders": { "org": { "content": "$1", @@ -8312,54 +8596,51 @@ } }, "next": { - "message": "Next" + "message": "הבא" }, "ssoLoginIsRequired": { - "message": "SSO login is required" + "message": "נדרשת כניסת SSO" }, "selectedRegionFlag": { - "message": "Selected region flag" + "message": "דגל האזור שנבחר" }, "accountSuccessfullyCreated": { - "message": "Account successfully created!" + "message": "החשבון נוצר בהצלחה!" }, "adminApprovalRequested": { - "message": "Admin approval requested" + "message": "התבקש אישור מנהל" }, "adminApprovalRequestSentToAdmins": { - "message": "Your request has been sent to your admin." - }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." + "message": "הבקשה שלך נשלחה למנהל שלך." }, "troubleLoggingIn": { - "message": "Trouble logging in?" + "message": "בעיות בכניסה?" }, "loginApproved": { - "message": "Login approved" + "message": "כניסה אושרה" }, "userEmailMissing": { - "message": "User email missing" + "message": "חסר דוא\"ל משתמש" }, "activeUserEmailNotFoundLoggingYouOut": { - "message": "Active user email not found. Logging you out." + "message": "דוא\"ל משתמש פעיל לא נמצא. מוציא אותך." }, "deviceTrusted": { - "message": "Device trusted" + "message": "מכשיר מהימן" }, "sendsNoItemsTitle": { - "message": "No active Sends", + "message": "אין סֵנְדים פעילים", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "message": "השתמש בסֵנְד כדי לשתף באופן מאובטח מידע מוצפן עם כל אחד.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inviteUsers": { - "message": "Invite Users" + "message": "הזמן משתמשים" }, "secretsManagerForPlan": { - "message": "Secrets Manager for $PLAN$", + "message": "מנהל הסודות עבור $PLAN$", "placeholders": { "plan": { "content": "$1", @@ -8368,19 +8649,19 @@ } }, "secretsManagerForPlanDesc": { - "message": "For engineering and DevOps teams to manage secrets throughout the software development lifecycle." + "message": "עבור צוותי הנדסה ו-DevOps כדי שיוכלו לנהל סודות לאורך כל מחזור החיים של פיתוח התוכנה." }, "free2PersonOrganization": { - "message": "Free 2-person Organizations" + "message": "ארגונים של 2 אנשים בחינם" }, "unlimitedSecrets": { - "message": "Unlimited secrets" + "message": "סודות ללא הגבלה" }, "unlimitedProjects": { - "message": "Unlimited projects" + "message": "פרויקטים ללא הגבלה" }, "projectsIncluded": { - "message": "$COUNT$ projects included", + "message": "$COUNT$ פרויקטים כלולים", "placeholders": { "count": { "content": "$1", @@ -8389,7 +8670,7 @@ } }, "serviceAccountsIncluded": { - "message": "$COUNT$ service accounts included", + "message": "$COUNT$ חשבונות שירות כלולים", "placeholders": { "count": { "content": "$1", @@ -8398,7 +8679,7 @@ } }, "additionalServiceAccountCost": { - "message": "$COST$ per month for additional service accounts", + "message": "$COST$ לחודש עבור חשבונות שירות נוספים", "placeholders": { "cost": { "content": "$1", @@ -8407,16 +8688,16 @@ } }, "subscribeToSecretsManager": { - "message": "Subscribe to Secrets Manager" + "message": "הירשם כמנוי למנהל הסודות" }, "addSecretsManagerUpgradeDesc": { - "message": "Add Secrets Manager to your upgraded plan to maintain access to any secrets created with your previous plan." + "message": "הוסף את מנהל הסודות לתוכנית המשודרגת שלך כדי לשמור על גישה לכל הסודות שנוצרו עם התוכנית הקודמת שלך." }, "additionalServiceAccounts": { - "message": "Additional service accounts" + "message": "חשבונות שירות נוספים" }, "includedServiceAccounts": { - "message": "Your plan comes with $COUNT$ service accounts.", + "message": "התוכנית שלך מגיעה עם $COUNT$ חשבונות שירות.", "placeholders": { "count": { "content": "$1", @@ -8425,7 +8706,7 @@ } }, "addAdditionalServiceAccounts": { - "message": "You can add additional service accounts for $COST$ per month.", + "message": "אתה יכול להוסיף חשבונות שירות נוספים עבור $COST$ לחודש.", "placeholders": { "cost": { "content": "$1", @@ -8434,92 +8715,92 @@ } }, "collectionManagement": { - "message": "Collection management" + "message": "ניהול אוספים" }, "collectionManagementDesc": { - "message": "Manage the collection behavior for the organization" + "message": "נהל את התנהגות האוספים עבור הארגון" }, "limitCollectionCreationDesc": { - "message": "Limit collection creation to owners and admins" + "message": "הגבל יצירת אוספים לבעלים ומנהלים" }, "limitCollectionDeletionDesc": { - "message": "Limit collection deletion to owners and admins" + "message": "הגבל מחיקת אוספים לבעלים ומנהלים" + }, + "limitItemDeletionDesc": { + "message": "הגבל מחיקת פריטים לחברים עם הרשאת יכולת ניהול" }, "allowAdminAccessToAllCollectionItemsDesc": { - "message": "Owners and admins can manage all collections and items" + "message": "בעלים ומנהלים יכולים לנהל את כל האוספים והפריטים" }, "updatedCollectionManagement": { - "message": "Updated collection management setting" + "message": "הגדרת ניהול אוספים עודכנה" }, "passwordManagerPlanPrice": { - "message": "Password Manager plan price" + "message": "מחיר תוכנית מנהל הסיסמאות" }, "secretsManagerPlanPrice": { - "message": "Secrets Manager plan price" + "message": "מחיר תוכנית מנהל הסודות" }, "passwordManager": { - "message": "Password Manager" + "message": "מנהל הסיסמאות" }, "freeOrganization": { - "message": "Free Organization" + "message": "ארגון חינמי" }, "limitServiceAccounts": { - "message": "Limit service accounts (optional)" + "message": "הגבל חשבונות שירות (אופציונלי)" }, "limitServiceAccountsDesc": { - "message": "Set a limit for your service accounts. Once this limit is reached, you will not be able to create new service accounts." + "message": "הגדר מגבלה עבור חשבונות השירות שלך. ברגע שתגיע למגבלה זו, לא תוכל ליצור חשבונות שירות חדשים." }, "serviceAccountLimit": { - "message": "Service account limit (optional)" + "message": "מגבלת חשבונות שירות (אופציונלי)" }, "maxServiceAccountCost": { - "message": "Max potential service account cost" + "message": "עלות מרבית פוטנציאלית של חשבון שירות" }, "loggedInExclamation": { - "message": "Logged in!" + "message": "נכנסת!" }, "beta": { - "message": "Beta" + "message": "בטא" }, "assignCollectionAccess": { - "message": "Assign collection access" + "message": "הקצה גישה לאוסף" }, "editedCollections": { - "message": "Edited collections" + "message": "אוספים שנערכו" }, "baseUrl": { - "message": "Server URL" + "message": "URL של שרת" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "URL של שרת אירוח עצמי", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domain" - }, "alreadyHaveAccount": { - "message": "Already have an account?" + "message": "כבר יש לך חשבון?" }, "toggleSideNavigation": { - "message": "Toggle side navigation" + "message": "החלף מצב ניווט צדדי" }, "skipToContent": { - "message": "Skip to content" + "message": "דלג לתוכן" }, "managePermissionRequired": { - "message": "At least one member or group must have can manage permission." + "message": "לפחות חבר אחד או קבוצה אחת חייבים להיות בעלי הרשאת יכולת ניהול." }, "typePasskey": { - "message": "Passkey" + "message": "מפתח גישה" }, "passkeyNotCopied": { - "message": "Passkey will not be copied" + "message": "מפתח גישה לא יועתק" }, "passkeyNotCopiedAlert": { - "message": "The passkey will not be copied to the cloned item. Do you want to continue cloning this item?" + "message": "מפתח הגישה לא יועתק לפריט המשוכפל. האם אתה רוצה להמשיך לשכפל פריט זה?" }, "modifiedCollectionManagement": { - "message": "Modified collection management setting $ID$.", + "message": "שונתה הגדרת ניהול אוספים $ID$.", "placeholders": { "id": { "content": "$1", @@ -8528,60 +8809,60 @@ } }, "seeDetailedInstructions": { - "message": "See detailed instructions on our help site at", + "message": "ראה הוראות מפורטות באתר העזרה שלנו ב-", "description": "This is followed a by a hyperlink to the help website." }, "installBrowserExtension": { - "message": "Install browser extension" + "message": "התקן הרחבת דפדפן" }, "installBrowserExtensionDetails": { - "message": "Use the extension to quickly save logins and auto-fill forms without opening the web app." + "message": "השתמש בהרחבה כדי לשמור כניסות ולמלא אוטומטית טפסים במהירות מבלי לפתוח את יישום הרשת." }, "projectAccessUpdated": { - "message": "Project access updated" + "message": "גישת פרויקט עודכנה" }, "unexpectedErrorSend": { - "message": "An unexpected error has occurred while loading this Send. Try again later." + "message": "אירעה שגיאה בלתי צפויה בעת טעינת סֵנְד זה. נסה שוב מאוחר יותר." }, "seatLimitReached": { - "message": "Seat limit has been reached" + "message": "מגבלת מקום הושגה" }, "contactYourProvider": { - "message": "Contact your provider to purchase additional seats." + "message": "צור קשר עם הספק שלך כדי לרכוש מקומות נוספים." }, "seatLimitReachedContactYourProvider": { - "message": "Seat limit has been reached. Contact your provider to purchase additional seats." + "message": "מגבלת מקום הושגה. צור קשר עם הספק שלך כדי לרכוש מקומות נוספים." }, "collectionAccessRestricted": { - "message": "Collection access is restricted" + "message": "הגישה לאוסף מוגבלת" }, "readOnlyCollectionAccess": { - "message": "You do not have access to manage this collection." + "message": "אין לך גישה לנהל את האוסף הזה." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "חסרות הרשאות ניהול אוסף" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "הענק הרשאות ניהול אוסף כדי לאפשר ניהול אוסף מלא כולל מחיקה של אוסף." }, "grantCollectionAccess": { - "message": "Grant groups or members access to this collection." + "message": "הענק לקבוצות או חברים גישה לאוסף זה." }, "grantCollectionAccessMembersOnly": { - "message": "Grant members access to this collection." + "message": "הענק לחברים גישה לאוסף זה." }, "adminCollectionAccess": { - "message": "Administrators can access and manage collections." + "message": "מנהלים יכולים לגשת ולנהל אוספים." }, "serviceAccountAccessUpdated": { - "message": "Service account access updated" + "message": "גישת חשבון השירות עודכנה" }, "commonImportFormats": { - "message": "Common formats", + "message": "פורמטים נפוצים", "description": "Label indicating the most common import formats" }, "maintainYourSubscription": { - "message": "To maintain your subscription for $ORG$, ", + "message": "כדי לשמור על המנוי שלך עבור$ORG$, ", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'", "placeholders": { "org": { @@ -8591,103 +8872,103 @@ } }, "addAPaymentMethod": { - "message": "add a payment method", + "message": "הוסף אמצעי תשלום", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { - "message": "Organization information" + "message": "מידע על הארגון" }, "confirmationDetails": { - "message": "Confirmation details" + "message": "פרטי אישור" }, "smFreeTrialThankYou": { - "message": "Thank you for signing up for Bitwarden Secrets Manager!" + "message": "תודה לך שנרשמת למנהל הסודות של Bitwarden!" }, "smFreeTrialConfirmationEmail": { - "message": "We've sent a confirmation email to your email at " + "message": "שלחנו דוא\"ל אימות לדוא\"ל שלך ב־" }, "sorryToSeeYouGo": { - "message": "Sorry to see you go! Help improve Bitwarden by sharing why you're canceling.", + "message": "מצטערים לראות אותך הולך! עזור לשפר את Bitwarden על ידי שיתוף הסיבה לביטול.", "description": "A message shown to users as part of an offboarding survey asking them to provide more information on their subscription cancelation." }, "selectCancellationReason": { - "message": "Select a reason for canceling", + "message": "בחר סיבה לביטול", "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": "האם יש משוב אחר כלשהו שתרצה לשתף?", "description": "Used as a form field label for a textarea input on the offboarding survey." }, "missingFeatures": { - "message": "Missing features", + "message": "תכונות חסרות", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "movingToAnotherTool": { - "message": "Moving to another tool", + "message": "עובר לכלי אחר", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "tooDifficultToUse": { - "message": "Too difficult to use", + "message": "קשה מדי לשימוש", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "notUsingEnough": { - "message": "Not using enough", + "message": "לא משתמש מספיק", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "tooExpensive": { - "message": "Too expensive", + "message": "יקר מדי", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "freeForOneYear": { - "message": "Free for 1 year" + "message": "חינם לשנה אחת" }, "newWebApp": { - "message": "Welcome to the new and improved web app. Learn more about what’s changed." + "message": "ברוך הבא ליישום הרשת החדש והמשופר. למד עוד על מה שהשתנה." }, "releaseBlog": { - "message": "Read release blog" + "message": "קרא בלוג שחרור" }, "adminConsole": { - "message": "Admin Console" + "message": "מסוף מנהל" }, "providerPortal": { - "message": "Provider Portal" + "message": "פורטל ספקים" }, "success": { - "message": "Success" + "message": "הצלחה" }, "restrictedGroupAccess": { - "message": "You cannot add yourself to groups." + "message": "אתה לא יכול להוסיף את עצמך לקבוצות." }, "cannotAddYourselfToCollections": { - "message": "You cannot add yourself to collections." + "message": "אתה לא יכול להוסיף את עצמך לאוספים." }, "assign": { - "message": "Assign" + "message": "הקצה" }, "assignToCollections": { - "message": "Assign to collections" + "message": "הקצה לאוספים" }, "assignToTheseCollections": { - "message": "Assign to these collections" + "message": "הקצה לאוספים אלה" }, "bulkCollectionAssignmentDialogDescriptionSingular": { - "message": "Only organization members with access to these collections will be able to see the item." + "message": "רק חברי ארגון עם גישה לאוספים אלה יוכלו לראות את הפריט." }, "bulkCollectionAssignmentDialogDescriptionPlural": { - "message": "Only organization members with access to these collections will be able to see the items." + "message": "רק חברי ארגון עם גישה לאוספים אלה יוכלו לראות את הפריטים." }, "selectCollectionsToAssign": { - "message": "Select collections to assign" + "message": "בחר אוספים להקצות" }, "noCollectionsAssigned": { - "message": "No collections have been assigned" + "message": "לא הוקצו אוספים" }, "successfullyAssignedCollections": { - "message": "Successfully assigned collections" + "message": "אוספים הוקצו בהצלחה" }, "bulkCollectionAssignmentWarning": { - "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "message": "בחרת ב־$TOTAL_COUNT$ פריטים. אינך יכול לעדכן $READONLY_COUNT$ מהפריטים בגלל שאין לך הרשאות עריכה.", "placeholders": { "total_count": { "content": "$1", @@ -8700,61 +8981,61 @@ } }, "addField": { - "message": "Add field" + "message": "הוסף שדה" }, "editField": { - "message": "Edit field" + "message": "ערוך שדה" }, "items": { - "message": "Items" + "message": " פריטים " }, "assignedSeats": { - "message": "Assigned seats" + "message": "מקומות שהוקצו" }, "assigned": { - "message": "Assigned" + "message": "הוקצו" }, "used": { - "message": "Used" + "message": "בשימוש" }, "remaining": { - "message": "Remaining" + "message": "נותרו" }, "unlinkOrganization": { - "message": "Unlink organization" + "message": "בטל קישור לארגון" }, "manageSeats": { - "message": "MANAGE SEATS" + "message": "נהל מקומות" }, "manageSeatsDescription": { - "message": "Adjustments to seats will be reflected in the next billing cycle." + "message": "התאמות למקומות ישתקפו במחזור החיוב הבא." }, "unassignedSeatsDescription": { - "message": "Unassigned seats" + "message": "מקומות לא מוקצים" }, "purchaseSeatDescription": { - "message": "Additional seats purchased" + "message": "מקומות נוספים נרכשו" }, "assignedSeatCannotUpdate": { - "message": "Assigned Seats can not be updated. Please contact your organization owner for assistance." + "message": "לא ניתן לעדכן מקומות מוקצים. נא ליצור קשר עם בעלי הארגון שלך עבור סיוע." }, "subscriptionUpdateFailed": { - "message": "Subscription update failed" + "message": "עדכון מנוי נכשל" }, "trial": { - "message": "Trial", + "message": "ניסיון", "description": "A subscription status label." }, "pastDue": { - "message": "Past due", + "message": "עבר את המועד", "description": "A subscription status label" }, "subscriptionExpired": { - "message": "Subscription expired", + "message": "פג תוקף המנוי", "description": "The date header used when a subscription is past due." }, "pastDueWarningForChargeAutomatically": { - "message": "You have a grace period of $DAYS$ days from your subscription expiration date to maintain your subscription. Please resolve the past due invoices by $SUSPENSION_DATE$.", + "message": "יש לך תקופת חסד של $DAYS$ ימים מתאריך תפוגת המנוי שלך כדי לשמור על המנוי שלך. נא ליישב את החשבוניות שמועד פירעונן עבר עד $SUSPENSION_DATE$.", "placeholders": { "days": { "content": "$1", @@ -8768,7 +9049,7 @@ "description": "A warning shown to the user when their subscription is past due and they are charged automatically." }, "pastDueWarningForSendInvoice": { - "message": "You have a grace period of $DAYS$ days from the date your first unpaid invoice is due to maintain your subscription. Please resolve the past due invoices by $SUSPENSION_DATE$.", + "message": "יש לך תקופת חסד של $DAYS$ ימים מתאריך הפירעון של החשבונית הראשונה שלך שלא שולמה כדי לשמור על המנוי שלך. נא ליישב את החשבוניות שמועד פירעונן עבר עד $SUSPENSION_DATE$.", "placeholders": { "days": { "content": "$1", @@ -8782,54 +9063,54 @@ "description": "A warning shown to the user when their subscription is past due and they pay via invoice." }, "unpaidInvoice": { - "message": "Unpaid invoice", + "message": "חשבונית שלא שולמה", "description": "The header of a warning box shown to a user whose subscription is unpaid." }, "toReactivateYourSubscription": { - "message": "To reactivate your subscription, please resolve the past due invoices.", + "message": "כדי להפעיל מחדש את המנוי שלך, נא ליישב את החשבוניות שמועד פירעונן עבר.", "description": "The body of a warning box shown to a user whose subscription is unpaid." }, "cancellationDate": { - "message": "Cancellation date", + "message": "תאריך ביטול", "description": "The date header used when a subscription is cancelled." }, "machineAccountsCannotCreate": { - "message": "Machine accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." + "message": "לא ניתן ליצור חשבונות מכונה בארגונים מושעים. נא ליצור קשר עם בעלי הארגון שלך עבור סיוע." }, "machineAccount": { - "message": "Machine account", + "message": "חשבון מכונה", "description": "A machine user which can be used to automate processes and access secrets in the system." }, "machineAccounts": { - "message": "Machine accounts", + "message": "חשבונות מכונה", "description": "The title for the section that deals with machine accounts." }, "newMachineAccount": { - "message": "New machine account", + "message": "חשבון מכונה חדש", "description": "Title for creating a new machine account." }, "machineAccountsNoItemsMessage": { - "message": "Create a new machine account to get started automating secret access.", + "message": "צור חשבון מכונה חדש כדי להתחיל לאטמט גישת סודות.", "description": "Message to encourage the user to start creating machine accounts." }, "machineAccountsNoItemsTitle": { - "message": "Nothing to show yet", + "message": "אין מה להראות עדיין", "description": "Title to indicate that there are no machine accounts to display." }, "deleteMachineAccounts": { - "message": "Delete machine accounts", + "message": "מחק חשבונות מכונה", "description": "Title for the action to delete one or multiple machine accounts." }, "deleteMachineAccount": { - "message": "Delete machine account", + "message": "מחק חשבון מכונה", "description": "Title for the action to delete a single machine account." }, "viewMachineAccount": { - "message": "View machine account", + "message": "הצג חשבון מכונה", "description": "Action to view the details of a machine account." }, "deleteMachineAccountDialogMessage": { - "message": "Deleting machine account $MACHINE_ACCOUNT$ is permanent and irreversible.", + "message": "מחיקת חשבון מכונה $MACHINE_ACCOUNT$ היא לצמיתות ובלתי הפיכה.", "placeholders": { "machine_account": { "content": "$1", @@ -8838,10 +9119,10 @@ } }, "deleteMachineAccountsDialogMessage": { - "message": "Deleting machine accounts is permanent and irreversible." + "message": "מחיקת חשבונות מכונה היא לצמיתות ובלתי הפיכה." }, "deleteMachineAccountsConfirmMessage": { - "message": "Delete $COUNT$ machine accounts", + "message": "מחק $COUNT$ חשבונות מכונה", "placeholders": { "count": { "content": "$1", @@ -8850,60 +9131,60 @@ } }, "deleteMachineAccountToast": { - "message": "Machine account deleted" + "message": "חשבון מכונה נמחק" }, "deleteMachineAccountsToast": { - "message": "Machine accounts deleted" + "message": "חשבונות מכונה נמחקו" }, "searchMachineAccounts": { - "message": "Search machine accounts", + "message": "חפש חשבונות מכונה", "description": "Placeholder text for searching machine accounts." }, "editMachineAccount": { - "message": "Edit machine account", + "message": "ערוך חשבון מכונה", "description": "Title for editing a machine account." }, "machineAccountName": { - "message": "Machine account name", + "message": "שם חשבון מכונה", "description": "Label for the name of a machine account" }, "machineAccountCreated": { - "message": "Machine account created", + "message": "חשבון מכונה נוצר", "description": "Notifies that a new machine account has been created" }, "machineAccountUpdated": { - "message": "Machine account updated", + "message": "חשבון מכונה עודכן", "description": "Notifies that a machine account has been updated" }, "projectMachineAccountsDescription": { - "message": "Grant machine accounts access to this project." + "message": "הענק לחשבונות מכונה גישה לפרויקט הזה." }, "projectMachineAccountsSelectHint": { - "message": "Type or select machine accounts" + "message": "הקלד או בחר חשבונות מכונה" }, "projectEmptyMachineAccountAccessPolicies": { - "message": "Add machine accounts to grant access" + "message": "הוסף חשבונות מכונה כדי להעניק גישה" }, "machineAccountPeopleDescription": { - "message": "Grant groups or people access to this machine account." + "message": "הענק לקבוצות או אנשים גישה לחשבון מכונה זה." }, "machineAccountProjectsDescription": { - "message": "Assign projects to this machine account. " + "message": "הקצה פרויקטים לחשבון המכונה הזה: " }, "createMachineAccount": { - "message": "Create a machine account" + "message": "צור חשבון מכונה" }, "maPeopleWarningMessage": { - "message": "Removing people from a machine account does not remove the access tokens they created. For security best practice, it is recommended to revoke access tokens created by people removed from a machine account." + "message": "הסרת אנשים מחשבון מכונה אינה מסירה את אסימוני הגישה שהם יצרו. עבור גישת האבטחה הטובה ביותר, מומלץ לבטל אסימוני גישה שנוצרו על ידי אנשים שהוסרו מחשבון מכונה." }, "smAccessRemovalWarningMaTitle": { - "message": "Remove access to this machine account" + "message": "הסר גישה לחשבון מכונה זה" }, "smAccessRemovalWarningMaMessage": { - "message": "This action will remove your access to the machine account." + "message": "פעולה זו תסיר את הגישה שלך לחשבון המכונה." }, "machineAccountsIncluded": { - "message": "$COUNT$ machine accounts included", + "message": "$COUNT$ חשבונות מכונה כלולים", "placeholders": { "count": { "content": "$1", @@ -8912,7 +9193,7 @@ } }, "additionalMachineAccountCost": { - "message": "$COST$ per month for additional machine accounts", + "message": "$COST$ לחודש עבור חשבונות מכונה נוספים", "placeholders": { "cost": { "content": "$1", @@ -8921,10 +9202,10 @@ } }, "additionalMachineAccounts": { - "message": "Additional machine accounts" + "message": "חשבונות מכונה נוספים" }, "includedMachineAccounts": { - "message": "Your plan comes with $COUNT$ machine accounts.", + "message": "התוכנית שלך מגיעה עם $COUNT$ חשבונות מכונה.", "placeholders": { "count": { "content": "$1", @@ -8933,7 +9214,7 @@ } }, "addAdditionalMachineAccounts": { - "message": "You can add additional machine accounts for $COST$ per month.", + "message": "אתה יכול להוסיף חשבונות מכונה נוספים עבור $COST$ לחודש.", "placeholders": { "cost": { "content": "$1", @@ -8942,31 +9223,31 @@ } }, "limitMachineAccounts": { - "message": "Limit machine accounts (optional)" + "message": "הגבל חשבונות מכונה (אופציונלי)" }, "limitMachineAccountsDesc": { - "message": "Set a limit for your machine accounts. Once this limit is reached, you will not be able to create new machine accounts." + "message": "הגדר מגבלה עבור חשבונות המכונה שלך. ברגע שתגיע למגבלה זו, לא תוכל ליצור חשבונות מכונה חדשים." }, "machineAccountLimit": { - "message": "Machine account limit (optional)" + "message": "מגבלת חשבונות מכונה (אופציונלי)" }, "maxMachineAccountCost": { - "message": "Max potential machine account cost" + "message": "עלות מרבית פוטנציאלית של חשבון מכונה" }, "machineAccountAccessUpdated": { - "message": "Machine account access updated" + "message": "גישת חשבון המכונה עודכנה" }, "restrictedGroupAccessDesc": { - "message": "You cannot add yourself to a group." + "message": "אתה לא יכול להוסיף את עצמך לקבוצה." }, "deleteProvider": { - "message": "Delete provider" + "message": "מחק ספק" }, "deleteProviderConfirmation": { - "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + "message": "מחיקת ספק היא לצמיתות ובלתי הפיכה. הזן את הסיסמה הראשית שלך כדי לאשר את המחיקה של הספק וכל הנתונים המשויכים." }, "deleteProviderName": { - "message": "Cannot delete $ID$", + "message": "לא ניתן למחוק את $ID$", "placeholders": { "id": { "content": "$1", @@ -8975,7 +9256,7 @@ } }, "deleteProviderWarningDescription": { - "message": "You must unlink all clients before you can delete $ID$.", + "message": "אתה מוכרח לבטל קישור של כל הלקוחות לפני שתוכל למחוק את $ID$.", "placeholders": { "id": { "content": "$1", @@ -8984,81 +9265,96 @@ } }, "providerDeleted": { - "message": "Provider deleted" + "message": "הספק נמחק" }, "providerDeletedDesc": { - "message": "The Provider and all associated data has been deleted." + "message": "הספק וכל המידע המשויך נמחק." }, "deleteProviderRecoverConfirmDesc": { - "message": "You have requested to delete this Provider. Use the button below to confirm." + "message": "ביקשת למחוק את הספק הזה. השתמש בלחצן למטה כדי לאשר." }, "deleteProviderWarning": { - "message": "Deleting your provider is permanent. It cannot be undone." + "message": "מחיקת הספק שלך היא לצמיתות. לא ניתן לבטלה." }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "שגיאה בהקצאת אוסף יעד." }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "שגיאה בהקצאת תיקיית יעד." }, "integrationsAndSdks": { - "message": "Integrations & SDKs", + "message": "שילובים & ערכות SDK", "description": "The title for the section that deals with integrations and SDKs." }, "integrations": { - "message": "Integrations" + "message": "שילובים" }, "integrationsDesc": { - "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + "message": "סנכרן באופן אוטומטי סודות ממנהל סודות של Bitwarden לשירות צד שלישי." }, "sdks": { - "message": "SDKs" + "message": "ערכות SDK" }, "sdksDesc": { - "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + "message": "השתמש ב־SDK של מנהל הסודות של Bitwarden בשפות התכנות הבאות כדי לבנות את היישומים שלך." }, "ssoDescStart": { - "message": "Configure", + "message": "קבע תצורת", "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, "ssoDescEnd": { - "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "message": "עבור Bitwarden באמצעות מדריך היישום עבור ספק הזהות שלך.", "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, "userProvisioning": { - "message": "User provisioning" + "message": "הקצאת משתמשים" }, "scimIntegration": { "message": "SCIM" }, "scimIntegrationDescStart": { - "message": "Configure ", + "message": "קבע תצורת ", "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, "scimIntegrationDescEnd": { - "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "message": "(מערכת לניהול זהות חוצה תחומים) כדי להקצות באופן אוטומטי משתמשים וקבוצות אל Bitwarden באמצעות מדריך היישום עבור ספק הזהות שלך.", "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, "bwdc": { - "message": "Bitwarden Directory Connector" + "message": "מחבר הספריות של Bitwarden" }, "bwdcDesc": { - "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." + "message": "קבע תצורת מחבר הספריות של Bitwarden כדי להקצות באופן אוטומטי משתמשים וקבוצות אל Bitwarden באמצעות מדריך היישום עבור ספק הזהות שלך." }, "eventManagement": { - "message": "Event management" + "message": "ניהול אירועים" }, "eventManagementDesc": { - "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." + "message": "שלב את יומני האירועים של Bitwarden עם מערכת ה־SIEM (מידע מערכת וניהול אירועים) שלך על ידי שימוש במדריך היישום עבור הפלטפורמה שלך." }, "deviceManagement": { - "message": "Device management" + "message": "ניהול מכשירים" }, "deviceManagementDesc": { - "message": "Configure device management for Bitwarden using the implementation guide for your platform." + "message": "קבע תצורת ניהול מכשירים עבור Bitwarden באמצעות מדריך היישום עבור הפלטפורמה שלך." + }, + "deviceIdMissing": { + "message": "מזהה המכשיר חסר" + }, + "deviceTypeMissing": { + "message": "סוג המכשיר חסר" + }, + "deviceCreationDateMissing": { + "message": "תאריך יצירת המכשיר חסר" + }, + "desktopRequired": { + "message": "נדרש שולחן עבודה" + }, + "reopenLinkOnDesktop": { + "message": "פתח מחדש קישור זה מהדוא\"ל שלך בשולחן עבודה." }, "integrationCardTooltip": { - "message": "Launch $INTEGRATION$ implementation guide.", + "message": "פתח מדריך יישום $INTEGRATION$.", "placeholders": { "integration": { "content": "$1", @@ -9067,7 +9363,7 @@ } }, "smIntegrationTooltip": { - "message": "Set up $INTEGRATION$.", + "message": "הגדר את $INTEGRATION$.", "placeholders": { "integration": { "content": "$1", @@ -9076,7 +9372,7 @@ } }, "smSdkTooltip": { - "message": "View $SDK$ repository", + "message": "הצג מאגר $SDK$", "placeholders": { "sdk": { "content": "$1", @@ -9085,7 +9381,7 @@ } }, "integrationCardAriaLabel": { - "message": "open $INTEGRATION$ implementation guide in a new tab.", + "message": "פתח מדריך מימוש $INTEGRATION$ בכרטיסיה חדשה.", "placeholders": { "integration": { "content": "$1", @@ -9094,7 +9390,7 @@ } }, "smSdkAriaLabel": { - "message": "view $SDK$ repository in a new tab.", + "message": "הצג מאגר $SDK$ בכרטיסיה חדשה.", "placeholders": { "sdk": { "content": "$1", @@ -9103,7 +9399,7 @@ } }, "smIntegrationCardAriaLabel": { - "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "message": "הגדר מדריך יישום $INTEGRATION$ בכרטיסיה חדשה.", "placeholders": { "integration": { "content": "$1", @@ -9112,64 +9408,67 @@ } }, "createNewClientToManageAsProvider": { - "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + "message": "צור ארגון לקוחות חדש כדי לנהל כספק. מקומות נוספים ישתקפו במחזור החיוב הבא." }, "selectAPlan": { - "message": "Select a plan" + "message": "בחר תוכנית" }, "thirtyFivePercentDiscount": { - "message": "35% Discount" + "message": "35% הנחה" }, "monthPerMember": { - "message": "month per member" + "message": "חודש לכל חבר" + }, + "monthPerMemberBilledAnnually": { + "message": "חודש לכל חבר מחויב מדי שנה" }, "seats": { - "message": "Seats" + "message": "מקומות" }, "addOrganization": { - "message": "Add organization" + "message": "הוסף ארגון" }, "createdNewClient": { - "message": "Successfully created new client" + "message": "יצר בהצלחה לקוח חדש" }, "noAccess": { - "message": "No access" + "message": "אין גישה" }, "collectionAdminConsoleManaged": { - "message": "This collection is only accessible from the admin console" + "message": "אוסף זה זמין רק ממסוף המנהל" }, "organizationOptionsMenu": { - "message": "Toggle Organization Menu" + "message": "שנה מצב תפריט ארגון" }, "vaultItemSelect": { - "message": "Select vault item" + "message": "בחר פריט כספת" }, "collectionItemSelect": { - "message": "Select collection item" + "message": "בחר פריט אוסף" }, "manageBillingFromProviderPortalMessage": { - "message": "Manage billing from the Provider Portal" + "message": "נהל חיובים מפורטל הספקים" }, "continueSettingUpFreeTrial": { - "message": "Continue setting up your free trial of Bitwarden" + "message": "המשך בהגדרת הניסיון החינמי של Bitwarden שלך" }, "continueSettingUpFreeTrialPasswordManager": { - "message": "Continue setting up your free trial of Bitwarden Password Manager" + "message": "המשך בהגדרת הניסיון החינמי של מנהל הסיסמאות Bitwarden שלך" }, "continueSettingUpFreeTrialSecretsManager": { - "message": "Continue setting up your free trial of Bitwarden Secrets Manager" + "message": "המשך בהגדרת הניסיון החינמי של מנהל הסודות Bitwarden שלך" }, "enterTeamsOrgInfo": { - "message": "Enter your Teams organization information" + "message": "הזן את פרטי ארגון הצוותים שלך" }, "enterFamiliesOrgInfo": { - "message": "Enter your Families organization information" + "message": "הזן את פרטי ארגון המשפחות שלך" }, "enterEnterpriseOrgInfo": { - "message": "Enter your Enterprise organization information" + "message": "הזן את פרטי הארגון הארגוני שלך" }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "הצג פריטים ב־$NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -9179,7 +9478,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "חזרה אל $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -9189,11 +9488,11 @@ } }, "back": { - "message": "Back", + "message": "הקודם", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "הסר $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -9203,34 +9502,34 @@ } }, "viewInfo": { - "message": "View info" + "message": "הצג מידע" }, "viewAccess": { - "message": "View access" + "message": "הצג גישה" }, "noCollectionsSelected": { - "message": "You have not selected any collections." + "message": "לא בחרת אוספים כלשהם." }, "updateName": { - "message": "Update name" + "message": "עדכן שם" }, "updatedOrganizationName": { - "message": "Updated organization name" + "message": "שם ארגון מעודכן" }, "providerPlan": { - "message": "Managed Service Provider" + "message": "ספק שירות מנוהל" }, "managedServiceProvider": { - "message": "Managed service provider" + "message": "ספק שירות מנוהל" }, "multiOrganizationEnterprise": { - "message": "Multi-organization enterprise" + "message": "ארגון רב-ארגוני" }, "orgSeats": { - "message": "Organization Seats" + "message": "מקומות ארגון" }, "providerDiscount": { - "message": "$AMOUNT$% Discount", + "message": "$AMOUNT$% הנחה", "placeholders": { "amount": { "content": "$1", @@ -9239,28 +9538,28 @@ } }, "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." + "message": "חזרות KDF נמוכות. הגדל את מספר החזרות שלך כדי לשפר את האבטחה של חשבונך." }, "changeKDFSettings": { - "message": "Change KDF settings" + "message": "שנה הגדרות KDF" }, "secureYourInfrastructure": { - "message": "Secure your infrastructure" + "message": "אבטח את התשתית שלך" }, "protectYourFamilyOrBusiness": { - "message": "Protect your family or business" + "message": "הגן על המשפחה או העסק שלך" }, "upgradeOrganizationCloseSecurityGaps": { - "message": "Close security gaps with monitoring reports" + "message": "צמצם פערי אבטחה עם דוחות ניטור" }, "upgradeOrganizationCloseSecurityGapsDesc": { - "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + "message": "היה תמיד לפני פרצות אבטחה על ידי שדרוג לתוכנית בתשלום עבור ניטור משופר." }, "approveAllRequests": { - "message": "Approve all requests" + "message": "אשר את כל הבקשות" }, "allLoginRequestsApproved": { - "message": "All login requests approved" + "message": "כל בקשות הכניסה אושרו" }, "payPal": { "message": "PayPal" @@ -9269,92 +9568,92 @@ "message": "Bitcoin" }, "updatedTaxInformation": { - "message": "Updated tax information" + "message": "פרטי מס מעודכנים" }, "billingInvalidTaxIdError": { - "message": "Invalid tax ID, if you believe this is an error please contact support." + "message": "מזהה מס לא תקין, אם אתה מאמין שזאת שגיאה, אנא פנה לתמיכה." }, "billingTaxIdTypeInferenceError": { - "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + "message": "לא הצלחנו לאמת את מזהה המס שלך, אם אתה מאמין שזאת שגיאה, אנא פנה לתמיכה." }, "billingPreviewInvalidTaxIdError": { - "message": "Invalid tax ID, if you believe this is an error please contact support." + "message": "מזהה מס לא תקין, אם אתה מאמין שזאת שגיאה, אנא פנה לתמיכה." }, "billingPreviewInvoiceError": { - "message": "An error occurred while previewing the invoice. Please try again later." + "message": "אירעה שגיאה בזמן הצגת התצוגה המקדימה של החשבונית. נא לנסות שוב מאוחר יותר." }, "unverified": { - "message": "Unverified" + "message": "לא מאומת" }, "verified": { - "message": "Verified" + "message": "מאומת" }, "viewSecret": { - "message": "View secret" + "message": "הצג סוד" }, "noClients": { - "message": "There are no clients to list" + "message": "אין לקוחות להצגה ברשימה" }, "providerBillingEmailHint": { - "message": "This email address will receive all invoices pertaining to this provider", + "message": "כתובת דוא\"ל זו תקבל את כל החשבוניות הנוגעות לספק זה", "description": "A hint that shows up on the Provider setup page to inform the admin the billing email will receive the provider's invoices." }, "upgradeOrganizationEnterprise": { - "message": "Identify security risks by auditing member access" + "message": "זהה סיכוני אבטחה על ידי ביקורת של גישת חברים" }, "onlyAvailableForEnterpriseOrganization": { - "message": "Quickly view member access across the organization by upgrading to an Enterprise plan." + "message": "הצג במהירות גישת חברים ברחבי הארגון על ידי שדרוג לתוכנית ארגונית." }, "date": { - "message": "Date" + "message": "תאריך" }, "exportClientReport": { - "message": "Export client report" + "message": "ייצא דוח לקוח" }, "memberAccessReport": { - "message": "Member access" + "message": "גישת חברים" }, "memberAccessReportDesc": { - "message": "Ensure members have access to the right credentials and their accounts are secure. Use this report to obtain a CSV of member access and account configurations." + "message": "וודא שלחברים יש גישה לתעודות הנכונות ושהחשבונות שלהם מאובטחים. השתמש בדוח הזה כדי לקבל CSV של גישת חברים ותצורות חשבון." }, "memberAccessReportPageDesc": { - "message": "Audit organization member access across groups, collections, and collection items. The CSV export provides a detailed breakdown per member, including information on collection permissions and account configurations." + "message": "ערוך ביקורת של גישת חברי ארגון ברחבי קבוצות, אוספים, ופריטי אוספים. ייצוא ה־CSV מספק פירוט מפורט לכל חבר, כולל מידע על הרשאות של אוספים ותצורות חשבון." }, "memberAccessReportNoCollection": { - "message": "(No Collection)" + "message": "(אין אוסף)" }, "memberAccessReportNoCollectionPermission": { - "message": "(No Collection Permission)" + "message": "(אין הרשאת אוסף)" }, "memberAccessReportNoGroup": { - "message": "(No Group)" + "message": "(אין קבוצה)" }, "memberAccessReportTwoFactorEnabledTrue": { - "message": "On" + "message": "מופעל" }, "memberAccessReportTwoFactorEnabledFalse": { - "message": "Off" + "message": "כבוי" }, "memberAccessReportAuthenticationEnabledTrue": { - "message": "On" + "message": "מופעל" }, "memberAccessReportAuthenticationEnabledFalse": { - "message": "Off" + "message": "כבוי" }, "higherKDFIterations": { - "message": "Higher KDF iterations can help protect your master password from being brute forced by an attacker." + "message": "ערכי חזרות KDF גבוהים יותר יכולים לעזור להגן על הסיסמה הראשית מפני תקיפה כוחנית על ידי תוקף." }, "incrementsOf100,000": { - "message": "increments of 100,000" + "message": "במרווחים של 100,000" }, "smallIncrements": { - "message": "small increments" + "message": "במרווחים קטנים" }, "kdfIterationRecommends": { - "message": "We recommend 600,000 or more" + "message": "אנו ממליצים על 600,000 או יותר" }, "kdfToHighWarningIncreaseInIncrements": { - "message": "For older devices, setting your KDF too high may lead to performance issues. Increase the value in $VALUE$ and test your devices.", + "message": "עבור מכשירים ישנים יותר, הגדרת ה־KDF שלך לערך גבוה מדי עשויה להוביל לבעיות ביצועים. הגדל את הערך ב־$VALUE$ ובדוק את המכשירים שלך.", "placeholders": { "value": { "content": "$1", @@ -9363,31 +9662,31 @@ } }, "providerReinstate": { - "message": " Contact Customer Support to reinstate your subscription." + "message": " צור קשר עם שירות הלקוחות כדי להחזיר את המנוי שלך." }, "secretPeopleDescription": { - "message": "Grant groups or people access to this secret. Permissions set for people will override permissions set by groups." + "message": "הענק לקבוצות או אנשים גישה לסוד הזה. הרשאות שהוגדרו עבור אנשים ידרסו הרשאות שהוגדרו על ידי קבוצות." }, "secretPeopleEmptyMessage": { - "message": "Add people or groups to share access to this secret" + "message": "הוסף אנשים או קבוצות כדי לשתף גישה לסוד הזה" }, "secretMachineAccountsDescription": { - "message": "Grant machine accounts access to this secret." + "message": "הענק לחשבונות מכונה גישה לסוד הזה." }, "secretMachineAccountsEmptyMessage": { - "message": "Add machine accounts to grant access to this secret" + "message": "הוסף חשבונות מכונה כדי להעניק גישה לסוד הזה" }, "smAccessRemovalWarningSecretTitle": { - "message": "Remove access to this secret" + "message": "הסר גישה לסוד הזה" }, "smAccessRemovalSecretMessage": { - "message": "This action will remove your access to this secret." + "message": "פעולה זו תסיר את הגישה שלך לסוד הזה." }, "invoice": { - "message": "Invoice" + "message": "חשבונית" }, "unassignedSeatsAvailable": { - "message": "You have $SEATS$ unassigned seats available.", + "message": "יש לך $SEATS$ מקומות לא מוקצים זמינים.", "placeholders": { "seats": { "content": "$1", @@ -9397,61 +9696,61 @@ "description": "A message showing how many unassigned seats are available for a provider." }, "contactYourProviderForAdditionalSeats": { - "message": "Contact your provider admin to purchase additional seats." + "message": "צור קשר עם מנהל הספק שלך כדי לרכוש מקומות נוספים." }, "open": { - "message": "Open", + "message": "פתח", "description": "The status of an invoice." }, "uncollectible": { - "message": "Uncollectible", + "message": "אינה ניתנת לאיסוף", "description": "The status of an invoice." }, "clientDetails": { - "message": "Client details" + "message": "פרטי לקוח" }, "downloadCSV": { - "message": "Download CSV" + "message": "הורד CSV" }, "monthlySubscriptionUserSeatsMessage": { - "message": "Adjustments to your subscription will result in prorated charges to your billing totals on your next billing period. " + "message": "התאמות למנוי שלך יגרמו לשינויים יחסיים לסך כל החיובים שלך במחזור החיוב הבא שלך. " }, "annualSubscriptionUserSeatsMessage": { - "message": "Adjustments to your subscription will result in prorated charges on a monthly billing cycle. " + "message": "התאמות למנוי שלך יגרמו לשינויים יחסיים במחזור חיוב חודשי. " }, "billingHistoryDescription": { - "message": "Download a CSV to obtain client details for each billing date. Prorated charges are not included in the CSV and may vary from the linked invoice. For the most accurate billing details, refer to your monthly invoices.", + "message": "הורד CSV כדי לקבל פרטי לקוח עבור כל תאריך חיוב. חיובים יחסיים אינם כלולים ב־CSV ועשויים להיות שונים מהחשבונית המקושרת. עבור פרטי החיוב המדויקים ביותר, עיין בחשבוניות החודשיות שלך.", "description": "A paragraph on the Billing History page of the Provider Portal letting users know they can download a CSV report for their invoices that does not include prorations." }, "noInvoicesToList": { - "message": "There are no invoices to list", + "message": "אין חשבוניות להצגה ברשימה", "description": "A paragraph on the Billing History page of the Provider Portal letting users know they can download a CSV report for their invoices that does not include prorations." }, "providerClientVaultPrivacyNotification": { - "message": "Notice: Later this month, client vault privacy will be improved and provider members will no longer have direct access to client vault items. For questions,", + "message": "הודעה: מאוחר יותר החודש, הפרטיות של כספת לקוח תשופר וחברי ספק לא יוכלו לגשת ישירות לפריטי כספת לקוח. לשאלות,", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'Notice: Later this month, client vault privacy will be improved and provider members will no longer have direct access to client vault items. For questions, please contact Bitwarden support'." }, "contactBitwardenSupport": { - "message": "contact Bitwarden support.", + "message": "פנה לתמיכת Bitwarden.", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'Notice: Later this month, client vault privacy will be improved and provider members will no longer have direct access to client vault items. For questions, please contact Bitwarden support'. 'Bitwarden' should not be translated" }, "sponsored": { - "message": "Sponsored" + "message": "ממומן" }, "licenseAndBillingManagementDesc": { - "message": "After making updates in the Bitwarden cloud server, upload your license file to apply the most recent changes." + "message": "לאחר ביצוע עדכונים בשרת הענן של Bitwarden, העלה את קובץ הרישיון שלך כדי להחיל את השינויים הכי אחרונים." }, "addToFolder": { - "message": "Add to folder" + "message": "הוסף לתיקייה" }, "selectFolder": { - "message": "Select folder" + "message": "בחר תיקייה" }, "personalItemTransferWarningSingular": { - "message": "1 item will be permanently transferred to the selected organization. You will no longer own this item." + "message": "פריט 1 יועבר לצמיתות לארגון הנבחר. לא תהיה יותר הבעלים של הפריט הזה." }, "personalItemsTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to the selected organization. You will no longer own these items.", + "message": "$PERSONAL_ITEMS_COUNT$ פריטים יועברו לצמיתות לארגון הנבחר. לא תהיה יותר הבעלים של הפריטים האלה.", "placeholders": { "personal_items_count": { "content": "$1", @@ -9460,7 +9759,7 @@ } }, "personalItemWithOrgTransferWarningSingular": { - "message": "1 item will be permanently transferred to $ORG$. You will no longer own this item.", + "message": "פריט 1 יועבר לצמיתות אל $ORG$. לא תהיה יותר הבעלים של הפריט הזה.", "placeholders": { "org": { "content": "$1", @@ -9469,7 +9768,7 @@ } }, "personalItemsWithOrgTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to $ORG$. You will no longer own these items.", + "message": "$PERSONAL_ITEMS_COUNT$ פריטים יועברו לצמיתות אל $ORG$. לא תהיה יותר הבעלים של הפריטים האלה.", "placeholders": { "personal_items_count": { "content": "$1", @@ -9482,79 +9781,85 @@ } }, "data": { - "message": "Data" + "message": "נתונים" }, "purchasedSeatsRemoved": { - "message": "purchased seats removed" + "message": "מקומות שנרכשו הוסרו" }, "environmentVariables": { - "message": "Environment variables" + "message": "משתני סביבה" }, "organizationId": { - "message": "Organization ID" + "message": "מזהה ארגון" }, "projectIds": { - "message": "Project IDs" + "message": "מזהי פרויקט" }, "projectId": { - "message": "Project ID" + "message": "מזהה פרויקט" }, "projectsAccessedByMachineAccount": { - "message": "The following projects can be accessed by this machine account." + "message": "ניתן לגשת לפרויקטים הבאים על ידי חשבון מכונה זה." }, "config": { - "message": "Config" + "message": "תצורה" }, "learnMoreAboutEmergencyAccess": { - "message": "Learn more about emergency access" + "message": "למד עוד על גישת חירום" }, "learnMoreAboutMatchDetection": { - "message": "Learn more about match detection" + "message": "למד עוד על זיהוי התאמה" }, "learnMoreAboutMasterPasswordReprompt": { - "message": "Learn more about master password re-prompt" + "message": "למד עוד על בקשה חוזרת של סיסמה ראשית" }, "learnMoreAboutSearchingYourVault": { - "message": "Learn more about searching your vault" + "message": "למד עוד על חיפוש הכספת שלך" }, "learnMoreAboutYourAccountFingerprintPhrase": { - "message": "Learn about your account fingerprint phrase" + "message": "למד עוד על ביטוי טביעת האצבע של החשבון שלך" }, "impactOfRotatingYourEncryptionKey": { - "message": "Impact of rotating your encryption key" + "message": "השפעת סיבוב מפתח ההצפנה שלך" }, "learnMoreAboutEncryptionAlgorithms": { - "message": "Learn more about encryption algorithms" + "message": "למד עוד על אלגוריתמי הצפנה" }, "learnMoreAboutKDFIterations": { - "message": "Learn more about KDF iterations" + "message": "למד עוד על חזרות KDF" }, "learnMoreAboutLocalization": { - "message": "Learn more about localization" + "message": "למד עוד על לוקליזציה" }, "learnMoreAboutWebsiteIcons": { - "message": "Learn more about using website icons" + "message": "למד עוד על שימוש בסמלי אתרים" }, "learnMoreAboutUserAccess": { - "message": "Learn more about user access" + "message": "למד עוד על גישת משתמש" }, "learnMoreAboutMemberRoles": { - "message": "Learn more about member roles and permissions" + "message": "למד עוד על תפקידים והרשאות של חברים" }, "whatIsACvvNumber": { - "message": "What is a CVV number?" + "message": "מה זה מספר CVV?" }, "learnMoreAboutApi": { - "message": "Learn more about Bitwarden's API" + "message": "למד עוד על ה־API של Bitwarden" + }, + "fileSend": { + "message": "סֵנְד של קובץ" }, "fileSends": { - "message": "File Sends" + "message": "סֵנְדים של קובץ" + }, + "textSend": { + "message": "סֵנְד של טקסט" }, "textSends": { - "message": "Text Sends" + "message": "סֵנְדים של טקסט" }, "includesXMembers": { - "message": "for $COUNT$ member", + "message": "עבור $COUNT$ חברים", "placeholders": { "count": { "content": "$1", @@ -9572,10 +9877,10 @@ } }, "optionalOnPremHosting": { - "message": "Optional on-premises hosting" + "message": "אירוח מקומי אופציונלי" }, "upgradeFreeOrganization": { - "message": "Upgrade your $NAME$ organization ", + "message": "שדרג את ארגון ה$NAME$ שלך ", "placeholders": { "name": { "content": "$1", @@ -9584,10 +9889,10 @@ } }, "includeSsoAuthenticationMessage": { - "message": "SSO Authentication" + "message": "אימות SSO" }, "familiesPlanInvLimitReachedManageBilling": { - "message": "Families organizations may have up to $SEATCOUNT$ members. Upgrade to a paid plan to invite more members.", + "message": "ארגוני משפחות יכולים לכלול עד $SEATCOUNT$ חברים. שדרג לתוכנית בתשלום כדי להזמין עוד חברים.", "placeholders": { "seatcount": { "content": "$1", @@ -9596,7 +9901,7 @@ } }, "familiesPlanInvLimitReachedNoManageBilling": { - "message": "Families organizations may have up to $SEATCOUNT$ members. Contact your organization owner to upgrade.", + "message": "ארגוני משפחות יכולים לכלול עד $SEATCOUNT$ חברים. פנה לבעלי הארגון שלך כדי לשדרג.", "placeholders": { "seatcount": { "content": "$1", @@ -9605,10 +9910,10 @@ } }, "upgradePlans": { - "message": "Upgrade your plan to invite members and experience powerful security features." + "message": "שדרג את התוכנית שלך כדי להזמין חברים ולחוות תכונות אבטחה עוצמתיות." }, "upgradeDiscount": { - "message": "Save $AMOUNT$%", + "message": "חסוך $AMOUNT$%", "placeholders": { "amount": { "content": "$1", @@ -9617,137 +9922,146 @@ } }, "enterprisePlanUpgradeMessage": { - "message": "Advanced capabilities for larger organizations" + "message": "יכולות מתקדמות עבור ארגונים גדולים יותר" }, "teamsPlanUpgradeMessage": { - "message": "Resilient protection for growing teams" + "message": "הגנה עמידה עבור צוותים בצמיחה" }, "teamsInviteMessage": { - "message": "Invite unlimited members" + "message": "הזמן מספר בלתי מוגבל של חברים" }, "accessToCreateGroups": { - "message": "Access to create groups" + "message": "גישה ליצור קבוצות" }, "syncGroupsAndUsersFromDirectory": { - "message": "Sync groups and users from a directory" + "message": "סנכרן קבוצות ומשתמשים מתוך ספריה" }, "familyPlanUpgradeMessage": { - "message": "Secure your family logins" + "message": "אבטח את הכניסות של המשפחה שלך" }, "accessToPremiumFeatures": { - "message": "Access to Premium features" + "message": "גישה לתכונות פרימיום" }, "additionalStorageGbMessage": { - "message": "GB additional storage" + "message": "GB אחסון נוסף" }, "sshKeyAlgorithm": { - "message": "Key algorithm" + "message": "אלגוריתם מפתח" + }, + "sshPrivateKey": { + "message": "מפתח פרטי" + }, + "sshPublicKey": { + "message": "מפתח ציבורי" + }, + "sshFingerprint": { + "message": "טביעת אצבע" }, "sshKeyFingerprint": { - "message": "Fingerprint" + "message": "טביעת אצבע" }, "sshKeyPrivateKey": { - "message": "Private key" + "message": "מפתח פרטי" }, "sshKeyPublicKey": { - "message": "Public key" + "message": "מפתח ציבורי" }, "sshKeyAlgorithmED25519": { "message": "ED25519" }, "sshKeyAlgorithmRSA2048": { - "message": "RSA 2048-Bit" + "message": "RSA‏ 2048 סיביות" }, "sshKeyAlgorithmRSA3072": { - "message": "RSA 3072-Bit" + "message": "RSA‏ 3072 סיביות" }, "sshKeyAlgorithmRSA4096": { - "message": "RSA 4096-Bit" + "message": "RSA‏ 4096 סיביות" }, "premiumAccounts": { - "message": "6 premium accounts" + "message": "6 חשבונות פרימיום" }, "unlimitedSharing": { - "message": "Unlimited sharing" + "message": "שיתוף ללא הגבלה" }, "unlimitedCollections": { - "message": "Unlimited collections" + "message": "אוספים ללא הגבלה" }, "secureDataSharing": { - "message": "Secure data sharing" + "message": "שיתוף נתונים מאובטח" }, "eventLogMonitoring": { - "message": "Event log monitoring" + "message": "ניטור יומן אירועים" }, "directoryIntegration": { - "message": "Directory integration" + "message": "שילוב ספריה" }, "passwordLessSso": { - "message": "Passwordless SSO" + "message": "SSO ללא סיסמה" }, "accountRecovery": { - "message": "Account recovery" + "message": "שחזור חשבון" }, "customRoles": { - "message": "Custom roles" + "message": "תפקידים מותאמים אישית" }, "unlimitedSecretsStorage": { - "message": "Unlimited secrets storage" + "message": "אחסון סודות ללא הגבלה" }, "unlimitedUsers": { - "message": "Unlimited users" + "message": "משתמשים ללא הגבלה" }, "UpTo50MachineAccounts": { - "message": "Up to 50 machine accounts" + "message": "עד 50 חשבונות מכונה" }, "UpTo20MachineAccounts": { - "message": "Up to 20 machine accounts" + "message": "עד 20 חשבונות מכונה" }, "current": { - "message": "Current" + "message": "נוכחי" }, "secretsManagerSubscriptionInfo": { - "message": "Your Secrets Manager subscription will upgrade based on the plan selected" + "message": "מנוי מנהל הסודות שלך ישודרג בהתאם לתוכנית שנבחרה" }, "bitwardenPasswordManager": { - "message": "Bitwarden Password Manager" + "message": "מנהל הסיסמאות Bitwarden" }, "secretsManagerComplimentaryPasswordManager": { - "message": "Your complimentary one year Password Manager subscription will upgrade to the selected plan. You will not be charged until the complimentary period is over." + "message": "מנוי מנהל הסיסמאות חינמי לשנה אחת שלך ישודרג לתוכנית שנבחרה. לא תחויב עד שהתקופה החינמית תסתיים." }, "fileSavedToDevice": { - "message": "File saved to device. Manage from your device downloads." + "message": "הקובץ נשמר למכשיר. נהל מהורדות המכשיר שלך." }, "publicApi": { - "message": "Public API", + "message": "API ציבורי", "description": "The text, 'API', is an acronym and should not be translated." }, "showCharacterCount": { - "message": "Show character count" + "message": "הצג מונה תווים" }, "hideCharacterCount": { - "message": "Hide character count" + "message": "הסתר מונה תווים" }, "editAccess": { - "message": "Edit access" + "message": "ערוך גישה" }, "textHelpText": { - "message": "Use text fields for data like security questions" + "message": "השתמש בשדות טקסט עבור נתונים כמו שאלות אבטחה" }, "hiddenHelpText": { - "message": "Use hidden fields for sensitive data like a password" + "message": "השתמש בשדות נסתרים עבור מידע רגיש כמו סיסמה" }, "checkBoxHelpText": { - "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + "message": "השתמש בתיבות סימון אם תרצה למלא אוטומטית תיבת סימון של טופס, כמו זכור דוא\"ל" }, "linkedHelpText": { - "message": "Use a linked field when you are experiencing autofill issues for a specific website." + "message": "השתמש בשדה מקושר כאשר אתה חווה בעיות מילוי אוטומטי עם אתר מסוים." }, "linkedLabelHelpText": { - "message": "Enter the the field's html id, name, aria-label, or placeholder." + "message": "הזן את מזהה ה־html, שם, תווית aria או מציין מיקום." }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "כלול תווי אות גדולה", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -9755,7 +10069,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "כלול תווי אות קטנה", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -9763,7 +10077,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "כלול מספרים", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -9771,40 +10085,36 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "כלול תווים מיוחדים", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { - "message": "Add attachment" + "message": "הוסף צרופה" }, "maxFileSizeSansPunctuation": { - "message": "Maximum file size is 500 MB" + "message": "גודל הקובץ המרבי הוא 500MB" }, "permanentlyDeleteAttachmentConfirmation": { - "message": "Are you sure you want to permanently delete this attachment?" + "message": "האם אתה בטוח שברצונך למחוק לצמיתות צרופה זו?" }, "manageSubscriptionFromThe": { - "message": "Manage subscription from the", + "message": "נהל מנוי מתוך", "description": "This represents the beginning of a sentence. The full sentence will be 'Manage subscription from the Provider Portal', but 'Provider Portal' will be a link and thus cannot be included in the translation file." }, "toHostBitwardenOnYourOwnServer": { - "message": "To host Bitwarden on your own server, you will need to upload your license file. To support Free Families plans and advanced billing capabilities for your self-hosted organization, you will need to set up automatic sync in your self-hosted organization." + "message": "כדי לארח את Bitwarden בשרת שלך, תצטרך להעלות את קובץ הרישיון שלך. כדי לתמוך בתוכניות למשפחות בחינם וביכולות חיוב מתקדמות עבור הארגון באירוח עצמי שלך, תצטרך להגדיר סנכרון אוטומטי בארגון האירוח העצמי שלך." }, "selfHostingTitleProper": { - "message": "Self-Hosting" + "message": "אירוח עצמי" }, "claim-domain-single-org-warning": { - "message": "Claiming a domain will turn on the single organization policy." + "message": "דרישת דומיין תפעיל את מדיניות הארגון היחיד." }, "single-org-revoked-user-warning": { - "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." + "message": "חברים שאינם עומדים בדרישות יבוטלו. מנהלים יכולים לשחזר חברים ברגע שהם עזבו את כל הארגונים האחרים." }, "deleteOrganizationUser": { - "message": "Delete $NAME$", + "message": "מחק $NAME$", "placeholders": { "name": { "content": "$1", @@ -9814,7 +10124,7 @@ } }, "deleteOrganizationUserWarningDesc": { - "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "message": "זה ימחק לצמיתות את כל הפריטים המנוהלים על ידי $NAME$. פריטי אוסף אינם מושפעים.", "description": "Warning description for the delete organization user dialog", "placeholders": { "name": { @@ -9824,11 +10134,11 @@ } }, "deleteManyOrganizationUsersWarningDesc": { - "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "message": "זה ימחק לצמיתות את כל הפריטים המנוהלים על ידי החברים הבאים. פריטי אוסף אינם מושפעים.", "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { - "message": "Deleted $NAME$", + "message": "$NAME$ נמחק", "placeholders": { "name": { "content": "$1", @@ -9837,10 +10147,10 @@ } }, "organizationUserDeletedDesc": { - "message": "The user was removed from the organization and all associated user data has been deleted." + "message": "המשתמש הוסר מהארגון וכל נתוני המשתמש המשויכים נמחקו." }, "deletedUserId": { - "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "message": "משתמש $ID$ נמחק - בעלים / מנהל מחק את חשבון המשתמש", "placeholders": { "id": { "content": "$1", @@ -9849,7 +10159,7 @@ } }, "userLeftOrganization": { - "message": "User $ID$ left organization", + "message": "משתמש $ID$ עזב את הארגון", "placeholders": { "id": { "content": "$1", @@ -9858,7 +10168,7 @@ } }, "suspendedOrganizationTitle": { - "message": "The $ORGANIZATION$ is suspended", + "message": "ה־$ORGANIZATION$ מושעה", "placeholders": { "organization": { "content": "$1", @@ -9867,52 +10177,61 @@ } }, "suspendedUserOrgMessage": { - "message": "Contact your organization owner for assistance." + "message": "צור קשר עם בעלי הארגון שלך עבור סיוע." }, "suspendedOwnerOrgMessage": { - "message": "To regain access to your organization, add a payment method." + "message": "כדי לקבל גישה מחדש לארגון שלך, הוסף אמצעי תשלום." }, "deleteMembers": { - "message": "Delete members" + "message": "מחק חברים" }, "noSelectedMembersApplicable": { - "message": "This action is not applicable to any of the selected members." + "message": "פעולה זו אינה ישימה לאף אחד מהחברים שנבחרו." }, "deletedSuccessfully": { - "message": "Deleted successfully" + "message": "נמחק בהצלחה" }, "freeFamiliesSponsorship": { - "message": "Remove Free Bitwarden Families sponsorship" + "message": "הסר חסות Bitwarden למשפחות בחינם" }, "freeFamiliesSponsorshipPolicyDesc": { - "message": "Do not allow members to redeem a Families plan through this organization." + "message": "אל תאפשר לחברים לממש תוכנית משפחות דרך ארגון זה." }, "verifyBankAccountWithStatementDescriptorWarning": { - "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + "message": "תשלום באמצעות חשבון בנק זמינה רק ללקוחות בארצות הברית. אתה תידרש לאמת את חשבון הבנק שלך. אנחנו נבצע מיקרו־הפקדה בתוך 1-2 ימי עסקים. הזן את קוד תיאור ההצהרה מהפקדה זו בדף החיוב של הארגון כדי לאמת את חשבון הבנק. כשל באימות חשבון הבנק יגרום לפספוס תשלום ולהשעיית המנוי שלך." }, "verifyBankAccountWithStatementDescriptorInstructions": { - "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + "message": "ביצענו מיקרו־הפקדה לחשבון הבנק שלך (זה עשוי לקחת 1-2 ימי עסקים). הזן את הקוד בן שש הספרות המתחיל ב־'SM' הנמצא בתיאור ההפקדה. כשל באימות חשבון הבנק יגרום לפספוס תשלום ולהשעיית המנוי שלך." }, "descriptorCode": { - "message": "Descriptor code" + "message": "קוד מתאר" + }, + "cannotRemoveViewOnlyCollections": { + "message": "אינך יכול להסיר אוספים עם הרשאות הצגה בלבד: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } }, "importantNotice": { - "message": "Important notice" + "message": "הודעה חשובה" }, "setupTwoStepLogin": { - "message": "Set up two-step login" + "message": "הגדר כניסה דו־שלבית" }, "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + "message": "Bitwarden ישלח קוד לדוא\"ל החשבון שלך כדי לאמת כניסות ממכשירים חדשים החל מפברואר 2025." }, "newDeviceVerificationNoticeContentPage2": { - "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + "message": "אתה יכול להגדיר כניסה דו־שלבית כדרך חלופית להגן על החשבון שלך או לשנות את הדוא\"ל שלך לאחד שאתה יכול לגשת אליו." }, "remindMeLater": { - "message": "Remind me later" + "message": "הזכר לי מאוחר יותר" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", + "message": "יש לך גישה מהימנה לדוא\"ל שלך, $EMAIL$?", "placeholders": { "email": { "content": "$1", @@ -9921,40 +10240,49 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "לא, אין לי" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" + "message": "כן, אני יכול לגשת לדוא\"ל שלי באופן מהימן" }, "turnOnTwoStepLogin": { - "message": "Turn on two-step login" + "message": "הפעל כניסה דו־שלבית" }, "changeAcctEmail": { - "message": "Change account email" + "message": "שנה דוא\"ל חשבון" }, "removeMembers": { - "message": "Remove members" + "message": "הסר חברים" + }, + "devices": { + "message": "מכשירים" + }, + "deviceListDescription": { + "message": "החשבון שלך היה מחובר לכל אחד מהמכשירים למטה. אם אתה לא מזהה מכשיר, הסר אותו עכשיו." + }, + "deviceListDescriptionTemp": { + "message": "החשבון שלך היה מחובר לכל אחד מהמכשירים למטה." }, "claimedDomains": { - "message": "Claimed domains" + "message": "דומיינים שנדרשו" }, "claimDomain": { - "message": "Claim domain" + "message": "דרוש דומיין" }, "reclaimDomain": { - "message": "Reclaim domain" + "message": "דרוש דומיין מחדש" }, "claimDomainNameInputHint": { - "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + "message": "דוגמה: mydomain.com. תת-דומיינים דורשים שרשומות נפרדות ידרשו." }, "automaticClaimedDomains": { - "message": "Automatic Claimed Domains" + "message": "דומיינים שנדרשו אוטומטית" }, "automaticDomainClaimProcess": { - "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + "message": "Bitwarden ינסה לדרוש את הדומיין 3 פעמים במהלך 72 השעות הראשונות. אם לא ניתן לדרוש את הדומיין, בדוק את רשומת ה־DNS במארח שלך ודרוש באופן ידני. הדומיין יוסר מהארגון שלך תוך 7 ימים אם הוא לא נדרש." }, "domainNotClaimed": { - "message": "$DOMAIN$ not claimed. Check your DNS records.", + "message": "$DOMAIN$ אינו נדרש. בדוק את רשומות ה־DNS שלך.", "placeholders": { "DOMAIN": { "content": "$1", @@ -9963,19 +10291,19 @@ } }, "domainStatusClaimed": { - "message": "Claimed" + "message": "נדרש" }, "domainStatusUnderVerification": { - "message": "Under verification" + "message": "תחת אימות" }, "claimedDomainsDesc": { - "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + "message": "דרוש דומיין כדי להחזיק בכל חשבונות החברים אשר כתובת הדוא\"ל שלהם תואמת את הדומיין. חברים יוכלו לדלג על מזהה ה־SSO בעת כניסה. מנהלים גם יוכלו למחוק חשבונות חברים." }, "invalidDomainNameClaimMessage": { - "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + "message": "הקלט אינו בפורמט תקין. פורמט: mydomain.com. תת-דומיינים דורשים שרשומות נפרדות ידרשו." }, "domainClaimedEvent": { - "message": "$DOMAIN$ claimed", + "message": "$DOMAIN$ נדרש", "placeholders": { "DOMAIN": { "content": "$1", @@ -9984,7 +10312,7 @@ } }, "domainNotClaimedEvent": { - "message": "$DOMAIN$ not claimed", + "message": "$DOMAIN$ לא נדרש", "placeholders": { "DOMAIN": { "content": "$1", @@ -9993,7 +10321,7 @@ } }, "updatedRevokeSponsorshipConfirmationForSentSponsorship": { - "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "message": "אם תסיר את $EMAIL$, החסות עבור תוכנית משפחות זו לא תהיה ניתנת למימוש. האם אתה בטוח שברצונך להמשיך?", "placeholders": { "email": { "content": "$1", @@ -10002,7 +10330,7 @@ } }, "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { - "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "message": "אם תסיר את $EMAIL$, החסות עבור תוכנית משפחות זו תסתיים ואמצעי התשלום השמור יחויב $40 + מס רלוונטי בתאריך $DATE$. לא תוכל לממש חסות חדשה עד $DATE$. האם אתה בטוח שברצונך להמשיך?", "placeholders": { "email": { "content": "$1", @@ -10015,13 +10343,75 @@ } }, "domainClaimed": { - "message": "Domain claimed" + "message": "דומיין נדרש" }, "organizationNameMaxLength": { - "message": "Organization name cannot exceed 50 characters." + "message": "שם ארגון לא יכול לחרוג מ־50 תווים." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "הסיסמה שהזנת שגויה." + }, + "importSshKey": { + "message": "ייבוא" + }, + "confirmSshKeyPassword": { + "message": "אשר סיסמה" + }, + "enterSshKeyPasswordDesc": { + "message": "הזן את הסיסמה עבור מפתח ה־SSH." + }, + "enterSshKeyPassword": { + "message": "הזן סיסמה" + }, + "invalidSshKey": { + "message": "מפתח ה־SSH אינו תקין" + }, + "sshKeyTypeUnsupported": { + "message": "סוג מפתח ה־SSH אינו נתמך" + }, + "importSshKeyFromClipboard": { + "message": "ייבא מפתח מלוח ההעתקה" + }, + "sshKeyImported": { + "message": "מפתח SSH יובא בהצלחה" + }, + "copySSHPrivateKey": { + "message": "העתק מפתח פרטי" + }, + "openingExtension": { + "message": "פותח את הרחבת היישום של Bitwarden" + }, + "somethingWentWrong": { + "message": "משהו השתבש..." + }, + "openingExtensionError": { + "message": "התקשינו לפתוח את הרחבת הדפדפן של Bitwarden. לחץ על הלחצן כדי לפתוח אותה עכשיו." + }, + "openExtension": { + "message": "פתח הרחבה" + }, + "doNotHaveExtension": { + "message": "אין לך את הרחבת הדפדפן של Bitwarden?" + }, + "installExtension": { + "message": "התקן הרחבה" + }, + "openedExtension": { + "message": "פתח את הרחבת הדפדפן" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "פתח בהצלחה את הרחבת הדפדפן של Bitwarden. אתה יכול לסקור עכשיו את הסיסמאות בסיכון שלך." + }, + "openExtensionManuallyPart1": { + "message": "התקשינו לפתוח את הרחבת הדפדפן של Bitwarden. פתח את הסמל של Bitwarden", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "מסרגל הכלים.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "המנוי שלך יתחדש בקרוב. כדי להבטיח שירות רציף, צור קשר עם $RESELLER$ כדי לאשר את החידוש שלך לפני $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "חשבונית עבור המנוי שלך הונפקה בתאריך $ISSUED_DATE$. כדי להבטיח שירות רציף, צור קשר עם $RESELLER$ כדי לאשר את החידוש שלך לפני $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "החשבונית עבור המנוי שלך לא שולמה. כדי להבטיח שירות רציף, צור קשר עם $RESELLER$ כדי לאשר את החידוש שלך לפני $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "מנוי ארגון הופעל מחדש" + }, + "restartSubscription": { + "message": "הפעל מחדש את המנוי שלך" + }, + "suspendedManagedOrgMessage": { + "message": "פנה אל $PROVIDER$ עבור סיוע.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "למנהלים עכשיו יש את היכולת למחוק חשבונות של חברים ששייכים לדומיין שנדרש." + }, + "deleteManagedUserWarningDesc": { + "message": "פעולה זו תמחק את חשבון החבר כולל כל הפריטים בכספת שלו. זו מחליפה את פעולת ההסרה הקודמת." + }, + "deleteManagedUserWarning": { + "message": "מחיקה היא פעולה חדשה!" + }, + "seatsRemaining": { + "message": "יש לך $REMAINING$ מקומות נותרים מתוך $TOTAL$ מקומות שהוקצו לארגון זה. פנה לספק שלך כדי לנהל את המנוי שלך.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "ארגון קיים" + }, + "selectOrganizationProviderPortal": { + "message": "בחר ארגון להוספה אל פורטל הספקים שלך." + }, + "noOrganizations": { + "message": "אין ארגונים להצגה ברשימה" + }, + "yourProviderSubscriptionCredit": { + "message": "מנוי הספק שלך יקבל זיכוי עבור כל הזמן שנותר במנוי של הארגון." + }, + "doYouWantToAddThisOrg": { + "message": "האם ברצונך להוסיף ארגון זה אל $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "נוסף ארגון קיים" + }, + "assignedExceedsAvailable": { + "message": "מקומות מוקצים עולים על מקומות פנויים." + }, + "changeAtRiskPassword": { + "message": "שנה סיסמה בסיכון" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "הסר ביטול נעילה עם PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "אל תאפשר לחברים לבטל את נעילת החשבון שלהם עם PIN." + }, + "limitedEventLogs": { + "message": "לתוכניות מסוג $PRODUCT_TYPE$ אין גישה ליומני אירועים אמיתיים", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "קבל גישה מלאה ליומני אירועים של ארגון על ידי שדרוג לתוכנית לצוותים או ארגונים." + }, + "upgradeEventLogTitle": { + "message": "שדרג עבור נתוני יומן אירועים אמיתיים" + }, + "upgradeEventLogMessage": { + "message": "האירועים האלה הם דוגמאות בלבד ולא משקפים אירועים אמיתיים בתוך ארגון ה־Bitwarden שלך." + }, + "cannotCreateCollection": { + "message": "לארגונים חינמיים יכולים להיות עד 2 אוספים. שדרג לתוכנית בתשלום כדי להוסיף עוד אוספים." } } diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index 82a0c12a390..1cab0dc843b 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Critical applications" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "यह किस प्रकार का आइटम है?" }, @@ -159,6 +201,9 @@ "notes": { "message": "नोट्स" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Note" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "सॉर्ट करने के लिए ड्रैग करें" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "शब्द" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Edit folder" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "Base domain", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "ex.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filter" }, - "moveSelectedToOrg": { - "message": "Move selected to organization" - }, "deleteSelected": { "message": "चयनित मिटाएं" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "नहीं" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "सुरक्षित तिजोरी में प्रवेश करने के लिए नया खाता बनाएं या लॉग इन करें।" }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Submit" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "मुझे याद रखें" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "फिर से सत्यापन कोड ईमेल भेजें" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Lost access to all of your two-step login providers? Use your recovery code to turn off all two-step login providers from your account." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Migrated from FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "Email" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." }, - "moveManyToOrgDesc": { - "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved." - }, "collectionsDesc": { "message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Verification code (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Regenerate password" - }, "length": { "message": "Length" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Danger zone" }, - "dangerZoneDesc": { - "message": "Careful, these actions are not reversible!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Deauthorize sessions" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Proceeding will also log you out of your current session, requiring you to log back in. You will also be prompted for two-step login again, if set up. Active sessions on other devices may continue to remain active for up to one hour." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "All sessions deauthorized" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Setting up two-step login can permanently lock you out of your Bitwarden account. A recovery code allows you to access your account in the event that you can no longer use your normal two-step login provider (example: you lose your device). Bitwarden support will not be able to assist you if you lose access to your account. We recommend you write down or print the recovery code and keep it in a safe place." }, + "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." + }, "viewRecoveryCode": { "message": "View recovery code" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Manage" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Turn off" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Revoke access" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "This two-step login provider is active on your account." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "There was a problem reading the security key. Try again." }, - "twoFactorWebAuthnWarning": { - "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used. Supported platforms:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Web vault and browser extensions on a desktop/laptop with a WebAuthn supported browser (Chrome, Opera, Vivaldi, or Firefox with FIDO U2F turned on)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Your Bitwarden two-step login recovery code" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "If you cannot access your account through your normal two-step login methods, you can use your two-step login recovery code to turn off all two-step providers on your account." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Recover account two-step login" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Set requirements for master password strength." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Require two-step login" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Set requirements for password generator." }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." - }, "masterPasswordPolicyInEffect": { "message": "One or more organization policies require your master password to meet the following requirements:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Organization owners and admins are exempt from this policy's enforcement." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "File" }, "sendTypeText": { "message": "Text" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Delete Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Are you sure you want to delete this Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "What type of Send is this?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maximum access count" }, - "maxAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Disabled" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Are you sure you want to remove the password?" }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, - "disableThisSend": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "All Sends" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Pending deletion" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Expired" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "The following organization policies are currently in effect:" - }, - "sendDisableHideEmailInEffect": { - "message": "Users are not allowed 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." - }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Remove individual ownership for organization users" }, - "textHiddenByDefault": { - "message": "When accessing the Send, hide the text by default", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to Send." - }, - "sendFileDesc": { - "message": "The file you want to Send." - }, - "copySendLinkOnSave": { - "message": "Copy the link to share this Send to my clipboard upon save." - }, - "sendLinkLabel": { - "message": "Send link", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "To verify your 2FA please click the button below." }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn is not supported in this browser." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, - "regenerateUsername": { - "message": "Regenerate username" - }, "generateUsername": { "message": "Generate username" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Username type" - }, "plusAddressedEmail": { "message": "Plus addressed email", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Hostname", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API access token" - }, "deviceVerification": { "message": "Device verification" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Number of users" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "pickAnAvatarColor": { "message": "Pick an avatar color" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "No collection" }, - "canView": { - "message": "Can view" - }, - "canViewExceptPass": { - "message": "Can view, except passwords" - }, - "canEdit": { - "message": "Can edit" - }, - "canEditExceptPass": { - "message": "Can edit, except passwords" - }, "noCollectionsAdded": { "message": "No collections added" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domain" - }, "alreadyHaveAccount": { "message": "Already have an account?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index 7b77ae9d58f..65c42a0cbf0 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Kritične aplikacije" }, + "noCriticalAppsAtRisk": { + "message": "Nema kritičnih aplikacija u opasnosti" + }, "accessIntelligence": { "message": "Pristup inteligenciji" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "Rizični korisnici" }, + "atRiskMembersWithCount": { + "message": "Izloženi članovi ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Izložene aplikacije ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "Ovi članovi se prijavljuju u aplikacije slabim, izloženim ili ponovno korištenim lozinkama." + }, + "atRiskApplicationsDescription": { + "message": "Ove aplikacije imaju slabe, izložene ili ponovno korištene lozinke." + }, + "atRiskMembersDescriptionWithApp": { + "message": "Ovi članovi se u $APPNAME$ prijavljuju slabim, izloženim ili ponovno korištenim lozinkama.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Ukupno korisnika" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Ukupno aplikacija" }, + "unmarkAsCriticalApp": { + "message": "Odznači kao kritičnu aplikaciju" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Kritična aplikacija uspješno odznačena" + }, "whatTypeOfItem": { "message": "Koja je ovo vrsta stavke?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Bilješke" }, + "privateNote": { + "message": "Privatna bilješka" + }, "note": { "message": "Bilješka" }, @@ -196,7 +241,7 @@ "message": "Povijest stavke" }, "authenticatorKey": { - "message": "Kôd za provjeru" + "message": "Ključ autentifikatora" }, "autofillOptions": { "message": "Postavke auto-ispune" @@ -366,7 +411,7 @@ "message": "Bitwarden može pohraniti i ispuniti kodove za dvostruku autentifikaciju. Kopiraj i zalijepi ključ u ovo polje." }, "totpHelperWithCapture": { - "message": "Bitwarden može pohraniti i ispuniti kodove za dvostruku autentifikaciju. Odaberi ikonu kamere i označi QR kôd za provjeru autentičnosti ove web stranice ili kopiraj i zalijepi ključ u ovo polje." + "message": "Bitwarden može pohraniti i ispuniti kodove provjeru dvostruke autentifikacije. Odaberi ikonu kamere i označi QR kôd za provjeru autentičnosti ove web stranice ili kopiraj i zalijepi ključ u ovo polje." }, "learnMoreAboutAuthenticators": { "message": "Više o autentifikatorima" @@ -383,6 +428,9 @@ "dragToSort": { "message": "Povuci za sortiranje" }, + "dragToReorder": { + "message": "Povuci za premještanje" + }, "cfTypeText": { "message": "Tekst" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Uredi mapu" }, + "editWithName": { + "message": "Uredi $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "Nova mapa" + }, + "folderName": { + "message": "Naziv mape" + }, + "folderHintText": { + "message": "Ugnijezdi mapu dodavanjem naziva roditeljske mape i znaka kroz. Npr. Mreže/Forumi" + }, + "deleteFolderPermanently": { + "message": "Sigurno želiš trajno izbrisati ovu mapu?" + }, "baseDomain": { "message": "Primarna domena", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Naziv stavke" }, - "cannotRemoveViewOnlyCollections": { - "message": "S dopuštenjima samo za prikaz ne možeš ukloniti zbirke: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "npr.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filtriraj" }, - "moveSelectedToOrg": { - "message": "Premjesti odabrano u Organizaciju" - }, "deleteSelected": { "message": "Obriši odabrano" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Odabrane stavke premještene u $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Stavke premještene u $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "Ne" }, + "location": { + "message": "Lokacija" + }, "loginOrCreateNewAccount": { "message": "Prijavi se ili stvori novi račun za pristup svojem sigurnom trezoru." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Prijavi se u Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Unesi kôd poslan e-poštom" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Unesi kôd iz svoje aplikacije za autentifikaciju" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Za autentifikaciju dodirni svoj YubiKey" + }, "authenticationTimeout": { "message": "Istek vremena za autentifikaciju" }, "authenticationSessionTimedOut": { "message": "Sesija za autentifikaciju je istekla. Ponovi proces prijave." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Potvrdi svoj identitet" }, + "weDontRecognizeThisDevice": { + "message": "Ne prepoznajemo ovaj uređaj. Za potvrdu identiteta unesi kôd poslan e-poštom." + }, + "continueLoggingIn": { + "message": "Nastavi prijavu" + }, + "whatIsADevice": { + "message": "Što je uređaj?" + }, + "aDeviceIs": { + "message": "Uređaj je jedinstvena instalacija Bitwarden aplikacije gdje si prijavljen/a. Ponovna instalacija, brisanje podataka aplikacije ili kolačića može rezultirati višestrukim prikazom uređaja." + }, "logInInitiated": { "message": "Pokrenuta prijava" }, + "logInRequestSent": { + "message": "Zahtjev poslan" + }, "submit": { "message": "Pošalji" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Unesi svoju adresu e-pošte računa i poslat ćemo ti tvoj podsjetnik" }, - "passwordHint": { - "message": "Podsjetnik za lozinku" - }, - "enterEmailToGetHint": { - "message": "Unesi adresu e-pošte svog računa za primitak podsjetnika glavne lozinke." - }, "getMasterPasswordHint": { "message": "Slanje podsjetnika glavne lozinke" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "Obavijest je poslana na tvoj uređaj." }, + "notificationSentDevicePart1": { + "message": "Otključaj Bitwarden na svojem uređaju ili na " + }, + "areYouTryingToAccessYourAccount": { + "message": "Pokušavaš li pristupiti svom računu?" + }, + "accessAttemptBy": { + "message": "$EMAIL$ pokušava pristupiti", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Potvrdi pristup" + }, + "denyAccess": { + "message": "Odbij pristup" + }, + "notificationSentDeviceAnchor": { + "message": "web trezoru" + }, + "notificationSentDevicePart2": { + "message": "Provjeri slaže li se jedinstvena fraza s ovdje prikazanom prije odobravanja." + }, + "notificationSentDeviceComplete": { + "message": "Otključaj Bitwarden na svojem uređaju. Provjeri slaže li se jedinstvena fraza s ovdje prikazanom prije odobravanja." + }, "aNotificationWasSentToYourDevice": { "message": "Obavijest je poslana na tvoj uređaj" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Provjeri je li trezor otključan i slaže li se jedinstvena fraza s drugim uređajem" - }, "versionNumber": { "message": "Verzija $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Zapamti me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Ne pitaj na ovom uređaju idućih 30 dana" + }, "sendVerificationCodeEmailAgain": { "message": "Ponovno slanje kontrolnog koda e-poštom" }, "useAnotherTwoStepMethod": { "message": "Koristiti drugi način prijave dvostrukom autentifikacijom" }, + "selectAnotherMethod": { + "message": "Odaberi drugi način", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Koristi kôd za oporavak" + }, "insertYubiKey": { "message": "Umetni svoj YubiKey u USB priključak računala, a zatim dodirni njegovu tipku." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Mogućnosti prijave dvostrukom autentifikacijom" }, + "selectTwoStepLoginMethod": { + "message": "Odaberi način prijave dvostrukom autentifikacijom" + }, "recoveryCodeDesc": { "message": "Izgubljen je pristup uređaju za prijavu dvostrukom autentifikacijom? Koristi svoj kôd za oporavak za onemogućavanje svih pružatelja usluga prijave dvostrukom autentifikacijom na svom računu." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(migrirano s FIDO)" }, + "openInNewTab": { + "message": "Otvori u novoj kartici" + }, "emailTitle": { "message": "E-pošta" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Odaberi organizaciju u koju želiš premjestiti ovu stavku. Premještanje prenosi vlasništvo stavke na organizaciju. Nakon premještanja više nećeš biti izravni vlasnik ove stavke." }, - "moveManyToOrgDesc": { - "message": "Odaberi organizaciju u koju želiš premjestiti ovu stavku. Premještanje prenosi vlasništvo stavke na organizaciju. Nakon premještanja više nećeš biti izravni vlasnik ove stavke." - }, "collectionsDesc": { "message": "Uredi zbirke s kojima se ova stavka koristi. Samo korisnici organizacije s pristupom ovim zbirkama će ih moći vidjeti." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "$MOVEABLE_COUNT$ od $COUNT$ odabranih stavki može biti premješteno u Organiziaciju; $NONMOVEABLE_COUNT$ nije moguće premjestiti.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Kôd za provjeru (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Izbjegavaj dvosmislene znakove", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Ponovno generiraj lozinku" - }, "length": { "message": "Duljina" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Molimo, ponovno se prijavi." }, + "currentSession": { + "message": "Trenutna sesija" + }, + "requestPending": { + "message": "Zahtjev u tijeku" + }, "logBackInOthersToo": { "message": "Molimo, ponovno se prijavi. Ako koristiš druge aplikacije Bitwarden i u njima napravi odjavu/prijavu." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "OPASNA zona!" }, - "dangerZoneDesc": { - "message": "Pažljivo, ove akcije su konačne i ne mogu se poništiti!" - }, - "dangerZoneDescSingular": { - "message": "Pažljivo, ova radnja se ne može poništiti!" - }, "deauthorizeSessions": { "message": "Deautoriziraj sesije" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Ako nastaviš, trenutna sesija će biti zatvorena, što će zahtijevati ponovnu prijavu uklljučujući i prijavu dvostrukom autentifikacijom, ako je ona aktivna. Aktivne sesije na drugim uređajima mogu ostati aktivne još jedan sat." }, + "newDeviceLoginProtection": { + "message": "Prijava novog uređaja" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Isključi zaštitu prijave novih uređaja" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Uključi zaštitu prijave novih uređaja" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Za prestanak dobivanja e-pošte kada se prijaviš s novog uređaja, nastavi niže." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Za dobivanje e-pošte kada se prijaviš s novog uređaja, nastavi niže." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "Ako je zaštita prijave novih uređaja isključena, bilo tko s tvojom glavnom lozinkom može pristupiti tvom računu s bilo kojeg uređaja. Za zaštitu svog računa bez potvrde e-poštom, uključi dvostruku autentifikaciju." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "Promjene zaštite prijave novih uređaja su spremljene" + }, "sessionsDeauthorized": { "message": "Sve sesije deautorizirane" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Uključivanje prijave dvostrukom autentifikacijom može ti trajno onemogućiti pristup Bitwarden računu. Kôd za oporavak ti omogućuje pristup računu u slučaju kada više ne možeš koristiti redovnog pružatelja prijave dvostrukom autentifikacijom (npr. izgubiš svoj uređaj). Bitwarden podrška neće ti moći pomoći ako izgubiš pristup svojem računu. Savjetujemo da zapišeš ili ispišeš kôd za oporavak i spremiš ga na sigurno mjesto." }, + "yourSingleUseRecoveryCode": { + "message": "Tvoj jednokratni kôd za oporavak može se koristiti za isključivanje prijave dvostruke autentifikacije u slučaju da izgubiš pristup svom davatelju usluge dvostruke autentifikacije. Bitwarden preporučuje da zapišeš kôd za oporavak i čuvaš ga na sigurnom mjestu." + }, "viewRecoveryCode": { "message": "Kôd za oporavak" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Upravljaj" }, - "canManage": { - "message": "Može upravljati" + "manageCollection": { + "message": "Upravljaj zbirkom" + }, + "viewItems": { + "message": "Prikaz stavke" + }, + "viewItemsHidePass": { + "message": "Prikaz stavke, skrivene lozinke" + }, + "editItems": { + "message": "Uredi stavke" + }, + "editItemsHidePass": { + "message": "Uredi stavke, skrivene lozinke" }, "disable": { "message": "Onemogući" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Opozovi pristup" }, + "revoke": { + "message": "Opozovi" + }, "twoStepLoginProviderEnabled": { "message": "Ovaj pružatelj prijave dvostrukom autentifikacijom je omogućen na tvojem računu." }, @@ -2141,7 +2273,7 @@ "message": "Nastavi na bitwarden.com?" }, "twoStepContinueToBitwardenUrlDesc": { - "message": "Bitwarden autentifikator omogućuje pohranu ključeva za autentifikaciju i generiranje TOTP kodova za dvostruku autentifikaciju. Saznaj više na bitwarden.com." + "message": "Bitwarden autentifikator omogućuje pohranu ključeva za autentifikator i generiranje TOTP kodova za provjeru dvostruke autentifikacije. Saznaj više na bitwarden.com." }, "twoStepAuthenticatorScanCodeV2": { "message": "Skeniraj donji QR kôd svojom autentifikatorskom aplikacijom ili unesi ključ." @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "Došlo je do pogreške kod očitavanja sigurnosnog ključa. Pokušaj ponovno." }, - "twoFactorWebAuthnWarning": { - "message": "Zbog platformskih ograničenja, WebAuthn nije moguće koristiti s Bitwarden aplikacijama na svim platformama. Za pristup računu kada nije moguće koristiti WebAuthn, trebalo bi uključiti drugog pružatelja prijave dvostrukom autentifikacijom. Platforme na kojima je WebAuthn podržan:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Web trezor i proširenja preglednika na stolnim/prijenosnim računalima s WebAuthn podržanim preglednikom (npr. Chrome, Opera, Vivaldi ili Firefox s omogućenim WebAuthn)." + "twoFactorWebAuthnWarning1": { + "message": "Zbog platformskih ograničenja, WebAuthn nije moguće koristiti sa svim Bitwarden aplikacijama. Uključi drugi način dvostruke autentifikacije za pristup računu kada se ne može koristiti WebAuthn." }, "twoFactorRecoveryYourCode": { "message": "Tvoj kôd za oporavak Bitwarden prijave dvostrukom autentifikacijom" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Imaš jednu preostalu pozivnicu." + }, + "inviteZeroEmailDesc": { + "message": "Nemaš preostalih pozivnica." + }, "userUsingTwoStep": { "message": "Ovaj korisnik upotrebljava prijavu u dva koraka za zaštitu svog računa." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "SSO odspojen." + }, "unlinkedSsoUser": { "message": "Odspojen SSO za korisnika $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Uređaj" }, + "loginStatus": { + "message": "Status prijave" + }, + "firstLogin": { + "message": "Prva prijava" + }, + "trusted": { + "message": "Pouzdan" + }, + "needsApproval": { + "message": "Zahtjeva odobrenje" + }, + "areYouTryingtoLogin": { + "message": "Pokušavaš li se prijaviti?" + }, + "logInAttemptBy": { + "message": "$EMAIL$ se pokušava prijaviti", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Vrsta uređaja" + }, + "ipAddress": { + "message": "IP Adresa" + }, + "confirmLogIn": { + "message": "Potvrdi prijavu" + }, + "denyLogIn": { + "message": "Odbij prijavu" + }, + "thisRequestIsNoLongerValid": { + "message": "Ovaj zahtjev više nije valjan." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Prijava za $EMAIL$ potvrđena na uređaju $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "Odbijena je prijava na drugom uređaju. Ako si ovo stvarno ti, pokušaj se ponovno prijaviti uređajem." + }, + "loginRequestHasAlreadyExpired": { + "message": "Zahtjev za prijavu je već istekao." + }, + "justNow": { + "message": "Upravo" + }, + "requestedXMinutesAgo": { + "message": "Zatraženo prije $MINUTES$ minute/a", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Stvaranje računa na" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "Koristiš nepodržani preglednik. Web trezor možda neće ispravno raditi." }, + "youHaveAPendingLoginRequest": { + "message": "Na čekanju je zahtjev za prijavu s drugog uređaja." + }, + "reviewLoginRequest": { + "message": "Pregledaj zahtjev za prijavu" + }, "freeTrialEndPromptCount": { "message": "Besplatno probno razdoblje završava za $COUNT$ dan/a.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "Ako ne možeš pristupiti računu koristeći svoje redovne načine prijave dvostrukom autentifikacijom, možeš iskoristiti svoj kôd za oporavak kako bi se u potpunosti onesposobili svi pružatelji prijave dvostrukom autentifikacijom na tvojem računu." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Prijavi se pomoću jednokratnog kôda za oporavak. Ovo će isključiti sve pružatelje usluga dvostruke autentifikacije na tvom računu." + }, "recoverAccountTwoStep": { "message": "Oporavi račun prijave dvostrukom autentifikacijom" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Ažuriranje ključa za šifriranje ne može se nastaviti" }, + "editFieldLabel": { + "message": "Uredi $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Ponovno poredaj $LABEL$. Koristi tipke sa strelicom za pomicanje stavke gore ili dolje.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ pomaknuto gore, pozicija $INDEX$ od $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ pomaknuto dolje, pozicija $INDEX$ od$LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "Prilikom ažuriranja tvojeg ključa za šifriranje, mape se nisu mogle dešifrirati. Za nastavak ažuriranja, tvoje mape moraju biti izbrisane. Nijedna stavka iz trezora neće biti izbrisana ako nastaviš." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Postavi pravila sigurnosti koja mora zadovoljiti glavna lozinka." }, + "passwordStrengthScore": { + "message": "Ocjena jačine lozinke: $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Zahtijevaj prijavu dvostrukom autentifikacijom" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Postavi pravila sigurnosti koje mora zadovoljiti generator lozinki." }, - "passwordGeneratorPolicyInEffect": { - "message": "Jedna ili više organizacijskih pravila utječe na postavke generatora." - }, "masterPasswordPolicyInEffect": { "message": "Jedna ili više organizacijskih pravila zahtijeva da tvoja glavna lozinka ispunjava sljedeće uvjete:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Pravilo neće biti primjenjeno na Vlasnike i Administratore." }, + "limitSendViews": { + "message": "Ograniči broj pogleda" + }, + "limitSendViewsHint": { + "message": "Nakon dosegnutog broja, nitko neće moći pogledati Send.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "Preostalo pogleda: $ACCESSCOUNT$", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Detalji Senda", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Tekst za dijeljenje" + }, "sendTypeFile": { "message": "Datoteka" }, "sendTypeText": { "message": "Tekst" }, + "sendPasswordDescV3": { + "message": "Dodaj neobaveznu lozinku za pristup ovom Sendu.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Stvori novi Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Izbriši Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Sigurno želiš izbrisati ovaj Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "Koja je ovo vrsta Send-a?", + "deleteSendPermanentConfirmation": { + "message": "Sigurno želiš trajno izbrisati ovaj Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Datum brisanja" }, - "deletionDateDesc": { - "message": "Send će biti trajno izbrisan navedenog datuma.", + "deletionDateDescV2": { + "message": "Send će na ovaj datum biti trajno izbrisan.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Najveći proj pristupanja" }, - "maxAccessCountDesc": { - "message": "Ako je uključeno, korisnici neće moći pristupiti ovom Sendu nakon što se postigne najveći broj pristupanja.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Trenutni broj pristupanja" - }, - "sendPasswordDesc": { - "message": "Ako želiš, možeš za pristup Sendu zahtijevati lozinku (nije obavezno).", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Privatne bilješke o Sendu.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Onemogućeno" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Sigurno želiš ukloniti lozinku?" }, - "hideEmail": { - "message": "Sakrij moju adresu e-pošte od primatelja." - }, - "disableThisSend": { - "message": "Onemogući ovaj Send da mu nitko ne može pristupiti.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Svi Sendovi" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Čeka brisanje" }, + "hideTextByDefault": { + "message": "Zadano sakrij tekst" + }, "expired": { "message": "Isteklo" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "Organizacijska pravila trenutno na snazi:" - }, - "sendDisableHideEmailInEffect": { - "message": "Korisnicima nije dopušteno skrivati adresu e-pošte od primatelja kod stvaranja ili uređivanja Senda.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "modifiedPolicyId": { "message": "Izmijenjena polica $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Onemogući osobno vlasnišvo za organizacijske korisnike" }, - "textHiddenByDefault": { - "message": "Zadano sakrij tekst pri pristupanju Sendu", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Nadimak za ovaj Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Tekst kojeg želiš poslati." - }, - "sendFileDesc": { - "message": "Datoteka koju želiš poslati." - }, - "copySendLinkOnSave": { - "message": "Kopiraj vezu za dijeljenje ovog Senda nakon spremanja." - }, - "sendLinkLabel": { - "message": "Veza na Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "Došlo je do greške kod spremanja vaših datuma isteka i brisanja." }, + "hideYourEmail": { + "message": "Sakriti adresu e-pošte od primatelja." + }, "webAuthnFallbackMsg": { "message": "Za ovjeru tvoje 2FA, odaberi donju tipku." }, "webAuthnAuthenticate": { "message": "Ovjeri WebAuthn" }, + "readSecurityKey": { + "message": "Pročitaj sigurnosni ključ" + }, + "awaitingSecurityKeyInteraction": { + "message": "Čekanje na interakciju sa sigurnosnim ključem..." + }, "webAuthnNotSupported": { "message": "WebAuthn nije podržan u ovom pregledniku." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Greška" }, + "decryptionError": { + "message": "Pogreška pri dešifriranju" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nije mogao dešifrirati sljedeće stavke trezora." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktiraj službu za korisnike", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "kako bi izbjegli gubitak podataka.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Upravljanje korisnicima mora također biti uključeno s dozvolom za Upravljanje ponovnim postavljanjem lozinke" }, @@ -6516,15 +6791,6 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "Što želiš generirati?" - }, - "passwordType": { - "message": "Tip lozinke" - }, - "regenerateUsername": { - "message": "Ponovno generiraj korisničko ime" - }, "generateUsername": { "message": "Generiraj korisničko ime" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Tip korisničkog imena" - }, "plusAddressedEmail": { "message": "Plus adresa e-pošte", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Koristi konfigurirani catch-all sandučić svoje domene." }, + "useThisEmail": { + "message": "Koristi ovu e-poštu" + }, "random": { "message": "Nasumično", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ je odbio tvoj zahtjev. Obrati se svom pružatelju usluga za pomoć.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ je odbio tvoj zahtjev: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Nije moguće dobiti $SERVICENAME$ maskirani ID računa e-pošte.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Naziv poslužitelja", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "Token za API pristup" - }, "deviceVerification": { "message": "Provjera uređaja" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Za tvoj račun je potrebna Duo dvostruka autentifikacija." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Za tvoj je račun potrebna Duo prijava u dva koraka. Za dovršetak prijave, slijedi daljnje korake." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Prati korake za dovršetak prijave." + }, "launchDuo": { "message": "Pokreni Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Broj korisnika" }, - "loggingInAs": { - "message": "Prijava kao" - }, - "notYou": { - "message": "Nisi ti?" - }, "pickAnAvatarColor": { "message": "Odaberi boju avatara" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "Nema zbirke" }, - "canView": { - "message": "Može vidjeti" - }, - "canViewExceptPass": { - "message": "Može vidjeti, osim lozinke" - }, - "canEdit": { - "message": "Može urediti" - }, - "canEditExceptPass": { - "message": "Može urediti, osim lozinke" - }, "noCollectionsAdded": { "message": "Niti jedna zbirka nije dodana" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Zatraži odobrenje administratora" }, - "approveWithMasterPassword": { - "message": "Odobri glavnom lozinkom" - }, "trustedDeviceEncryption": { "message": "Enkripcija pouzdanog uređaja" }, "trustedDevices": { "message": "Pouzdani uređaji" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Jednom autentificirani, korisnici će moći dešifrirati podatke u trezoru koristeći ključ spremljen na njihovom uređaju. ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Članovi neće trebati glavnu lozinku kada se prijavljuju putem SSO-a. Glavnu lozinku zamjenjuje ključem za šifriranje pohranjen na uređaju, što taj uređaj čini pouzdanim. Prvi uređaj na kojem član kreira svoj račun i s kojeg se prijavi bit će pouzdan. Nove uređaje morat će odobriti ili postojeći pouzdani uređaj ili administrator. Pravilo", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "pravilo Isključive organizacije", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "isključive organizacije,", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": ",", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "pravilo obavezne SSO autentifikacije", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "obavezne SSO prijave", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "pravilo i ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "i", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "pravilo administracije povrata računa", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "administracije oporavka računa", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "s automatskim učlanjenjem će se uključiti kada se koristi ova opcija.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "biti će uključeni ako se koristi ova opcija.", + "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": "Moraš postaviti glavnu lozinku jer su dopuštenja tvoje organizacije ažurirana.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Odobri zahtjev" }, + "deviceApproved": { + "message": "Uređaj odobren" + }, + "deviceRemoved": { + "message": "Uređaj uklonjen" + }, + "removeDevice": { + "message": "Ukloni uređaj" + }, + "removeDeviceConfirmation": { + "message": "Sigurno želiš ukoniti ovaj uređaj?" + }, "noDeviceRequests": { "message": "Nema zahtjeva na čekanju" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Tvoj zahtjev je poslan administratoru." }, - "youWillBeNotifiedOnceApproved": { - "message": "Dobiti ćeš obavijest kada bude odobreno." - }, "troubleLoggingIn": { "message": "Problem s prijavom?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Omogući brisanje zbirki samo vlasnicima i adminima" }, + "limitItemDeletionDesc": { + "message": "Ograniči brisanje stavke samo članovima koji imaju dozvolu „Moguće upravljanje”" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Vlasnici i admini mogu upravljati svim zbirkama i stavkama" }, @@ -8494,9 +8778,6 @@ "message": "URL vlastitog poslužitelja", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domene" - }, "alreadyHaveAccount": { "message": "Već imaš račun?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "Nemaš pristup za upravljanje ovom zbirkom." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Neodstaju prava „Moguće upravljanje”" + "grantManageCollectionWarningTitle": { + "message": "Nedostaju prava za upravljanje zbirkom" }, - "grantAddAccessCollectionWarning": { - "message": "Dodijeli prava „Moguće upravljanje” kako bi dozvolili puni pristup zbirkama uključujući brisanje." + "grantManageCollectionWarning": { + "message": "Dodijeli potpuna prava upravljanja zbirkom što uključuje i brisanje." }, "grantCollectionAccess": { "message": "Dodijeli grupama i članovima pristup ovoj zbirki." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Konfiguriraj upravljanje uređajima za Bitwarden pomoću vodiča za implementaciju za svoju platformu." }, + "deviceIdMissing": { + "message": "Nedostaje ID uređaja" + }, + "deviceTypeMissing": { + "message": "Nedostaje vrsta uređaja" + }, + "deviceCreationDateMissing": { + "message": "Nedostaje datum stvaranja uređaja" + }, + "desktopRequired": { + "message": "Potrebno stolno računalo" + }, + "reopenLinkOnDesktop": { + "message": "Otvori ovu poveznicu na stolnom računalu." + }, "integrationCardTooltip": { "message": "Pokreni vodič za implementaciju $INTEGRATION$.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "mjesečno po korisniku" }, + "monthPerMemberBilledAnnually": { + "message": "mjesečno po članu, naplata godišnje" + }, "seats": { "message": "Mjesta" }, @@ -9272,16 +9571,16 @@ "message": "Ažurirane porezne informacije" }, "billingInvalidTaxIdError": { - "message": "Invalid tax ID, if you believe this is an error please contact support." + "message": "Neispravni porezni broj. Ako misliš da je broj ispravan, kontaktiraj podršku." }, "billingTaxIdTypeInferenceError": { - "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + "message": "Nismo mogli provjeriti tvoj porezni broj. Ako misliš da je broj ispravan, kontaktiraj podršku." }, "billingPreviewInvalidTaxIdError": { - "message": "Invalid tax ID, if you believe this is an error please contact support." + "message": "Neispravni porezni broj. Ako misliš da je broj ispravan, kontaktiraj podršku." }, "billingPreviewInvoiceError": { - "message": "An error occurred while previewing the invoice. Please try again later." + "message": "Greška kod pregleda fakture. Pokušaj ponovno kasnije." }, "unverified": { "message": "Nepotvrđeno" @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Saznaj više o Bitwarden API" }, + "fileSend": { + "message": "Send datoteke" + }, "fileSends": { "message": "Send datoteke" }, + "textSend": { + "message": "Send teksta" + }, "textSends": { "message": "Send tekstovi" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Algoritam ključa" }, + "sshPrivateKey": { + "message": "Privatni ključ" + }, + "sshPublicKey": { + "message": "Javni ključ" + }, + "sshFingerprint": { + "message": "Otisak prsta" + }, "sshKeyFingerprint": { "message": "Otisak prsta" }, @@ -9774,10 +10088,6 @@ "message": "Uključi posebne znakove", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "! @ # $ % ^ & *", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Dodaj privitak" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Šifra uplate" }, + "cannotRemoveViewOnlyCollections": { + "message": "S dopuštenjima samo za prikaz ne možeš ukloniti zbirke: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Važna napomena" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Ukloni članove" }, + "devices": { + "message": "Uređaji" + }, + "deviceListDescription": { + "message": "Tvoj račun je prijavljen na svakom od ovih uređaja. Ako ne prepoznaješ uređaj, odmah ga ukloni." + }, + "deviceListDescriptionTemp": { + "message": "Tvoj račun je prijavljen na svakom od ovih uređaja." + }, "claimedDomains": { "message": "Potvrđene domene" }, @@ -10020,7 +10348,69 @@ "organizationNameMaxLength": { "message": "Naziv organizacije ne može biti duži od 50 znakova." }, - "resellerRenewalWarning": { + "sshKeyWrongPassword": { + "message": "Unesena lozinka nije ispravna." + }, + "importSshKey": { + "message": "Uvoz" + }, + "confirmSshKeyPassword": { + "message": "Potvrdi lozinku" + }, + "enterSshKeyPasswordDesc": { + "message": "Unesi lozinku za SSH ključ." + }, + "enterSshKeyPassword": { + "message": "Unesi lozinku" + }, + "invalidSshKey": { + "message": "SSH ključ nije valjan" + }, + "sshKeyTypeUnsupported": { + "message": "Tip SSH ključa nije podržan" + }, + "importSshKeyFromClipboard": { + "message": "Uvezi ključ iz međuspremnika" + }, + "sshKeyImported": { + "message": "SSH ključ uspješno uvezen" + }, + "copySSHPrivateKey": { + "message": "Kopiraj privatni ključ" + }, + "openingExtension": { + "message": "Otvaranje Bitwarden proširenja preglednika" + }, + "somethingWentWrong": { + "message": "Dogodila se greška..." + }, + "openingExtensionError": { + "message": "Nismo mogli otvoriti Bitwarden proširenje preglednika. Klikni na tipku za otvaranje." + }, + "openExtension": { + "message": "Otvori proširenje" + }, + "doNotHaveExtension": { + "message": "Nemaš Bitwarden proširenje preglednika?" + }, + "installExtension": { + "message": "Instaliraj proširenje" + }, + "openedExtension": { + "message": "Proširenje preglednika otvoreno" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Uspjšeno otvoreno Bitwarden proširenje preglednika. Sada možeš pregledati svoje rizične lozinke." + }, + "openExtensionManuallyPart1": { + "message": "Nismo mogli otvoriti Bitwarden proširenje preglednika. Otvori Bitwarden ikonu", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "na alatnoj traci.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { "message": "Tvoja će se pretplata uskoro obnoviti. Za neprekinutu uslugu, kontaktiraj $RESELLER$ za potvrdu obnove prije $RENEWAL_DATE$.", "placeholders": { "reseller": { @@ -10033,7 +10423,7 @@ } } }, - "resellerOpenInvoiceWarning": { + "resellerOpenInvoiceWarningMgs": { "message": "Za tvoju je pretplatu $ISSUED_DATE$ izdana faktura. Za neprekinutu uslugu, kontaktiraj $RESELLER$ za potvrdu obnove prije $DUE_DATE$.", "placeholders": { "reseller": { @@ -10050,7 +10440,7 @@ } } }, - "resellerPastDueWarning": { + "resellerPastDueWarningMsg": { "message": "Faktura za tvoju pretplatu nije plaćena. Za neprekinutu uslugu, kontaktiraj $RESELLER$ za potvrdu obnovu prije $GRACE_PERIOD_END$.", "placeholders": { "reseller": { @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organizacijska pretplata ponovno pokrenuta" + }, + "restartSubscription": { + "message": "Ponovno pokreni pretplatu" + }, + "suspendedManagedOrgMessage": { + "message": "Kontaktiraj $PROVIDER$ za pomoć.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administratori sada imaju mogućnost obrisati članski račun koji pripada potvrđenoj domeni." + }, + "deleteManagedUserWarningDesc": { + "message": "Ovo će obrisati članski račun uključujući sve stavke u trezoru. Ovo zamjenjuje prethodnu radnju „Ukloni”." + }, + "deleteManagedUserWarning": { + "message": "Brisanje je nova radnja!" + }, + "seatsRemaining": { + "message": "Imaš još $REMAINING$ preostalih sjedišta od $TOTAL$ dodijeljenih ovog organizaciji. Kontaktiraj svog pružatelja usluge za upravljanje pretplatom.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Postojeća organizacija" + }, + "selectOrganizationProviderPortal": { + "message": "Odaberi organizaciju koju želiš dodati na svoj portal pružatelja usluga." + }, + "noOrganizations": { + "message": "Nema organizaciji za prikaz" + }, + "yourProviderSubscriptionCredit": { + "message": "Tvoja pretplata davatelja usluga dobit će kredit za preostalo vrijeme u organizacijskoj pretplati." + }, + "doYouWantToAddThisOrg": { + "message": "IP Adresa", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Postojeća organizacija dodana" + }, + "assignedExceedsAvailable": { + "message": "Dodijeljene licence premašuju dostupne licence." + }, + "changeAtRiskPassword": { + "message": "Promijeni rizičnu lozinku" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Ukloni otključavanje PIN-om" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Ne dozvoli članovima otključavanje računa PIN-om." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ planovi nemaju pristup stvarnim zapisima događaja", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Omogući puni pristup zapisnicima događaja organizacije nadogradnjom na plan Teams ili Enterprise." + }, + "upgradeEventLogTitle": { + "message": "Nadogradi za stvarne podatke dnevnika događaja" + }, + "upgradeEventLogMessage": { + "message": "Ovi događaji su samo primjeri i ne odražavaju stvarne događaje unutar tvoje Bitwarden organizacije." + }, + "cannotCreateCollection": { + "message": "Besplatne organizacije mogu imati do 2 zbirke. Nadogradi na plaćeni plan za dodavanje više zbirki." } } diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index ecd7db04ca1..3d6a2e258a4 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Kritikus alkalmazások" }, + "noCriticalAppsAtRisk": { + "message": "Nincsenek veszélyben levő kritikus alkalmazások." + }, "accessIntelligence": { "message": "Elérés intelligencia" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "Veszélyes tagok" }, + "atRiskMembersWithCount": { + "message": "Veszélyes tagok ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Kockázatos alkalmazások ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "Ezek a tagok gyenge, nyílt vagy újrafelhasznált jelszavakkal jelentkeznek be az alkalmazásokba." + }, + "atRiskApplicationsDescription": { + "message": "Ezeknél az alkalmazásoknál gyenge, nyílt vagy újra felhasznált jelszavak vannak." + }, + "atRiskMembersDescriptionWithApp": { + "message": "Ezek a tagok gyenge, nyilvános vagy újrahasznált jelszavakkal jelentkeznek be $APPNAME$ alkalmazásba.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Összes tagok száma" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Összes alkalmazás" }, + "unmarkAsCriticalApp": { + "message": "Ktritkus alkalmazás jelölés eltávolítása" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "A ktritkus alkalmazás jelölés eltávolítása sikeres volt." + }, "whatTypeOfItem": { "message": "Milyen típusú elem ez?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Jegyzetek" }, + "privateNote": { + "message": "Személyes jegyzet" + }, "note": { "message": "Jegyzet" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Húzás a rendezéshez" }, + "dragToReorder": { + "message": "Átrendezés áthúzással" + }, "cfTypeText": { "message": "Szöveg" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Mappa szerkesztése" }, + "editWithName": { + "message": "$ITEM$: $NAME$ szerkesztése", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "Új mappa" + }, + "folderName": { + "message": "Mappanév" + }, + "folderHintText": { + "message": "Mappa beágyazása a szülőmappa nevének hozzáadásával, majd egy “/” karakterrel. Példa: Közösségi/Fórumok" + }, + "deleteFolderPermanently": { + "message": "Biztosan véglegesen törlésre kerüljön ez a mappa?" + }, "baseDomain": { "message": "Alap domain", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Elem neve" }, - "cannotRemoveViewOnlyCollections": { - "message": "Nem távolíthatók el a csak megtekintési engedéllyel bíró gyűjtemények: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "Példa:", "description": "Short abbreviation for 'example'." @@ -722,7 +786,7 @@ } }, "copySuccessful": { - "message": "A másolás sikeres volt." + "message": "A másolás sikeres volt" }, "copyValue": { "message": "Érték másolása", @@ -815,9 +879,6 @@ "filter": { "message": "Szűrő" }, - "moveSelectedToOrg": { - "message": "A kiválasztott áthelyezése szervezetbe" - }, "deleteSelected": { "message": "Kiválasztott törlése" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "A kiválasztott elemek átkerültek $ORGNAME$ szervezethez", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Az elemek áthelyezésre kerültek: $ORGNAME$", "placeholders": { @@ -958,16 +1010,16 @@ "message": "A bejelentkezési munkamenet lejárt." }, "restartRegistration": { - "message": "Regisztráció újra kezdése" + "message": "Regisztráció újrakezdése" }, "expiredLink": { - "message": "A hivatkozás lejárt." + "message": "A hivatkozás lejárt" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "Kezdd előlről a regisztrációt vagy próbálj belépni." }, "youMayAlreadyHaveAnAccount": { - "message": "Lehet hogy már rendelkezik fiókkal." + "message": "Lehet hogy már rendelkezel fiókkal" }, "logOutConfirmation": { "message": "Biztosan szeretnénk kijelentkezni?" @@ -984,6 +1036,9 @@ "no": { "message": "Nem" }, + "location": { + "message": "Hely" + }, "loginOrCreateNewAccount": { "message": "Bejelentkezés vagy új fiók létrehozása a biztonsági széf eléréséhez." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Bejelentkezés a Bitwardenbe" }, + "enterTheCodeSentToYourEmail": { + "message": "Adjuk meg az email címre elküldött kódot." + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Adjuk meg a hitelesítő alkalmazása által generált kódot." + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Nyomjuk meg a YubiKey-t a hitelesítéshez." + }, "authenticationTimeout": { "message": "Hitelesítési időkifutás" }, "authenticationSessionTimedOut": { "message": "A hitelesítési munkamenet időkifutással lejárt. Indítsuk újra a bejelentkezési folyamatot." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Személyazonosság ellenőrzése" }, + "weDontRecognizeThisDevice": { + "message": "Nem ismerhető fel ez az eszköz. Írjuk be az email címünkre küldött kódot a személyazonosság igazolásához." + }, + "continueLoggingIn": { + "message": "A bejelentkezés folytatása" + }, + "whatIsADevice": { + "message": "Mi az eszköz?" + }, + "aDeviceIs": { + "message": "Az eszköz a Bitwarden alkalmazás egyedi telepítése, amelyre bejelentkeztünk. Az alkalmazás adatok újratelepítése, törlése vagy a sütik törlése azt eredményezheti, hogy egy eszköz többször is megjelenhet." + }, "logInInitiated": { "message": "A bejelentkezés elindításra került." }, + "logInRequestSent": { + "message": "A kérés elküldésre került." + }, "submit": { "message": "Elküldés" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Adjuk meg fiók email címét és elküldésre kerül a jelszóra vonatkozó tipp." }, - "passwordHint": { - "message": "Jelszó emlékeztető" - }, - "enterEmailToGetHint": { - "message": "A fiók email címének megadása a mesterjelszó emlékeztető fogadásához." - }, "getMasterPasswordHint": { "message": "Mesterjelszó emlékeztető kérése" }, @@ -1254,7 +1327,7 @@ "message": "Email cím" }, "yourVaultIsLockedV2": { - "message": "A széf zárolva van." + "message": "A széf zárolva van" }, "yourAccountIsLocked": { "message": "A fiók zárolva van." @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "A rendszer értesítést küldött az eszközre." }, + "notificationSentDevicePart1": { + "message": "A Bitwarden zárolás feloldása az eszközön vagy: " + }, + "areYouTryingToAccessYourAccount": { + "message": "A fiókhoz próbálunk hozzáférni?" + }, + "accessAttemptBy": { + "message": "Bejelentkezési kísérlet $EMAIL$ segítségével", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Hozzáférés megerősítése" + }, + "denyAccess": { + "message": "Hozzáférés megtagadása" + }, + "notificationSentDeviceAnchor": { + "message": "webalkalmazás" + }, + "notificationSentDevicePart2": { + "message": "Jóváhagyás előtt győződjünk meg arról, hogy az ujjlenyomat kifejezés megegyezik az alábbi kifejezéssel." + }, + "notificationSentDeviceComplete": { + "message": "Oldjuk fel a Bitwarden zárolását az eszközön. Jóváhagyás előtt győződjünk meg arról, hogy az ujjlenyomat kifejezés megegyezik az alábbi kifejezéssel." + }, "aNotificationWasSentToYourDevice": { "message": "Egy értesítés lett elküldve az eszközre." }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Ellenőrizzük, hogy a széf feloldásra került és az ujjlenyomat kifejezés egyezik a másik eszközön levővel." - }, "versionNumber": { "message": "Verzió: $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Adatok megjegyzése" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Ne kérdezzen újra ezen az eszközön 30 napig" + }, "sendVerificationCodeEmailAgain": { "message": "Megerősítő kód ismételt elküldése emailben" }, "useAnotherTwoStepMethod": { "message": "Másik kétlépcsős bejelentkezés használata" }, + "selectAnotherMethod": { + "message": "Másik módszer választás", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Helyreállító kód használata" + }, "insertYubiKey": { "message": "A YubiKey beillesztése a számítógép USB portjába és a rajta levő gomb megnyomása." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Kétlépcsős bejelentkezés opciók" }, + "selectTwoStepLoginMethod": { + "message": "Kétlépcsős bejelentkezési mód használata" + }, "recoveryCodeDesc": { "message": "Elveszett a hozzáférés az összes kétlépcsős szolgáltatóhoz? A visszaállítókód használatával letilthatók fiókból a kétlépcsős szolgáltatók." }, @@ -1400,7 +1513,7 @@ "message": "YubiKey OTP biztonsági kulcs" }, "yubiKeyDesc": { - "message": "YubiKey használata a fiók eléréséhez. Működik a YubiKey 4, 4 Nano, 4C, és NEO eszközökkel." + "message": "Használj YubiKey 4, 5 vagy NEO eszközt." }, "duoDescV2": { "message": "Adjuk meg a Duo Security által generált kódot.", @@ -1420,11 +1533,14 @@ "message": "Biztonsági kulcs" }, "webAuthnDesc": { - "message": "Használjunk bármilyen WebAuthn engedélyezett biztonsági kulcsot a saját fiók eléréséhez." + "message": "Használd az eszközöd biometrikus azonosítását vagy egy FIDO2 kompatibilis biztonsági kulcsot." }, "webAuthnMigrated": { "message": "(FIDO-ból áthelyezve)" }, + "openInNewTab": { + "message": "Megnyitás új fülön" + }, "emailTitle": { "message": "Email cím" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Válasszunk egy szervezetet, ahová áthelyezni szeretnénk ezt az elemet. A szervezetbe áthelyezés átruházza az elem tulajdonjogát az adott szervezetre. Az áthelyezés után többé nem leszünk az elem közvetlen tulajdonosa." }, - "moveManyToOrgDesc": { - "message": "Válasszunk egy szervezetet, ahová áthelyezni szeretnénk ezeket az elemeket. A szervezetbe áthelyezés átruházza az elemek tulajdonjogát az adott szervezetre. Az áthelyezés után többé nem leszünk az elemek közvetlen tulajdonosa." - }, "collectionsDesc": { "message": "A megosztásra kerülő elem gyűjteményének szerkesztése. Csak az ezeket a gyűjteményeket elérő szervezeti felhasználók látják ezt az elemet." }, @@ -1471,7 +1584,7 @@ "message": "Biztos folytatni szeretnénk?" }, "moveSelectedItemsDesc": { - "message": "Choose a folder that you would like to add the $COUNT$ selected item(s) to.", + "message": "Válassz ki egy mappát, amihez ezt a(z) $COUNT$ kiválasztott elemet hozzá szeretnéd adni.", "placeholders": { "count": { "content": "$1", @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "$COUNT$ elem került kiválasztásra. $MOVEABLE_COUNT$ elem áthelyezhető szervezethezi, $NONMOVEABLE_COUNT$ nem.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Verification Code (TOTP)" }, @@ -1506,10 +1602,10 @@ "message": "UUID másolása" }, "errorRefreshingAccessToken": { - "message": "Access Token Refresh Error" + "message": "Hozzáférési token frissítés hiba" }, "errorRefreshingAccessTokenDesc": { - "message": "No refresh token or API keys found. Please try logging out and logging back in." + "message": "Nem található frissítőtoken vagy API kulcs. Kérlek próbálj kilépni és újra belépni." }, "warning": { "message": "Figyelmeztetés" @@ -1527,7 +1623,7 @@ "message": "Ez az exportálás titkosítatlan formátumban tartalmazza a titkos adatokat. Ne tároljuk vagy ne küldjük el az exportált fájlt nem biztonságos csatornákon (például emailben). A használat befejezése után azonnal töröljük." }, "encExportKeyWarningDesc": { - "message": "Ez az exportálás titkosítja az adatokat a fiók titkosítási kulcsával. Ha valaha a diók forgatási kulcsa más lesz, akkor újra exportálni kell, mert nem lehet visszafejteni ezt az exportálási fájlt." + "message": "Ez az exportálás titkosítja az adatokat a fiók titkosítási kulcsával. Ha valaha a fiók forgatási kulcsa más lesz, akkor újra exportálni kell, mert nem lehet visszafejteni ezt az exportálási fájlt." }, "encExportAccountWarningDesc": { "message": "A fiók titkosítási kulcsai minden Bitwarden felhasználói fiókhoz egyediek, ezért nem importálhatunk titkosított exportálást egy másik fiókba." @@ -1566,7 +1662,7 @@ "message": "Fájl jelszó megerősítés" }, "accountRestrictedOptionDescription": { - "message": "Használjuk a fiókjfelhasználónevéből és a fő jelszóból származó titkosítási kulcsot az exportálás titkosításához és az importálás korlátozásához csak az aktuális Bitwarden fiókra." + "message": "Használjuk a fiók felhasználónevéből és a mesterjelszóból származó titkosítási kulcsot az exportálás titkosításához és az importálás korlátozásához csak az aktuális Bitwarden fiókra." }, "passwordProtectedOptionDescription": { "message": "Hozzunk létre egy felhasználó által generált jelszót az exportálás védelme érdekében. Ezzel más fiókokban is használható exportálást hozhatunk létre." @@ -1613,9 +1709,6 @@ "message": "Félreérthető karakterek mellőzése", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Jelszó újragenerálása" - }, "length": { "message": "Hossz" }, @@ -1676,7 +1769,7 @@ "message": "Nincs megjeleníthető elem" }, "nothingGeneratedRecently": { - "message": "Mostanában nem lett semmi generálva." + "message": "Nem generáltál semmit mostanában" }, "clear": { "message": "Kiürítés", @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Ismételten be kell jelentkezni." }, + "currentSession": { + "message": "Jelenlegi munkamenet" + }, + "requestPending": { + "message": "Függőben lévő kérelem" + }, "logBackInOthersToo": { "message": "Ismételten be kell jelentkezni. Ha másik Bitwarden alkalmazásokat használunk, ott is jelentkezzünk ki és ismételten be." }, @@ -1782,21 +1881,36 @@ "dangerZone": { "message": "Veszélyes terület" }, - "dangerZoneDesc": { - "message": "Óvatosan! Ezeket a műveleteket nem lehet visszaállítani." - }, - "dangerZoneDescSingular": { - "message": "Óvatosan! Ezeket a műveleteket nem lehet visszaállítani." - }, "deauthorizeSessions": { "message": "Munkamenetek hitelesítésének eldobása" }, "deauthorizeSessionsDesc": { - "message": "Aggódunk a egy másik eszközön történő bejelentkezés miatt? Az alábbiakban ismertetett módon dobjuk el az összes hitelesítést az összes számítógépen és eszközön. Ez a biztonsági lépés akkor ajánlott, ha korábban nyilvános helyen levő számítógépet használtunk vagy véletlenül mentettünk jelszót egy olyan eszközön, amely nem a sajátunk. Ez a lépés törli az összes korábban megjegyzett kétlépéses bejelentkezési munkamenetet." + "message": "Aggódunk a egy másik eszközön történő bejelentkezés miatt? Az alábbiakban ismertetett módon dobjuk el az összes hitelesítést az összes számítógépen és eszközön. Ez a biztonsági lépés akkor ajánlott, ha korábban nyilvános helyen levő számítógépet használtunk vagy véletlenül mentettünk jelszót egy olyan eszközön, amely nem a sajátunk. Ez a lépés törli az összes korábban megjegyzett kétfaktoros bejelentkezési munkamenetet." }, "deauthorizeSessionsWarning": { "message": "A folytatásban s felhasználó kiléptetésre kerül az aktuális munkamenetből, szükséges az ismételt bejelentkezés. Ismételten megjelenik a kétlépcsős bejelentkezés, ha az engedélyezett. Más eszközök aktív munkamenetei akár egy óráig is aktívak maradhatnak." }, + "newDeviceLoginProtection": { + "message": "Új eszköz bejelentkezés" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Az új eszköz bejelentkezési védelmének kikapcsolása" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Az új eszköz bejelentkezési védelmének bekapcsolása" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Folytassuk az alábbiakkal a Bitwarden által küldendő ellenőrző emailek kikapcsolásához, amikor új eszközről jelentkezünk be." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Folytassuk az alábbiakkal a Bitwarden által küldendő ellenőrző emailek bekapcsolásához, amikor új eszközről jelentkezünk be." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "Ha az új eszköz bejelentkezési védelme ki van kapcsolva, a mesterjelszó birtokában bárki hozzáférhet fiókhoz bármilyen eszközről. Ha meg szeretnénk védeni fiókot ellenőrző emailek nélkül, állítsunk be kétlépcsős bejelentkezést." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "Az új eszköz bejelentkezés védelmi módosítások mentésre kerültek." + }, "sessionsDeauthorized": { "message": "Az összes munkamenet hitelesítése eldobásra került." }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "A kétlépcsős bejelentkezés engedélyezése véglegesen kizárhatja a felhasználót a Bitwarden fiókból. A helyreállítási kód lehetővé teszi a fiókjához való hozzáférést abban az esetben, ha már nem tudjuk használni a szokásos kétlépcsős bejelentkezési szolgáltatást (pl. készülék elvesztése). A Bitwarden támogatás nem tud segíteni abban az esetben, ha elveszítjük a hozzáférést a fiókhoz. Célszerű leírni vagy kinyomtatni a helyreállítási kódot és azt biztonságos helyen tartani." }, + "yourSingleUseRecoveryCode": { + "message": "Az egyszer használatos helyreállítási kóddal kikapcsolhatjuk a kétlépcsős bejelentkezést abban az esetben, ha elveszítjük a hozzáférést a kétlépcsős bejelentkezési szolgáltatóhoz. A Bitwarden azt javasolja, hogy írjuk le a helyreállítási kódot és tartsuk biztonságos helyen." + }, "viewRecoveryCode": { "message": "Helyreállító kód megtekintése" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Kezelés" }, - "canManage": { - "message": "Kezelhet" + "manageCollection": { + "message": "Gyűjtemény kezelése" + }, + "viewItems": { + "message": "Elemek megtekintése" + }, + "viewItemsHidePass": { + "message": "Elemek megtekintése, rejtett jelszavak" + }, + "editItems": { + "message": "Elemek szerkesztése" + }, + "editItemsHidePass": { + "message": "Elemek szerkesztése, rejtett jelszavak" }, "disable": { "message": "Letiltás" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Hozzáférés visszavonása" }, + "revoke": { + "message": "Visszavonás" + }, "twoStepLoginProviderEnabled": { "message": "Ez a kétlépéses bejelentkezés szolgáltató már engedélyezett a fiókon." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "Probléma lépett fel a biztonsági kulcs olvasásakor. Próbáljuk újra." }, - "twoFactorWebAuthnWarning": { - "message": "A platform korlátozások miatt nem lehetséges minden Bitwarden alkalmazásban YubiKey eszközt használni. Engedélyezzünk egy másik kétlépcsős bejelentkezés szolgáltatót, hogy hozzáférhessünk a fiókhoz akkor is, ha a YubiKey nem használható. Támogatott platformok:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Web széf és böngésző kiegészítők asztali gépen / laptopon engedélyezett U2F böngészővel (Chrome, Opera, Vivaldi, vagy Firefox bekapcsolt FIDO U2F-fel)." + "twoFactorWebAuthnWarning1": { + "message": "A platform korlátozások miatt nem lehetséges minden Bitwarden alkalmazásban a WebAuthn használata. Be kell üzemelni egy másik kétlépcsős bejelentkezés szolgáltatót, hogy hozzáférhessünk a fiókhoz akkor is, ha a WebAuthn nem használható." }, "twoFactorRecoveryYourCode": { "message": "Bitwarden kétlépcsős bejelentkezés helyreállító kód" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "1 meghívó maradt." + }, + "inviteZeroEmailDesc": { + "message": "0 meghívó maradt." + }, "userUsingTwoStep": { "message": "Ez a felhasználó kétlépcsős bejelentkezést használ fiókja védelmére." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Az SSO szétkapcsolásra került." + }, "unlinkedSsoUser": { "message": "SSO szétkapcsolva $ID$ felhasználónál.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Eszköz" }, + "loginStatus": { + "message": "Bejelentkezési állapot" + }, + "firstLogin": { + "message": "Első bejelentkezés" + }, + "trusted": { + "message": "Megbízható" + }, + "needsApproval": { + "message": "Jóváhagyást igényel" + }, + "areYouTryingtoLogin": { + "message": "Megpróbálunk bejelentkezni?" + }, + "logInAttemptBy": { + "message": "Bejelentkezési kísérlet $EMAIL$ segítségével", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Eszköz típus" + }, + "ipAddress": { + "message": "IP cím" + }, + "confirmLogIn": { + "message": "Bejelentkezés megerősítése" + }, + "denyLogIn": { + "message": "Bejelentkezés megtagadása" + }, + "thisRequestIsNoLongerValid": { + "message": "A kérés a továbbiakban már nem érvényes." + }, + "logInConfirmedForEmailOnDevice": { + "message": "A bejelelentketés $EMAIL$ email címmel megerősítésre került $DEVICE$ eszközön.", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "Megtagadásra került egy bejelentkezési kísérletet egy másik eszközről. Ha valóban mi voltunk, próbáljunk meg újra bejelentkezni az eszközzel." + }, + "loginRequestHasAlreadyExpired": { + "message": "A bejelentkezési kérés már lejárt." + }, + "justNow": { + "message": "Éppen most" + }, + "requestedXMinutesAgo": { + "message": "Kérve $MINUTES$ perccel ezelőtt", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Fiók létrehozása:" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "Nem támogatott böngészőt használunk. Előfordulhat, hogy a webes széf nem működik megfelelően." }, + "youHaveAPendingLoginRequest": { + "message": "Függőben lévő bejelentkezési kérelem van egy másik eszközről." + }, + "reviewLoginRequest": { + "message": "Bejelentkezési kérés áttekintése" + }, "freeTrialEndPromptCount": { "message": "Az ingyenes próbaidőszak $COUNT$ nap múlva ér véget.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "Ha nem férünk hozzá a fiókhoz a normál kétlépcsős bejelentkezési módokkal, használhatjuk a kétlépcsős bejelentkezés helyreállító kódot a fiókra vonatkozó összes kétlépcsős szolgáltató kikapcsolásához." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Jelentkezzünk be lentebb az egyszer használatos helyreállítási kóddal. Ezzel kikapcsoljuk az összes kétlépcsős szolgáltatót a fiókban." + }, "recoverAccountTwoStep": { "message": "Fiók kétlépcsős bejelentkezés helyreállítása" }, @@ -4130,7 +4347,7 @@ "message": "Állítsunk be helykorlátot az előfizetéshez. Ha elérjük ezt a korlátot, nem tudunk új felhasználókat meghívni." }, "limitSmSubscriptionDesc": { - "message": "Állítsunk be egy korlátot a Secrets Manger előfizetéshez. Ha elérjük ezt a korlátot, nem tudunk új tagokat meghívni." + "message": "Állíts be egy korlátot a Secrets Manger előfizetéshez. Ha eléred ezt a korlátot, nem tudsz új tagokat meghívni." }, "maxSeatLimit": { "message": "Maximális helykorlát (opcionális)", @@ -4271,10 +4488,62 @@ } }, "encryptionKeyUpdateCannotProceed": { - "message": "Encryption key update cannot proceed" + "message": "A titkosítókulcs frissítése nem végrehajtható" + }, + "editFieldLabel": { + "message": "$LABEL$ szerkesztése", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "$LABEL$ átrendezése. A nyílbillentyűkkel mozgassuk az elemet felfelé vagy lefelé.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ feljebb került, $INDEX$/$LENGTH$ pozícióba", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ lejjebb került, $INDEX$/$LENGTH$ pozícióba", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } }, "keyUpdateFoldersFailed": { - "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." + "message": "A titkosítókulcs frissítésekor a mappáid nem fejthetőek vissza. A frissítés folytatásához a mappáidat törölni kell. Semmi nem fog törlődni, ha folytatod." }, "keyUpdated": { "message": "A kulcs frissítésre került." @@ -4319,7 +4588,7 @@ "message": "Nincs kiválasztva semmi." }, "receiveMarketingEmailsV2": { - "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." + "message": "Kapj tanácsokat, bejelentéseket és közvélemény-kutatásokat a Bitwardentől a postaládádba." }, "unsubscribe": { "message": "Leiratkozás" @@ -4452,10 +4721,10 @@ "message": "A választott mesterjelszó gyenge. Erős jelszót kell használni a Bitwarden fiók megfelelő védelme érdekében. Biztosan ezt a mesterjelszót szeretnénk használni?" }, "rotateAccountEncKey": { - "message": "A fiók titkosító kulcs forgatása is" + "message": "A felhasználói fiókom titkosítókódját is cseréljük le" }, "rotateEncKeyTitle": { - "message": "Titkosító kulcs forgatása" + "message": "Titkosító kulcs cseréje" }, "rotateEncKeyConfirmation": { "message": "Biztosan fordításra kerüljön a fiók titkosító kulcsa?" @@ -4474,7 +4743,7 @@ "message": "A széfben régi mellékletek vannak, amelyeket javítani kell a fiók titkosító kulcsának fordítása előtt." }, "yourAccountsFingerprint": { - "message": "Fók ujjnyomat kifejezés", + "message": "Fiók ujjlenyomat kifejezés", "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." }, "fingerprintEnsureIntegrityVerify": { @@ -4505,7 +4774,7 @@ "message": "Az API kulcs használható a Bitwarden nyilvános API hitelesítéséhez." }, "apiKeyRotateDesc": { - "message": "Az API kulcs forgatása érvényteleníti a korábbi kulcsot. Az API kulcs forgatható, ha a jelenlegi kulcs már nem tűnik biztonságosnak." + "message": "Az API kulcs cseréje érvényteleníti a korábbi kulcsot. Ha azt gondolod, hogy a jelenlegi kulcs nem biztonságos akkor lecserélheted az API kulcsot." }, "apiKeyWarning": { "message": "Az API kulcs teljes hozzáférést biztosít a szervezethez. Célszerű titokban tartani." @@ -4524,7 +4793,7 @@ "message": "API kulcs megtekintése" }, "rotateApiKey": { - "message": "API kulcs forgatása" + "message": "API kulcs cseréje" }, "selectOneCollection": { "message": "Legalább egy gyűjteményt ki kell választani." @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "A minimális követelmények beállítása a mesterjelszó hosszához." }, + "passwordStrengthScore": { + "message": "A jelszó erősségi pontszáma $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Kétlépéses bejelentkezés szükséges" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "A minimális követelmények beállítása a jelszó generáló konfigurációhoz." }, - "passwordGeneratorPolicyInEffect": { - "message": "Egy vagy több szervezeti szabály érinti a generátor beállításokat." - }, "masterPasswordPolicyInEffect": { "message": "Egy vagy több szervezeti szabály előírja a mesterjelszóhoz a következő követelményeket:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "A szervezet tulajdonosai és adminisztrátorai mentesülnek az irányelv végrehajtása alól." }, + "limitSendViews": { + "message": "Megtekintések korlátozása" + }, + "limitSendViewsHint": { + "message": "Senki sem tudja megtekinteni ezt a Send elemet a korlát elérése után.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ megtekintés maradt.", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send részletek", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Megosztandó szöveg" + }, "sendTypeFile": { "message": "Fájl" }, "sendTypeText": { "message": "Szöveg" }, + "sendPasswordDescV3": { + "message": "Adjunk meg egy opcionális jelszót a címzetteknek a Send eléréséhez.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Új Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Küldés törlése", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Biztosan törlésre kerüljön ez a küldés?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "Milyen típusú ez a küldés?", + "deleteSendPermanentConfirmation": { + "message": "Biztosan véglegesen törlésre kerüljön ez a Send elem?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Törlési dátum" }, - "deletionDateDesc": { - "message": "A Send véglegesen törölve lesz a meghatározott időpontban.", + "deletionDateDescV2": { + "message": "A Send véglegesen törölve lesz ebben az időpontban.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maximális elérési szám" }, - "maxAccessCountDesc": { - "message": "Amennyiben be van állítva, a Send elérhetetlen lesz, amint elérik a meghatározott hozzáférések számát.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Aktuális elérési szám" - }, - "sendPasswordDesc": { - "message": "Opcionálissan egy jelszó kérhető a felhasználóktól a Küldés eléréséhez.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Személyes megjegyzések erről a Küldésről.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Letiltva" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Biztosan eltávolításra kerüljön ez a jelszó?" }, - "hideEmail": { - "message": "Saját email cím elrejtése a címzettek elől." - }, - "disableThisSend": { - "message": "A Send letiltásával mindenki hozzáférése megvonható.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Összes küldés" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Függőben lévő törlés" }, + "hideTextByDefault": { + "message": "Szöveg elrejtése alapértelmezetten" + }, "expired": { "message": "Lejárt" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "A következő szervezeti irányelvek vannak érvényben:" - }, - "sendDisableHideEmailInEffect": { - "message": "A felhasználók nem rejthetik el email címüket a címzettek elől egy Send elem létrehozásakor vagy szerkesztésekor.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "modifiedPolicyId": { "message": "$ID$ szabály módosításra került.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "A szervezeti felhasználók személyes tulajdon letiltása" }, - "textHiddenByDefault": { - "message": "A Küldés elérésekor alapértelmezés szerint a szöveg elrejtése", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Barátságos név a Küldés leírására.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "A küldendő szöveg." - }, - "sendFileDesc": { - "message": "A küldendő fájl." - }, - "copySendLinkOnSave": { - "message": "A hivatkozás másolása a Küldés megosztásához a vágólapra mentéskor." - }, - "sendLinkLabel": { - "message": "Hivatkozás küldése", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Küldés", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5320,10 +5572,10 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more, see how it works, **or** try it now.'" }, "developmentDevOpsAndITTeamsChooseBWSecret": { - "message": "Development, DevOps, and IT teams choose Bitwarden Secrets Manager to securely manage and deploy their infrastructure and machine secrets." + "message": "Fejlesztők, DevOps és informatikai csapatok választják a Bitwarden Secret Managert hogy biztonságosan tudják telepíteni az infrastruktúra és eszköztitkokat." }, "centralizeSecretsManagement": { - "message": "Centralize secrets management." + "message": "Központosítsd a titkosított adattárolást." }, "centralizeSecretsManagementDescription": { "message": "Securely store and manage secrets in one location to prevent secret sprawl across your organization." @@ -5347,22 +5599,22 @@ "message": "Maintain tight control over machine and human access to secrets with SSO integrations, event logs, and access rotation." }, "tryItNow": { - "message": "Try it now" + "message": "Próbáld ki most" }, "sendRequest": { - "message": "Send request" + "message": "Kérelem küldése" }, "addANote": { - "message": "Add a note" + "message": "Jegyzet hozzáadása" }, "bitwardenSecretsManager": { "message": "Bitwarden Secrets Manager" }, "moreProductsFromBitwarden": { - "message": "More products from Bitwarden" + "message": "További Bitwarden termékek" }, "requestAccessToSecretsManager": { - "message": "Request access to Secrets Manager" + "message": "Igényelj hozzáférést a Secrets Managerhez" }, "youNeedApprovalFromYourAdminToTrySecretsManager": { "message": "You need approval from your administrator to try Secrets Manager." @@ -5383,10 +5635,10 @@ "message": "Open your organization's" }, "usingTheMenuSelect": { - "message": "Using the menu, select" + "message": "A menü segítségével válassz az alábbiakból" }, "toGrantAccessToSelectedMembers": { - "message": "to grant access to selected members." + "message": "hogy kiválasztott tagoknak hozzáférést biztosíts." }, "sendVaultCardTryItNow": { "message": "próbáljuk ki most", @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "Hiba történt a törlés és a lejárati dátum mentésekor." }, + "hideYourEmail": { + "message": "Saját email cím elrejtése a megtekintések elől." + }, "webAuthnFallbackMsg": { "message": "A 2FA ellenőrzéséhez kattintsunk az alábbi gombra." }, "webAuthnAuthenticate": { "message": "WebAutn hitelesítés" }, + "readSecurityKey": { + "message": "Biztonsági kulcs olvasása" + }, + "awaitingSecurityKeyInteraction": { + "message": "Várakozás a biztonsági kulcs interakciójára..." + }, "webAuthnNotSupported": { "message": "Ezen a böngészőn a WebAuthn nem támogatott." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Hiba" }, + "decryptionError": { + "message": "Visszafejtési hiba" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "A Bitwarden nem tudta visszafejteni az alább felsorolt ​​széf elemeket." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Ügyfélszolgálat elérése", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "további adatvesztés elkerülése érdekében.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "A felhasználók kezelését engedélyezni kell a Jelszó visszaállításának kezelése jogosultsággal is." }, @@ -6516,15 +6791,6 @@ "message": "Generátor", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "Mit szeretnénk generálni?" - }, - "passwordType": { - "message": "Jelszótípus" - }, - "regenerateUsername": { - "message": "Felhasználónév ismételt generálása" - }, "generateUsername": { "message": "Felhasználónév generálása" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Felhasználónév típusa" - }, "plusAddressedEmail": { "message": "További címzési email cím", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Használjuk a tartomány konfigurált összes befogási bejövő postaládát." }, + "useThisEmail": { + "message": "Ezen email használata" + }, "random": { "message": "Véletlen", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ elutasította a kérést. Vegyük fel a kapcsolatot szolgáltatóval segítségért.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ elutasította a kérést: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Nem lehet beolvasni $SERVICENAME$ maszkolt email fiók azonosítót.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Kiszolglónév", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API hozzáférési vezérjel" - }, "deviceVerification": { "message": "Eszköz ellenőrzés" }, @@ -6843,11 +7130,11 @@ "description": "the text, 'SCIM' and 'API', are acronyms and should not be translated." }, "rotateScimKeyWarning": { - "message": "Biztosan forgatni akarjuk a SCIM API kulcsot? A jelenlegi kulcs már nem működik egyik meglévő integrációhoz sem.", + "message": "Biztosan szeretnénk lecserélni a SCIM API kulcsot? A jelenlegi kulcs már nem működik egyik meglévő integrációhoz sem.", "description": "the text, 'SCIM' and 'API', are acronyms and should not be translated." }, "rotateKey": { - "message": "Kulcs forgatása" + "message": "Kulcs cseréje" }, "scimApiKey": { "message": "SCIM API kulcs", @@ -6862,7 +7149,7 @@ "description": "the text, 'SCIM' and 'URL', are acronyms and should not be translated." }, "scimApiKeyRotated": { - "message": "A SCIM API kulcs sikeresen forgatásra került.", + "message": "A SCIM API kulcs sikeresen lecserélésre került.", "description": "the text, 'SCIM' and 'API', are acronyms and should not be translated." }, "scimSettingsSaved": { @@ -6957,13 +7244,19 @@ "message": "Hiba történt a Duo szolgáltatáshoz csatlakozáskor. Használjunk másik kétlépcsős bejelentkezési módot vagy kérjünk segítséget a Duotól." }, "launchDuoAndFollowStepsToFinishLoggingIn": { - "message": "Indítsuk el a DUO-t és kövessük a lépéseket a bejelentkezés befejezéséhez." + "message": "Indítsd el a Duo-t és kövesd a lépéseket a bejelentkezés befejezéséhez." }, "duoRequiredByOrgForAccount": { - "message": "A fiókhoz kétlépcsős DUO bejelentkezés szükséges." + "message": "A fiókhoz kétlépcsős Duo bejelentkezés szükséges." + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo kétlépcsős bejelentkezés szükséges a fiókhoz. Kövessük az alábbi lépéseket a bejelentkezés befejezéséhez." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Kövessük az alábbi lépéseket a bejelentkezés befejezéséhez." }, "launchDuo": { - "message": "DUO indítása" + "message": "Duo indítása" }, "turnOn": { "message": "Bekapcsolás" @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Felhasználók száma" }, - "loggingInAs": { - "message": "Bejelentkezve mint" - }, - "notYou": { - "message": "Ez tévedés?" - }, "pickAnAvatarColor": { "message": "Avatar szín választás" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "Nincs gyűjtemény." }, - "canView": { - "message": "Megtekintheti" - }, - "canViewExceptPass": { - "message": "Megtekintheti, kivéve a jelszavakat" - }, - "canEdit": { - "message": "Szerkesztheti" - }, - "canEditExceptPass": { - "message": "Szerkesztheti, kivéve a jelszavakat" - }, "noCollectionsAdded": { "message": "Nem lett gyűjtemény hozzáadva." }, @@ -8049,7 +8324,7 @@ "message": "Gyenge és kiszivárgott mesterjelszó" }, "weakAndBreachedMasterPasswordDesc": { - "message": "Gyenge jelszó lett azonosítva és megtalálva egy adatvédelmi incidens során. A fók védelme érdekében használjunk erős és egyedi jelszót. Biztosan használni szeretnénk ezt a jelszót?" + "message": "Gyenge jelszó lett azonosítva és megtalálva egy adatvédelmi incidens során. A fiók védelme érdekében használjunk erős és egyedi jelszót. Biztosan használni szeretnénk ezt a jelszót?" }, "characterMinimum": { "message": "Legalább $LENGTH$ karakter", @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Adminisztrátori jóváhagyás kérés" }, - "approveWithMasterPassword": { - "message": "Jóváhagyás mesterjelszóval" - }, "trustedDeviceEncryption": { "message": "Megbízható eszköztitkosítás" }, "trustedDevices": { "message": "Megbízható eszközök" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "A hitelesítés után a tagok az eszközükön tárolt kulccsal visszafejtik a tároló adatait. Ehhez", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "A tagoknak nincs szükségük mesterjelszóra az egyszeri bejelentkezéskor. A mesterjelszót az eszközön tárolt titkosítási kulccsal helyettesítjük, így az eszköz megbízható. Az az első eszköz, amelyen a tag létrehozza a fiókját és bejelentkezik, megbízható lesz. Az új eszközöket egy meglévő megbízható eszköznek vagy egy rendszergazdának kell jóváhagynia. Az", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "önálló szervezet", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "rendszabály,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "SSO szükséges", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "az SSO szükséges", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "rendszabály és ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "rendszabály és", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "fiók helyreállítási adminisztráció", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "a fiók helyreállítási adminisztráció", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "automatikus regisztrációval rendelkező rendszabály kerül bekapcsolásra, ha ezt az opció van használatban.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "rendszabály bekapcsolásra kerül, ha ezt a lehetőséget használjuk.", + "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": "A szervezeti engedélyek frissítésre kerültek, ezért be kell állítani egy mesterjelszót.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Kérés megerősítése" }, + "deviceApproved": { + "message": "Az eszköz jóváhagyásra került." + }, + "deviceRemoved": { + "message": "Az eszköz eltávolításra került." + }, + "removeDevice": { + "message": "Eszköz eltávolítása" + }, + "removeDeviceConfirmation": { + "message": "Biztos eltávolításra kerüljön ez az eszköz?" + }, "noDeviceRequests": { "message": "Nincs eszköz kérés" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "A kérés elküldésre került az adminisztrátornak." }, - "youWillBeNotifiedOnceApproved": { - "message": "A jóváhagyás után értesítés érkezik." - }, "troubleLoggingIn": { "message": "Probléma van a bejelentkezéssel?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "A gyűjtemény törlésének korlátozása tulajdonosokra és adminisztrátorokra" }, + "limitItemDeletionDesc": { + "message": "Az elemek törlésének korlátozása a Kezelheti jogosultsággal rendelkező tagokra" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,14 +8778,11 @@ "message": "Saját üzemeltetésű szerver webcím", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Áldomain" - }, "alreadyHaveAccount": { "message": "Van már saját fiók?" }, "toggleSideNavigation": { - "message": "Toggle side navigation" + "message": "Navigációs sáv ki/bekapcsolása" }, "skipToContent": { "message": "Ugrás a tartalomra" @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "Nincs jogosultság ennek a gyűjteménynek a kezelésére." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Hiányzó gyűjtemény kezelési engedélyek" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Adjunk gyűjtemény kezelési engedélyeket, hogy lehetővé tegyük a teljes gyűjtemény kezelést, beleértve a gyűjtemény törlését is." }, "grantCollectionAccess": { "message": "Adjunk hozzáférést csoportoknak vagy személyeknek eennél a gyűjteménynél." @@ -8607,35 +8888,35 @@ "message": "A megerősítő email elküldésre került az email címre:" }, "sorryToSeeYouGo": { - "message": "Sorry to see you go! Help improve Bitwarden by sharing why you're canceling.", + "message": "Sajnáljuk, hogy itt hagysz minket! Segíts fejlődnünk azzal, hogy megosztod, hogy miért mondod fel a szolgáltatást.", "description": "A message shown to users as part of an offboarding survey asking them to provide more information on their subscription cancelation." }, "selectCancellationReason": { - "message": "Select a reason for canceling", + "message": "Válaszd ki a felmondás okát", "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": "Van még bármi visszajelzés, amit meg szeretnél osztani?", "description": "Used as a form field label for a textarea input on the offboarding survey." }, "missingFeatures": { - "message": "Missing features", + "message": "Hiányzó funkciók", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "movingToAnotherTool": { - "message": "Moving to another tool", + "message": "Más eszközre váltok", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "tooDifficultToUse": { - "message": "Too difficult to use", + "message": "Túl bonyolult", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "notUsingEnough": { - "message": "Not using enough", + "message": "Ritkán használtam", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "tooExpensive": { - "message": "Too expensive", + "message": "Túl drága", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "freeForOneYear": { @@ -8657,7 +8938,7 @@ "message": "Sikeres" }, "restrictedGroupAccess": { - "message": "Nem adhadjuk magunkat a csoporthoz." + "message": "Nem adhatod saját magadat csoportokhoz." }, "cannotAddYourselfToCollections": { "message": "Nem adhadjuk magunkat a gyűjteményhez." @@ -8966,7 +9247,7 @@ "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." }, "deleteProviderName": { - "message": "Cannot delete $ID$", + "message": "$ID$ nem törölhető", "placeholders": { "id": { "content": "$1", @@ -9002,17 +9283,17 @@ "message": "Hiba történt a célmappa hozzárendelése során." }, "integrationsAndSdks": { - "message": "Integrations & SDKs", + "message": "Integráció és fejlesztői környezet", "description": "The title for the section that deals with integrations and SDKs." }, "integrations": { - "message": "Integrations" + "message": "Integráció" }, "integrationsDesc": { "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." }, "sdks": { - "message": "SDKs" + "message": "Fejlesztői környezetek" }, "sdksDesc": { "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Az eszközkezelés konfigurálása a Bitwarden számára a platform megvalósítási útmutatója segítségével." }, + "deviceIdMissing": { + "message": "Az eszköz AZ hiányzik." + }, + "deviceTypeMissing": { + "message": "Az eszköz típus hiányzik." + }, + "deviceCreationDateMissing": { + "message": "Az eszköz létrehozás dátuma hiányzik." + }, + "desktopRequired": { + "message": "Asztali gép szükséges" + }, + "reopenLinkOnDesktop": { + "message": "Nyissuk meg újra ezt a hivatkozást az emailből az asztali gépen." + }, "integrationCardTooltip": { "message": "$INTEGRATION$ megvalósítási útmutató elindítása.", "placeholders": { @@ -9115,22 +9411,25 @@ "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." }, "selectAPlan": { - "message": "Select a plan" + "message": "Előfizetés kiválasztása" }, "thirtyFivePercentDiscount": { - "message": "35% Discount" + "message": "35% engedmény" }, "monthPerMember": { - "message": "month per member" + "message": "hónap/fő" + }, + "monthPerMemberBilledAnnually": { + "message": "hónap tagonként éves számlázással" }, "seats": { "message": "Seats" }, "addOrganization": { - "message": "Add organization" + "message": "Szervezet hozzáadása" }, "createdNewClient": { - "message": "Successfully created new client" + "message": "Az új ügyfél sikeresen létrejött" }, "noAccess": { "message": "Nincs hozzáférés." @@ -9432,7 +9731,7 @@ "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'Notice: Later this month, client vault privacy will be improved and provider members will no longer have direct access to client vault items. For questions, please contact Bitwarden support'." }, "contactBitwardenSupport": { - "message": "contact Bitwarden support.", + "message": "vedd fel a kapcsolatot a Bitwarden ügyfélszolgálattal.", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'Notice: Later this month, client vault privacy will be improved and provider members will no longer have direct access to client vault items. For questions, please contact Bitwarden support'. 'Bitwarden' should not be translated" }, "sponsored": { @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "További információ a Bitwarden API-járól" }, + "fileSend": { + "message": "Fájl típusú Send" + }, "fileSends": { "message": "Fájl küldés" }, + "textSend": { + "message": "Szöveg típusú Send" + }, "textSends": { "message": "Szöveg küldés" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Kulcs algoritmus" }, + "sshPrivateKey": { + "message": "Személyes kulcs" + }, + "sshPublicKey": { + "message": "Nyilvános kulcs" + }, + "sshFingerprint": { + "message": "Ujjlenyomat" + }, "sshKeyFingerprint": { "message": "Ujjlenyomat" }, @@ -9713,7 +10027,7 @@ "message": "Bitwarden jelszókezelő" }, "secretsManagerComplimentaryPasswordManager": { - "message": "Your complimentary one year Password Manager subscription will upgrade to the selected plan. You will not be charged until the complimentary period is over." + "message": "A kiegészítő egyéves Jelszókezelő előfizetésed a kiválasztott csomagra frissül. A díjmentes időszak lejártáig nem számítunk fel díjat." }, "fileSavedToDevice": { "message": "A fájl mentésre került az eszközre. Kezeljük az eszközről a letöltéseket." @@ -9774,10 +10088,6 @@ "message": "Speciális karakterek bevonása", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Melléklet hozzáadása" }, @@ -9896,11 +10206,20 @@ "descriptorCode": { "message": "Leíró kód" }, + "cannotRemoveViewOnlyCollections": { + "message": "Nem távolíthatók el a csak megtekintési engedéllyel bíró gyűjtemények: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Fontos megjegyzés" }, "setupTwoStepLogin": { - "message": "Kétlépéses bejelentkezés szükséges" + "message": "Kétfaktoros bejelentkezés beállítása" }, "newDeviceVerificationNoticeContentPage1": { "message": "A Bitwarden 2025 februárjától kódot küld a fiókhoz tartozó email-címre, amellyel ellenőrizhetők az új eszközökről történő bejelentkezések." @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Tagok eltávolítása" }, + "devices": { + "message": "Eszközök" + }, + "deviceListDescription": { + "message": "A fiók az alábbi eszközök mindegyikére bejelentkezett. Ha nem ismerünk fel egy eszközt, távolítsuk el most." + }, + "deviceListDescriptionTemp": { + "message": "A fiók az alábbi eszközök mindegyikére bejelentkezett." + }, "claimedDomains": { "message": "Igényelt tartományok" }, @@ -10020,7 +10348,69 @@ "organizationNameMaxLength": { "message": "A szervezet neve nem haladhatja meg az 50 karaktert." }, - "resellerRenewalWarning": { + "sshKeyWrongPassword": { + "message": "A megadott jelszó helytelen." + }, + "importSshKey": { + "message": "Importálás" + }, + "confirmSshKeyPassword": { + "message": "Jelszó megerősítése" + }, + "enterSshKeyPasswordDesc": { + "message": "Adjuk meg az SSH kulcs jelszót." + }, + "enterSshKeyPassword": { + "message": "Jelszó megadása" + }, + "invalidSshKey": { + "message": "Az SSH kulcs érvénytelen." + }, + "sshKeyTypeUnsupported": { + "message": "Az SSH kulcstípus nem támogatott." + }, + "importSshKeyFromClipboard": { + "message": "Kulcs importálása vágólapból" + }, + "sshKeyImported": { + "message": "Az SSH kulcs sikeresen importálásra került." + }, + "copySSHPrivateKey": { + "message": "Személyes kulcs másolása" + }, + "openingExtension": { + "message": "A Bitwarden böngésző bővítmény megnyitása" + }, + "somethingWentWrong": { + "message": "Valami hiba történt." + }, + "openingExtensionError": { + "message": "Probléma merült fel a Bitward böngésző bővítmény megnyitásakor. Kattintsunk a gombra a megnyitáshoz." + }, + "openExtension": { + "message": "Bővítmény megnyitása" + }, + "doNotHaveExtension": { + "message": "Nincs a Bitwarden böngésző bővítmény?" + }, + "installExtension": { + "message": "Bővítmény telepítése" + }, + "openedExtension": { + "message": "Megnyitottuk a böngésző bővítményt." + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Sikeresen megnyitottuk a Bitwarden böngésző bővítményt. Mostantól áttekinthetjük a veszélyeztetett jelszavakat." + }, + "openExtensionManuallyPart1": { + "message": "Probléma merült fel a Bitward böngésző bővítmény megnyitásakor. Nyissuk meg a Bitwarden ikont", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "az eszköztárból.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { "message": "Az előfizetés hamarosan megújul. A folyamatos szolgáltatás biztosítása érdekében lépjünk kapcsolatba $RESELLER$ viszonteladóval és erősítsük meg a megújítást $RENEWAL_DATE$ előtt.", "placeholders": { "reseller": { @@ -10033,7 +10423,7 @@ } } }, - "resellerOpenInvoiceWarning": { + "resellerOpenInvoiceWarningMgs": { "message": "Az előfizetésről szóló számla kiállítása $ISSUED_DATE$ napon történt. A megszakítás nélküli szolgáltatás biztosítása érdekében lépjünk kapcsolatba $RESELLER$ viszonteladóval és erősítsük meg a megújítást $DUE_DATE$ előtt.", "placeholders": { "reseller": { @@ -10050,7 +10440,7 @@ } } }, - "resellerPastDueWarning": { + "resellerPastDueWarningMsg": { "message": "Az előfizetésről szóló számla nem lett kfizetve. A folyamatos szolgáltatás biztosítása érdekében lépjünk kapcsolatba $RESELLER$ viszonteladóval és erősítsük meg a megújítást $GRACE_PERIOD_END$ előtt.", "placeholders": { "reseller": { @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "A szervezeti feliratkozás újraindult." + }, + "restartSubscription": { + "message": "Feliratkozás újra indítása" + }, + "suspendedManagedOrgMessage": { + "message": "Segítséget $PROVIDER$ szolgáltatótól kaphatunk.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Az adminisztrátorok mostantól törölhetik az igényelt tartományhoz tartozó tagi fiókokat." + }, + "deleteManagedUserWarningDesc": { + "message": "Ez a művelet törli a tagi fiókot, beleértve a tárolókban lévő összes elemet. Ez felváltja az előző Eltávolítás műveletet." + }, + "deleteManagedUserWarning": { + "message": "A törlés új művelet!" + }, + "seatsRemaining": { + "message": "$REMAINING$ hely maradt a szervezethez rendelt $TOTAL$ helyből. Az előfizetés kezeléséhez forduljunk a szolgáltatóhoz.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Létező szervezet" + }, + "selectOrganizationProviderPortal": { + "message": "Válasszuk ki a szolgáltatói portálhoz hozzáadni kívánt szervezetet." + }, + "noOrganizations": { + "message": "Nincsenek listázható szervezetek." + }, + "yourProviderSubscriptionCredit": { + "message": "A szolgáltatói előfizetés jóváírást kap a szervezet előfizetésből hátralévő időre." + }, + "doYouWantToAddThisOrg": { + "message": "Hozzáadásra kerüljön ez a szervezet $PROVIDER$ szolgáltatáshoz?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "A létező szervezet hozzáadásra került." + }, + "assignedExceedsAvailable": { + "message": "A hozzárendelt helyek száma meghaladja a rendelkezésre álló helyek számát." + }, + "changeAtRiskPassword": { + "message": "Kockázatos jelszó megváltoztatása" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Feloldás eltávolítása PIN kóddal" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Nem engedjük meg a tagoknak, hogy PIN kóddal oldják fel fiókjuk zárolását." + }, + "limitedEventLogs": { + "message": "A $PRODUCT_TYPE$ csomagok nem férnek hozzá a valós eseménynaplókhoz.", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Teljes hozzáférést kaphatunk a szervezeti eseménynaplókhoz, ha Teams vagy Enterprise csomagra térünk át." + }, + "upgradeEventLogTitle": { + "message": "Áttérés valós eseménynapló adatokhoz" + }, + "upgradeEventLogMessage": { + "message": "Ezek az események csak példák és nem tükröznek valós eseményeket a Bitwarden szervezetén belül." + }, + "cannotCreateCollection": { + "message": "Az ingyenes szervezeteknek legfeljebb 2 gyűjteményük lehet. Térjünk át egy fizetett csomagra további gyűjtemények hozzáadásához." } } diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index 7517cac5ae5..22b4e3a6132 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -1,10 +1,13 @@ { "allApplications": { - "message": "All applications" + "message": "Semua aplikasi" }, "criticalApplications": { "message": "Critical applications" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -12,7 +15,7 @@ "message": "Risk Insights" }, "passwordRisk": { - "message": "Password Risk" + "message": "Petunjuk Sandi" }, "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." @@ -30,16 +33,16 @@ "message": "Notified members" }, "revokeMembers": { - "message": "Revoke members" + "message": "Cabut Pengguna" }, "restoreMembers": { - "message": "Restore members" + "message": "Pulihkan pengguna" }, "cannotRestoreAccessError": { "message": "Cannot restore organization access" }, "allApplicationsWithCount": { - "message": "All applications ($COUNT$)", + "message": "Semua aplikasi ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -51,7 +54,7 @@ "message": "Create new login item" }, "criticalApplicationsWithCount": { - "message": "Critical applications ($COUNT$)", + "message": "Semua aplikasi ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -69,7 +72,7 @@ } }, "noAppsInOrgTitle": { - "message": "No applications found in $ORG NAME$", + "message": "Tidak menemukan aplikasi di $ORG NAME$", "placeholders": { "org name": { "content": "$1", @@ -96,31 +99,70 @@ "message": "Apps marked as critical" }, "application": { - "message": "Application" + "message": "Aplikasi" }, "atRiskPasswords": { "message": "At-risk passwords" }, "requestPasswordChange": { - "message": "Request password change" + "message": "Minta petunjuk kata sandi" }, "totalPasswords": { - "message": "Total passwords" + "message": "Jumlah Kata Sandi" }, "searchApps": { - "message": "Search applications" + "message": "Cari aplikasi" }, "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Semua aplikasi ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { - "message": "Total members" + "message": "Jumlah Anggota" }, "atRiskApplications": { "message": "At-risk applications" }, "totalApplications": { - "message": "Total applications" + "message": "Semua aplikasi" + }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" }, "whatTypeOfItem": { "message": "Jenis barang apa ini?" @@ -159,8 +201,11 @@ "notes": { "message": "Catatan" }, + "privateNote": { + "message": "Catatan pribadi" + }, "note": { - "message": "Note" + "message": "Catatan" }, "customFields": { "message": "Kolom Ubahsuai" @@ -172,19 +217,19 @@ "message": "Login credentials" }, "personalDetails": { - "message": "Personal details" + "message": "Rincian pribadi" }, "identification": { - "message": "Identification" + "message": "Identifikasi" }, "contactInfo": { - "message": "Contact info" + "message": "Info kontak" }, "cardDetails": { - "message": "Card details" + "message": "Rincian kartu" }, "cardBrandDetails": { - "message": "$BRAND$ details", + "message": "Rincian $BRAND$", "placeholders": { "brand": { "content": "$1", @@ -193,19 +238,19 @@ } }, "itemHistory": { - "message": "Item history" + "message": "Riwayat benda" }, "authenticatorKey": { - "message": "Authenticator key" + "message": "Kunci Autentikator" }, "autofillOptions": { - "message": "Autofill options" + "message": "Pilihan isi otomatis" }, "websiteUri": { - "message": "Website (URI)" + "message": "Situs web (URI)" }, "websiteUriCount": { - "message": "Website (URI) $COUNT$", + "message": "$COUNT$ Situs web (URI)", "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", "placeholders": { "count": { @@ -383,6 +428,9 @@ "dragToSort": { "message": "Tarik untuk mengurutkan" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Teks" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Edit Folder" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Apakah Anda yakin akan menghapus keranjang ini selamanya?" + }, "baseDomain": { "message": "Domain dasar", "description": "Domain name. Example: website.com" @@ -469,7 +542,7 @@ "message": "Buat Kata Sandi" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "Buat frasa sandi" }, "checkPassword": { "message": "Periksa apakah kata sandi telah terekspos." @@ -524,11 +597,11 @@ "description": "Search Login type" }, "searchCard": { - "message": "Search cards", + "message": "Cari kode", "description": "Search Card type" }, "searchIdentity": { - "message": "Search identities", + "message": "Cari identitas", "description": "Search Identity type" }, "searchSecureNote": { @@ -539,10 +612,10 @@ "message": "Cari Brankas" }, "searchMyVault": { - "message": "Search my vault" + "message": "Cari brankas" }, "searchOrganization": { - "message": "Search organization" + "message": "Cari organisasi" }, "searchMembers": { "message": "Search members" @@ -689,15 +762,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "cth.", "description": "Short abbreviation for 'example'." @@ -768,31 +832,31 @@ "message": "Copy website" }, "copyNotes": { - "message": "Copy notes" + "message": "Salin catatan" }, "copyAddress": { "message": "Copy address" }, "copyPhone": { - "message": "Copy phone" + "message": "Salin nomor telepon" }, "copyEmail": { - "message": "Copy email" + "message": "Salin alamat surat elektronik" }, "copyCompany": { - "message": "Copy company" + "message": "Salin perusahaan" }, "copySSN": { - "message": "Copy Social Security number" + "message": "Salin nomor Keamanan Sosial" }, "copyPassportNumber": { - "message": "Copy passport number" + "message": "Salin nomor paspor" }, "copyLicenseNumber": { - "message": "Copy license number" + "message": "Salin nomor lisensi" }, "copyName": { - "message": "Copy name" + "message": "Salin nama" }, "me": { "message": "Saya" @@ -815,9 +879,6 @@ "filter": { "message": "Filter" }, - "moveSelectedToOrg": { - "message": "Pindahkan terpilih ke Organisasi" - }, "deleteSelected": { "message": "Hapus yang Dipilih" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Item terpilih dipindah ke $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -940,34 +992,34 @@ "message": "Edit info" }, "access": { - "message": "Access" + "message": "Akses" }, "accessLevel": { - "message": "Access level" + "message": "Tingkat akses" }, "accessing": { - "message": "Accessing" + "message": "Sedang mengakses" }, "loggedOut": { "message": "Keluar" }, "loggedOutDesc": { - "message": "You have been logged out of your account." + "message": "Anda telah keluar dari akun Anda." }, "loginExpired": { "message": "Sesi masuk Anda telah berakhir." }, "restartRegistration": { - "message": "Restart registration" + "message": "Mulai ulang pendaftaran" }, "expiredLink": { - "message": "Expired link" + "message": "Tautan telah kadaluwarsa" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "Mohon mulai ulang pendaftaran atau coba masuk." }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "Anda mungkin telah memiliki sebuah akun" }, "logOutConfirmation": { "message": "Anda yakin ingin keluar?" @@ -984,6 +1036,9 @@ "no": { "message": "Tidak" }, + "location": { + "message": "Lokasi" + }, "loginOrCreateNewAccount": { "message": "Masuk atau buat akun baru untuk mengakses brankas Anda." }, @@ -991,10 +1046,10 @@ "message": "Masuk dengan perangkat" }, "loginWithDeviceEnabledNote": { - "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" + "message": "Masuk dengan perangkat harus diatur di pengaturan aplikasi ini. Perlu pilihan lainnya?" }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "Perlu pilihan lainnya?" }, "loginWithMasterPassword": { "message": "Masuk dengan kata sandi utama" @@ -1009,13 +1064,13 @@ "message": "Use a different log in method" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Masuk dengan kunci sandi" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "Gunakan masuk tunggal" }, "welcomeBack": { - "message": "Welcome back" + "message": "Selamat datang kembali" }, "invalidPasskeyPleaseTryAgain": { "message": "Invalid Passkey. Please try again." @@ -1042,10 +1097,10 @@ "message": "Error creating passkey" }, "errorCreatingPasskeyInfo": { - "message": "There was a problem creating your passkey." + "message": "Terdapat masalah mengimpor kuncimu." }, "passkeySuccessfullyCreated": { - "message": "Passkey successfully created!" + "message": "Akun berhasil dibuat!" }, "customPasskeyNameInfo": { "message": "Name your passkey to help you identify it." @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Kirim" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Petunjuk Kata Sandi" - }, - "enterEmailToGetHint": { - "message": "Masukkan email akun Anda untuk menerima pentunjuk sandi utama Anda." - }, "getMasterPasswordHint": { "message": "Dapatkan petunjuk sandi utama" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "Versi $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Ingat saya" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Kirim ulang email kode verifikasi" }, "useAnotherTwoStepMethod": { "message": "Gunakan metode login dua-langkah lainnya" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Masukkan YubiKey Anda ke port USB komputer Anda, lalu sentuh tombol nya." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Opsi login dua-langkah" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Kehilangan akses ke semua penyedia dua faktor Anda? Gunakan kode pemulihan untuk menonaktifkan semua penyedia dua faktor dari akun Anda." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Dipindahkan dari FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "Surel" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Pilihlah sebuah organisasi yang Anda ingin memindahkan item ini. Memindahkan berarti memberikan kepemilikan kepada organisasi tersebut. Anda tidak akan lagi menjadi pemilik item ini." }, - "moveManyToOrgDesc": { - "message": "Pilihlah sebuah organisasi yang Anda ingin memindahkan item ini. Memindahkan berarti memberikan kepemilikan kepada organisasi tersebut. Anda tidak akan lagi menjadi pemilik item ini." - }, "collectionsDesc": { "message": "Edit koleksi tempat item ini dibagikan. Hanya pengguna organisasi dengan akses ke koleksi ini yang dapat melihat item ini." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "Anda telah memilih $COUNT$ item. $MOVEABLE_COUNT$ item bisa dipindahkan ke sebuah organisasi, $NONMOVEABLE_COUNT$ tidak bisa.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Kode Verifikasi (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Buat Ulang Sandi" - }, "length": { "message": "Panjang" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Harap masuk kembali." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Harap masuk kembali. Jika Anda menggunakan aplikasi Bitwarden lain, keluarlah dan masuk kembali ke sana juga." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Zona Bahaya" }, - "dangerZoneDesc": { - "message": "Hati-hati, tindakan ini tidak bisa dibatalkan!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Batalkan Otorisasi Sesi" }, @@ -1797,6 +1890,27 @@ "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." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "Semua Sesi Dicabut Izinnya" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Dengan mengaktifkan login dua-langkah, Anda bisa terkunci dari akun Bitwarden secara permanen. Jika Anda tidak bisa menggunakan provider login dua-langkah di kondisi normal (misal karena perangkat Anda hilang), Anda bisa menggunakan kode pemulihan untuk mengakses akun tersebut. Bitwarden sama sekali tidak dapat membantu jika Anda kehilangan akses ke akun Anda. Oleh karena itu, kami menyarankan Anda untuk menulis atau mencetak kode pemulihan dan menyimpannya di tempat yang aman." }, + "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." + }, "viewRecoveryCode": { "message": "Lihat Kode Pemulihan" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Kelola" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Nonaktifkan" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Cabut Akses" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "Penyedia proses masuk dua langkah ini diaktifkan di akun Anda." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "Ada masalah saat membaca kunci keamanan. Coba lagi." }, - "twoFactorWebAuthnWarning": { - "message": "Karena keterbatasan platform, WebAuthn tidak dapat digunakan di semua aplikasi Bitwarden. Anda harus mengaktifkan penyedia proses masuk dua langkah lainnya sehingga Anda dapat mengakses akun Anda saat WebAUthn tidak dapat digunakan. Platform yang didukung:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Brankas web dan ekstensi browser di desktop / laptop dengan browser yang mendukung WebAuthn (Chrome, Opera, Vivaldi, atau Firefox dengan FIDO U2F diaktifkan)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Kode pemulihan masuk dua langkah Bitwarden Anda" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "Pengguna ini menggunakan proses masuk dua langkah untuk melindungi akun mereka." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "SSO tidak ditautkan untuk pengguna $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Perangkat" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3888,6 +4096,12 @@ "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." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "Jika Anda tidak dapat mengakses akun Anda melalui metode masuk dua langkah biasa, Anda dapat menggunakan kode pemulihan masuk dua langkah untuk menonaktifkan semua penyedia dua langkah di akun Anda." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Pulihkan Akun Dua Langkah Masuk" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Tetapkan persyaratan minimum untuk kekuatan kata sandi utama." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Diperlukan login dua-langkah" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Tetapkan persyaratan minimum untuk konfigurasi pembuat kata sandi." }, - "passwordGeneratorPolicyInEffect": { - "message": "Satu atau beberapa kebijakan organisasi memengaruhi pengaturan generator Anda." - }, "masterPasswordPolicyInEffect": { "message": "Satu atau lebih kebijakan organisasi memerlukan kata sandi utama Anda untuk memenuhi persyaratan berikut:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Pemilik dan Administrator Organisasi dibebaskan dari penegakan kebijakan ini." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "Berkas" }, "sendTypeText": { "message": "Teks" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Buat Pengiriman Baru", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Hapus Kirim", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Anda yakin ingin menghapus Kirim ini?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "Jenis Pengiriman apakah ini?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Tanggal Penghapusan" }, - "deletionDateDesc": { - "message": "Pengiriman akan dihapus secara permanen pada tanggal dan waktu yang ditentukan.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Hitungan Akses Maksimum" }, - "maxAccessCountDesc": { - "message": "Jika disetel, pengguna tidak dapat lagi mengakses pengiriman ini setelah jumlah akses maksimum tercapai.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Hitungan Akses Saat Ini" - }, - "sendPasswordDesc": { - "message": "Secara opsional, minta kata sandi bagi pengguna untuk mengakses Kirim ini.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Catatan pribadi tentang Send ini.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Dinonaktifkan" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Anda yakin ingin menghapus kata sandi?" }, - "hideEmail": { - "message": "Sembunyikan alamat email dari penerima." - }, - "disableThisSend": { - "message": "Nonaktifkan Pengiriman ini sehingga tidak ada yang dapat mengaksesnya.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Semua Dikirim" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Penghapusan menunggu keputusan" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Kedaluwarsa" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "Ketentuan organization berikut ini sedang berlaku:" - }, - "sendDisableHideEmailInEffect": { - "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." - }, "modifiedPolicyId": { "message": "Kebijakan yang diubah $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Nonaktifkan kepemilikan pribadi untuk pengguna organisasi" }, - "textHiddenByDefault": { - "message": "Saat mengakses Send, sembunyikan teks secara default", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Nama yang ramah untuk menggambarkan kirim ini.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Teks yang ingin Anda kirim." - }, - "sendFileDesc": { - "message": "File yang ingin Anda kirim." - }, - "copySendLinkOnSave": { - "message": "Salin tautan untuk membagikan kirim ini ke clipboard saya saat menyimpan." - }, - "sendLinkLabel": { - "message": "Kirim tautan", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Kirim", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "Ada kesalahan menyimpan penghapusan dan tanggal kedaluwarsa Anda." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "Untuk mengverifikasi dua-langkat autentikasi anda, silahkan click tombol dibawah." }, "webAuthnAuthenticate": { "message": "Autentikasi dengan WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn tidak didukung oleh browser ini." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Galat" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Jenis kata sandi" - }, - "regenerateUsername": { - "message": "Regenerate username" - }, "generateUsername": { "message": "Generate username" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Jenis nama pengguna" - }, "plusAddressedEmail": { "message": "Plus addressed email", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Acak", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Hostname", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API access token" - }, "deviceVerification": { "message": "Verifikasi Perangkat" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Jumplah pengguna" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Bukan Anda?" - }, "pickAnAvatarColor": { "message": "Pick an avatar color" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "No collection" }, - "canView": { - "message": "Can view" - }, - "canViewExceptPass": { - "message": "Can view, except passwords" - }, - "canEdit": { - "message": "Can edit" - }, - "canEditExceptPass": { - "message": "Can edit, except passwords" - }, "noCollectionsAdded": { "message": "No collections added" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domain" - }, "alreadyHaveAccount": { "message": "Already have an account?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index 0154a3c8c78..0cbc425d588 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -3,22 +3,25 @@ "message": "Tutte le applicazioni" }, "criticalApplications": { - "message": "Critical applications" + "message": "Applicazioni critiche" + }, + "noCriticalAppsAtRisk": { + "message": "Nessuna applicazione critica a rischio" }, "accessIntelligence": { - "message": "Access Intelligence" + "message": "Intelligence sugli accessi" }, "riskInsights": { - "message": "Risk Insights" + "message": "Approfondimento rischi" }, "passwordRisk": { - "message": "Password Risk" + "message": "Rischio password" }, "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." + "message": "Controlla le password a rischio (deboli, esposte o riutilizzate). Seleziona le applicazioni critiche per determinare la priorità delle azioni di sicurezza." }, "dataLastUpdated": { - "message": "Data last updated: $DATE$", + "message": "Ultimo aggiornamento: $DATE$", "placeholders": { "date": { "content": "$1", @@ -30,13 +33,13 @@ "message": "Membri notificati" }, "revokeMembers": { - "message": "Revoke members" + "message": "Revoca membri" }, "restoreMembers": { - "message": "Restore members" + "message": "Ripristina membri" }, "cannotRestoreAccessError": { - "message": "Cannot restore organization access" + "message": "Impossibile ripristinare l'accesso all'organizzazione" }, "allApplicationsWithCount": { "message": "Tutte le applicazioni ($COUNT$)", @@ -48,10 +51,10 @@ } }, "createNewLoginItem": { - "message": "Create new login item" + "message": "Crea nuovo elemento di login" }, "criticalApplicationsWithCount": { - "message": "Critical applications ($COUNT$)", + "message": "Applicazioni critiche ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -69,7 +72,7 @@ } }, "noAppsInOrgTitle": { - "message": "No applications found in $ORG NAME$", + "message": "Nessuna applicazione trovata in $ORG NAME$", "placeholders": { "org name": { "content": "$1", @@ -78,49 +81,88 @@ } }, "noAppsInOrgDescription": { - "message": "As users save logins, applications appear here, showing any at-risk passwords. Mark critical apps and notify users to update passwords." + "message": "Quando gli utenti salvano i login, i relativi dati e le password a rischio sono mostrati qui. Contrassegna le applicazioni critiche e notifica agli utenti di aggiornare le password." }, "noCriticalAppsTitle": { - "message": "You haven't marked any applications as a Critical" + "message": "Non hai contrassegnato nessuna applicazione come critica" }, "noCriticalAppsDescription": { - "message": "Select your most critical applications to discover at-risk passwords, and notify users to change those passwords." + "message": "Seleziona le applicazioni critiche per scoprire le password a rischio e invitare gli utenti a modificarle." }, "markCriticalApps": { - "message": "Mark critical apps" + "message": "Seleziona le applicazioni critiche" }, "markAppAsCritical": { - "message": "Mark app as critical" + "message": "Contrassegna l'applicazione come critica" }, "appsMarkedAsCritical": { - "message": "Apps marked as critical" + "message": "Contrassegna le applicazioni selezionate come critiche" }, "application": { - "message": "Application" + "message": "Applicazione" }, "atRiskPasswords": { - "message": "At-risk passwords" + "message": "Password a rischio" }, "requestPasswordChange": { - "message": "Request password change" + "message": "Richiedi modifica password" }, "totalPasswords": { - "message": "Total passwords" + "message": "Password totali" }, "searchApps": { - "message": "Search applications" + "message": "Cerca applicazioni" }, "atRiskMembers": { - "message": "At-risk members" + "message": "Membri a rischio" + }, + "atRiskMembersWithCount": { + "message": "Membri a rischio ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Applicazioni a rischio ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "Questi membri accedono ad applicazioni con parole d'accesso deboli, esposte, o riutilizzate." + }, + "atRiskApplicationsDescription": { + "message": "Queste applicazioni hanno parole d'accesso deboli, esposte o riutilizzate." + }, + "atRiskMembersDescriptionWithApp": { + "message": "Questi membri stanno entrando in $APPNAME$ con parole d'accesso deboli, esposte, o riutilizzate.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } }, "totalMembers": { - "message": "Total members" + "message": "Membri totali" }, "atRiskApplications": { - "message": "At-risk applications" + "message": "Applicazioni a rischio" }, "totalApplications": { - "message": "Total applications" + "message": "Applicazioni totali" + }, + "unmarkAsCriticalApp": { + "message": "Contrassegna l'applicazione come non critica" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Applicazione contrassegnata come non critica" }, "whatTypeOfItem": { "message": "Che tipo di elemento è questo?" @@ -159,6 +201,9 @@ "notes": { "message": "Note" }, + "privateNote": { + "message": "Nota privata" + }, "note": { "message": "Nota" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Trascina per ordinare" }, + "dragToReorder": { + "message": "Trascina per riordinare" + }, "cfTypeText": { "message": "Testo" }, @@ -393,17 +441,17 @@ "message": "Booleano" }, "cfTypeCheckbox": { - "message": "Checkbox" + "message": "Caselle di controllo" }, "cfTypeLinked": { "message": "Collegato", "description": "This describes a field that is 'linked' (related) to another field." }, "fieldType": { - "message": "Field type" + "message": "Tipo di campo" }, "fieldLabel": { - "message": "Field label" + "message": "Etichetta campo" }, "remove": { "message": "Rimuovi" @@ -425,6 +473,31 @@ "editFolder": { "message": "Modifica cartella" }, + "editWithName": { + "message": "Modifica $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "Nuova cartella" + }, + "folderName": { + "message": "Nome cartella" + }, + "folderHintText": { + "message": "Annida una cartella aggiungendo il nome della cartella superiore seguito da un “/”. Esempio: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Sei sicuro di voler eliminare definitivamente questo cartella?" + }, "baseDomain": { "message": "Dominio di base", "description": "Domain name. Example: website.com" @@ -469,7 +542,7 @@ "message": "Genera password" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "Genera passphrase" }, "checkPassword": { "message": "Verifica se la password è stata esposta." @@ -572,7 +645,7 @@ "message": "Nota sicura" }, "typeSshKey": { - "message": "SSH key" + "message": "Chiave SSH" }, "typeLoginPlural": { "message": "Login" @@ -650,7 +723,7 @@ "message": "Visualizza elemento" }, "newItemHeader": { - "message": "New $TYPE$", + "message": "Nuovo $TYPE$", "placeholders": { "type": { "content": "$1", @@ -659,7 +732,7 @@ } }, "editItemHeader": { - "message": "Edit $TYPE$", + "message": "Modifica $TYPE$", "placeholders": { "type": { "content": "$1", @@ -689,15 +762,6 @@ "itemName": { "message": "Nome elemento" }, - "cannotRemoveViewOnlyCollections": { - "message": "Non puoi rimuovere raccolte con i soli permessi di visualizzazione: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "es.", "description": "Short abbreviation for 'example'." @@ -722,7 +786,7 @@ } }, "copySuccessful": { - "message": "Copy Successful" + "message": "Copia riuscita" }, "copyValue": { "message": "Copia valore", @@ -733,11 +797,11 @@ "description": "Copy password to clipboard" }, "copyPassphrase": { - "message": "Copy passphrase", + "message": "Copia passphrase", "description": "Copy passphrase to clipboard" }, "passwordCopied": { - "message": "Password copied" + "message": "Password copiata" }, "copyUsername": { "message": "Copia nome utente", @@ -756,7 +820,7 @@ "description": "Copy URI to clipboard" }, "copyCustomField": { - "message": "Copy $FIELD$", + "message": "Copia $FIELD$", "placeholders": { "field": { "content": "$1", @@ -765,34 +829,34 @@ } }, "copyWebsite": { - "message": "Copy website" + "message": "Copia sito Web" }, "copyNotes": { - "message": "Copy notes" + "message": "Copia note" }, "copyAddress": { - "message": "Copy address" + "message": "Copia indirizzo" }, "copyPhone": { - "message": "Copy phone" + "message": "Copia telefono" }, "copyEmail": { - "message": "Copy email" + "message": "Copia email" }, "copyCompany": { - "message": "Copy company" + "message": "Copia azienda" }, "copySSN": { - "message": "Copy Social Security number" + "message": "Copia codice fiscale" }, "copyPassportNumber": { - "message": "Copy passport number" + "message": "Copia numero passaporto" }, "copyLicenseNumber": { - "message": "Copy license number" + "message": "Copia numero patente" }, "copyName": { - "message": "Copy name" + "message": "Copia nome" }, "me": { "message": "Io" @@ -815,9 +879,6 @@ "filter": { "message": "Filtra" }, - "moveSelectedToOrg": { - "message": "Sposta selezionati in organizzazione" - }, "deleteSelected": { "message": "Elimina selezionati" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Elementi selezionati spostati in $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Elementi spostati su $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "No" }, + "location": { + "message": "Luogo" + }, "loginOrCreateNewAccount": { "message": "Entra o crea un nuovo account per accedere alla tua cassaforte." }, @@ -994,7 +1049,7 @@ "message": "L'accesso con dispositivo deve essere abilitato nelle impostazioni dell'app Bitwarden. Ti serve un'altra opzione?" }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "Ti serve un'alternativa?" }, "loginWithMasterPassword": { "message": "Accedi con password principale" @@ -1009,13 +1064,13 @@ "message": "Usa un altro metodo di accesso" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Accedi con passkey" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "Usa login unificato (SSO)" }, "welcomeBack": { - "message": "Welcome back" + "message": "Bentornat*" }, "invalidPasskeyPleaseTryAgain": { "message": "Passkey non valida. Riprova." @@ -1099,7 +1154,7 @@ "message": "Crea account" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Hai appena iniziato ad usare Bitwarden?" }, "setAStrongPassword": { "message": "Imposta una password robusta" @@ -1117,20 +1172,44 @@ "message": "Accedi" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Accedi a Bitwarden" + }, + "enterTheCodeSentToYourEmail": { + "message": "Inserisci il codice che hai ricevuto via email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Inserisci il codice generato dalla tua app di autenticazione" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Conferma con la tua chiave YubiKey" }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "Tempo per l'autenticazione scaduto" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "La sessione di autenticazione è scaduta. Accedi di nuovo." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verifica la tua identità" }, + "weDontRecognizeThisDevice": { + "message": "Inserisci il codice che hai ricevuto via email per confermare la tua identità." + }, + "continueLoggingIn": { + "message": "Continua l'accesso" + }, + "whatIsADevice": { + "message": "Cos'è un dispositivo?" + }, + "aDeviceIs": { + "message": "Un dispositivo è un'installazione unica dell'app Bitwarden con la quale hai effettuato l'accesso. Reinstallazione, eliminazione dei dati dell'app o cancellazione dei cookie potrebbero causare la comparsa di un dispositivo più volte." + }, "logInInitiated": { "message": "Login avviato" }, + "logInRequestSent": { + "message": "Richiesta inviata" + }, "submit": { "message": "Invia" }, @@ -1187,19 +1266,13 @@ "message": "Account email" }, "requestHint": { - "message": "Request hint" + "message": "Richiedi suggerimento" }, "requestPasswordHint": { - "message": "Request password hint" + "message": "Richiedi suggerimento per la password" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { - "message": "Enter your account email address and your password hint will be sent to you" - }, - "passwordHint": { - "message": "Suggerimento per la password" - }, - "enterEmailToGetHint": { - "message": "Inserisci l'indirizzo email del tuo account per ricevere il suggerimento per la password principale." + "message": "Inserisci l'indirizzo email del tuo account Bitwarden per ricevere il suggerimento" }, "getMasterPasswordHint": { "message": "Ottieni suggerimento per la password principale" @@ -1257,7 +1330,7 @@ "message": "La cassaforte è bloccata" }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "Il tuo account è bloccato" }, "uuid": { "message": "UUID" @@ -1294,7 +1367,7 @@ "message": "Non hai i permessi necessari per visualizzare tutti gli elementi in questa raccolta." }, "youDoNotHavePermissions": { - "message": "You do not have permissions to this collection" + "message": "Non hai l'autorizzazione per l'accesso a questa raccolta" }, "noCollectionsInList": { "message": "Nessuna raccolta da mostrare." @@ -1320,11 +1393,38 @@ "notificationSentDevice": { "message": "Una notifica è stata inviata al tuo dispositivo." }, - "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "notificationSentDevicePart1": { + "message": "Sblocca Bitwarden sul tuo dispositivo o su " }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "areYouTryingToAccessYourAccount": { + "message": "Stai cercando di accedere al tuo account?" + }, + "accessAttemptBy": { + "message": "Tentativo di accesso di $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Conferma accesso" + }, + "denyAccess": { + "message": "Nega accesso" + }, + "notificationSentDeviceAnchor": { + "message": "app web" + }, + "notificationSentDevicePart2": { + "message": "Assicurarsi che la frase di impronta digitale corrisponda a quella sottostante prima dell'approvazione." + }, + "notificationSentDeviceComplete": { + "message": "Sblocca Bitwarden sul tuo dispositivo. Assicurati che la frase di impronta digitale corrisponda a quella sottostante prima di approvare." + }, + "aNotificationWasSentToYourDevice": { + "message": "Una notifica è stata inviata al tuo dispositivo" }, "versionNumber": { "message": "Versione $VERSION_NUMBER$", @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Ricordami" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Non chiedere più per 30 giorni su questo dispositivo" + }, "sendVerificationCodeEmailAgain": { "message": "Invia di nuovo l'email con codice di verifica" }, "useAnotherTwoStepMethod": { "message": "Usa un altro metodo di verifica in due passaggi" }, + "selectAnotherMethod": { + "message": "Scegli un altro metodo", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Usa un codice di recupero" + }, "insertYubiKey": { "message": "Inserisci la tua YubiKey nella porta USB del computer e premi il suo pulsante." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Opzioni verifica in due passaggi" }, + "selectTwoStepLoginMethod": { + "message": "Scegli il metodo di accesso in due passaggi" + }, "recoveryCodeDesc": { "message": "Hai perso l'accesso a tutti i tuoi metodi di verifica in due passaggi? Usa il tuo codice di recupero per disattivarli tutti dal tuo account." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Trasferito da FIDO)" }, + "openInNewTab": { + "message": "Apri in una nuova scheda del browser" + }, "emailTitle": { "message": "Email" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Scegli un'organizzazione in cui vuoi spostare questo elemento. Spostarlo in un'organizzazione trasferisce la proprietà dell'elemento all'organizzazione. Una volta spostato, non sarai più il proprietario diretto di questo elemento." }, - "moveManyToOrgDesc": { - "message": "Scegli un'organizzazione in cui vuoi spostare questi elementi. Spostarli in un'organizzazione trasferisce la proprietà degli elementi all'organizzazione. Una volta spostati, non sarai più il proprietario diretto di questi elementi." - }, "collectionsDesc": { "message": "Modifica le raccolte con cui questo elemento è condiviso. Solo gli utenti di organizzazioni che hanno accesso a queste raccolte potranno visualizzare questo elemento." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "Hai selezionato $COUNT$ elementi. $MOVEABLE_COUNT$ elementi possono essere spostati in un'organizzazione, $NONMOVEABLE_COUNT$ no.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Codice di verifica (TOTP)" }, @@ -1610,12 +1706,9 @@ "description": "deprecated. Use avoidAmbiguous instead." }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "Evita caratteri ambigui", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Rigenera password" - }, "length": { "message": "Lunghezza" }, @@ -1651,32 +1744,32 @@ "message": "Includi numero" }, "generatorPolicyInEffect": { - "message": "Enterprise policy requirements have been applied to your generator options.", + "message": "I requisiti della politica aziendale sono stati applicati alle opzioni del generatore.", "description": "Indicates that a policy limits the credential generator screen." }, "passwordHistory": { "message": "Cronologia delle password" }, "generatorHistory": { - "message": "Generator history" + "message": "Cronologia generazione" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "Cancella cronologia" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "Vuoi davvero eliminare definitivamente tutti gli elementi della cronologia?" }, "noPasswordsInList": { "message": "Non ci sono password da mostrare." }, "clearHistory": { - "message": "Clear history" + "message": "Cancella cronologia" }, "nothingToShow": { - "message": "Nothing to show" + "message": "Nessun elemento" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "Nessuna generazione recente" }, "clear": { "message": "Cancella", @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Accedi di nuovo." }, + "currentSession": { + "message": "Sessione attuale" + }, + "requestPending": { + "message": "Richiesta in attesa" + }, "logBackInOthersToo": { "message": "Accedi di nuovo. Se stai usando Bitwarden su altre app, esci e rientra anche in quelle." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Zona pericolosa" }, - "dangerZoneDesc": { - "message": "Attento, queste azioni non sono reversibili!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Annulla autorizzazione sessioni" }, @@ -1797,11 +1890,32 @@ "deauthorizeSessionsWarning": { "message": "Inoltre, procedere ti farà uscire dalla sessione corrente, richiedendoti di accedere. Se attivata, dovrai completare la verifica in due passaggi di nuovo. Le sessioni attive su altri dispositivi potrebbero continuare a rimanere attive per un massimo di un'ora." }, + "newDeviceLoginProtection": { + "message": "Accesso nuovo dispositivo" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Disattiva la nuova protezione di accesso del dispositivo" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Attiva la nuova protezione di accesso del dispositivo" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Procedi qui sotto per disattivare le e-mail di verifica che Bitwarden invia quando accedi da un nuovo dispositivo." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Procedi qui sotto per far sì che Bitwarden invii e-mail di verifica quando accedi da un nuovo dispositivo." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "Con la nuova protezione di accesso del dispositivo disattivata, chiunque con la tua parola d'accesso principale può accedere al tuo account da qualsiasi dispositivo. Per proteggere il tuo account senza e-mail di verifica, imposta l'accesso in due passaggi." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "Modifiche alla protezione del nuovo dispositivo salvate" + }, "sessionsDeauthorized": { "message": "Tutte le sessioni revocate" }, "accountIsOwnedMessage": { - "message": "This account is owned by $ORGANIZATIONNAME$", + "message": "Questo account è gestito da $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Impostare la verifica in due passaggi potrebbe bloccarti permanentemente fuori dal tuo account Bitwarden. Un codice di recupero ti permette di accedere al tuo account il caso non potessi più usare il tuo solito metodo di verifica in due passaggi (per esempio se perdi il telefono). L'assistenza clienti di Bitwarden non sarà in grado di aiutarti se perdi l'accesso al tuo account. Scrivi o stampa il tuo codice di recupero e conservalo in un luogo sicuro." }, + "yourSingleUseRecoveryCode": { + "message": "Puoi usare il codice di recupero monouso se non hai accesso a nessuno dei metodi impostati per l'accesso in due passaggi. Se accedi con un codice, l'accesso in due passaggi sarà disattivato. Conserva il codice in un luogo sicuro e accessibile solo a te." + }, "viewRecoveryCode": { "message": "Visualizza codice di recupero" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Gestisci" }, - "canManage": { - "message": "Può gestire" + "manageCollection": { + "message": "Gestisci raccolta" + }, + "viewItems": { + "message": "Vedi voci" + }, + "viewItemsHidePass": { + "message": "Vedi elementi, parole d'accesso nascoste" + }, + "editItems": { + "message": "Modifica voci" + }, + "editItemsHidePass": { + "message": "Modifica elementi, parole d'accesso nascoste" }, "disable": { "message": "Disattiva" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Revoca accesso" }, + "revoke": { + "message": "Revoca" + }, "twoStepLoginProviderEnabled": { "message": "Questo metodo di verifica in due passaggi è attivo sul tuo account." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "Si è verificato un problema durante la lettura della chiave di sicurezza. Riprova." }, - "twoFactorWebAuthnWarning": { - "message": "A causa di limitazioni della piattaforma, WebAuthn non può essere utilizzato su tutte le app Bitwarden. Dovresti abilitare un altro metodo di verifica in due passaggi per accedere al tuo account anche quando WebAuthn non può essere usato. Piattaforme supportate:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Cassaforte Web ed estensione per il browser desktop/laptop con un browser abilitato per WebAuthn (Chrome, Opera, Vivaldi o Firefox con FIDO U2F abilitato)." + "twoFactorWebAuthnWarning1": { + "message": "A causa delle limitazioni della piattaforma, WebAuthn non può essere usato in tutte le applicazioni Bitwarden. È necessario impostare un altro fornitore d'accesso in due passaggi in modo da poter accedere al tuo account quando WebAuthn non può essere usato." }, "twoFactorRecoveryYourCode": { "message": "Il tuo codice di recupero Bitwarden per la verifica in due passaggi" @@ -2405,7 +2534,7 @@ "message": "Controlla password esposte" }, "timesExposed": { - "message": "Times exposed" + "message": "Quante volte vittima o a rischio di data breach" }, "exposedXTimes": { "message": "Esposta $COUNT$ volte", @@ -2442,7 +2571,7 @@ "message": "Nessun elemento nella tua cassaforte ha password deboli." }, "weakness": { - "message": "Weakness" + "message": "Debolezza" }, "reusedPasswordsReport": { "message": "Password riutilizzate" @@ -2470,7 +2599,7 @@ "message": "Nessun login nella tua cassaforte ha password riutilizzate." }, "timesReused": { - "message": "Times reused" + "message": "Quante volte riutilizzata" }, "reusedXTimes": { "message": "Riutilizzata $COUNT$ volte", @@ -2770,7 +2899,7 @@ "message": "Scarica licenza" }, "viewBillingToken": { - "message": "View Billing Token" + "message": "Visualizza token di fatturazione" }, "updateLicense": { "message": "Aggiorna licenza" @@ -2819,10 +2948,10 @@ "message": "Fatture" }, "noUnpaidInvoices": { - "message": "No unpaid invoices." + "message": "Nessuna fattura non pagata." }, "noPaidInvoices": { - "message": "No paid invoices." + "message": "Nessuna fattura pagata." }, "paid": { "message": "Pagata", @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Hai ancora 1 invito." + }, + "inviteZeroEmailDesc": { + "message": "Hai 0 inviti rimanenti." + }, "userUsingTwoStep": { "message": "Questo utente usa la verifica in due passaggi per proteggere il suo account." }, @@ -3430,7 +3565,7 @@ } }, "viewAllLogInOptions": { - "message": "View all log in options" + "message": "Visualizza tutte le opzioni di accesso" }, "viewAllLoginOptions": { "message": "Visualizza tutte le opzioni di accesso" @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "SSO non collegato." + }, "unlinkedSsoUser": { "message": "SSO per l'utente $ID$ scollegato.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Dispositivo" }, + "loginStatus": { + "message": "Stato accesso" + }, + "firstLogin": { + "message": "Primo accesso" + }, + "trusted": { + "message": "Di fiducia" + }, + "needsApproval": { + "message": "Necessita approvazione" + }, + "areYouTryingtoLogin": { + "message": "Stai tentando di accedere?" + }, + "logInAttemptBy": { + "message": "Tentativo di accesso di $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Tipo di dispositivo" + }, + "ipAddress": { + "message": "Indirizzo IP" + }, + "confirmLogIn": { + "message": "Conferma accesso" + }, + "denyLogIn": { + "message": "Nega accesso" + }, + "thisRequestIsNoLongerValid": { + "message": "La richiesta non è più valida." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Accesso confermato per $EMAIL$ con $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "Hai negato un tentativo di accesso da un altro dispositivo. Se eri davvero tu, prova di nuovo ad accedere con il dispositivo." + }, + "loginRequestHasAlreadyExpired": { + "message": "La richiesta di accesso è già scaduta." + }, + "justNow": { + "message": "Proprio ora" + }, + "requestedXMinutesAgo": { + "message": "Richiesto $MINUTES$ minuti fa", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creazione account su" }, @@ -3883,13 +4091,19 @@ "message": "Aggiorna browser" }, "generatingRiskInsights": { - "message": "Generating your risk insights..." + "message": "Generazione delle tue informazioni sui rischi..." }, "updateBrowserDesc": { "message": "Stai utilizzando un browser non supportato. La cassaforte web potrebbe non funzionare correttamente." }, + "youHaveAPendingLoginRequest": { + "message": "Hai una richiesta di accesso in sospeso da un altro dispositivo." + }, + "reviewLoginRequest": { + "message": "Rivedi richiesta di accesso" + }, "freeTrialEndPromptCount": { - "message": "Your free trial ends in $COUNT$ days.", + "message": "Il tuo periodo di prova scade tra $COUNT$ giorni.", "placeholders": { "count": { "content": "$1", @@ -3898,7 +4112,7 @@ } }, "freeTrialEndPromptMultipleDays": { - "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "message": "$ORGANIZATION$, la tua prova gratuita termina tra $COUNT$ giorni.", "placeholders": { "count": { "content": "$2", @@ -3911,7 +4125,7 @@ } }, "freeTrialEndPromptTomorrow": { - "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "message": "$ORGANIZATION$, la tua prova gratuita termina domani.", "placeholders": { "organization": { "content": "$1", @@ -3920,10 +4134,10 @@ } }, "freeTrialEndPromptTomorrowNoOrgName": { - "message": "Your free trial ends tomorrow." + "message": "La tua prova gratuita termina domani." }, "freeTrialEndPromptToday": { - "message": "$ORGANIZATION$, your free trial ends today.", + "message": "$ORGANIZATION$, la tua prova gratuita termina domani.", "placeholders": { "organization": { "content": "$1", @@ -3932,16 +4146,16 @@ } }, "freeTrialEndingTodayWithoutOrgName": { - "message": "Your free trial ends today." + "message": "La tua prova gratuita termina oggi." }, "clickHereToAddPaymentMethod": { - "message": "Click here to add a payment method." + "message": "Clicca qui per aggiungere un metodo di pagamento." }, "joinOrganization": { "message": "Unisciti all'organizzazione" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "Unisciti a $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "Se non puoi accedere al tuo account usando i normali metodi di verifica in due passaggi, usa il tuo codice di recupero per disattivarli tutti dal tuo account." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Accedi utilizzando il tuo codice di recupero monouso. Tutti i metodi di accesso in due passaggi saranno disattivati sul tuo account." + }, "recoverAccountTwoStep": { "message": "Ripristina verifica in due passaggi dell'account" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Non è possibile procedere con l'aggiornamento della chiave di cifratura" }, + "editFieldLabel": { + "message": "Modifica $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Riordina $LABEL$. Utilizza i tasti freccia per spostare l'elemento verso l'alto o verso il basso.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ spostato su, in posizione $INDEX$ di $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ spostato giù, in posizione $INDEX$ di $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "Quando si aggiorna la chiave di cifratura, le cartelle non possono essere decifrate. Per continuare con l'aggiornamento, le cartelle devono essere eliminate. Nessun elemento della cassaforte verrà eliminato se si procede." }, @@ -4492,7 +4761,7 @@ "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "youWillBeNotifiedOnceTheRequestIsApproved": { - "message": "You will be notified once the request is approved" + "message": "Riceverai una notifica quando la richiesta sarà approvata" }, "free": { "message": "Gratis", @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Imposta requisiti minimi di complessità della password principale." }, + "passwordStrengthScore": { + "message": "Valutazione complessità parola d'accesso $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Verifica in due passaggi obbligatoria" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Imposta requisiti minimi di complessità per il generatore di password." }, - "passwordGeneratorPolicyInEffect": { - "message": "Una o più politiche dell'organizzazione stanno influenzando le impostazioni del tuo generatore." - }, "masterPasswordPolicyInEffect": { "message": "Una o più politiche dell'organizzazione richiedono che la tua password principale soddisfi questi requisiti:" }, @@ -4725,10 +5000,10 @@ "message": "Accedi usando il portale di accesso (SSO) della tua organizzazione. Inserisci l'identificativo della tua organizzazione per iniziare." }, "singleSignOnEnterOrgIdentifier": { - "message": "Enter your organization's SSO identifier to begin" + "message": "Inserisci l'identificatore SSO della tua organizzazione per iniziare" }, "singleSignOnEnterOrgIdentifierText": { - "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + "message": "Per accedere con il tuo provider SSO, inserisci l'identificatore SSO della tua organizzazione. Potrebbe essere necessario inserirlo di nuovo quando si accede da un nuovo dispositivo." }, "enterpriseSingleSignOn": { "message": "Single Sign-On aziendale" @@ -4798,7 +5073,7 @@ "message": "Impedisci ai membri di unirsi ad altre organizzazioni." }, "singleOrgPolicyDesc": { - "message": "Restrict members from joining other organizations. This policy is required for organizations that have enabled domain verification." + "message": "Impedisci ai membri di entrare in altre organizzazioni. Questa politica è richiesta per le organizzazioni che hanno abilitato la verifica del dominio." }, "singleOrgBlockCreateMessage": { "message": "La tua organizzazione corrente ha una politica che non ti consente di unirti ad altre organizzazioni. Contatta gli amministratori della tua organizzazione o registrati da un altro account Bitwarden." @@ -4807,7 +5082,7 @@ "message": "I membri dell'organizzazione che non sono proprietari o amministratori e sono membri di un'altra organizzazione saranno rimossi dalla tua organizzazione." }, "singleOrgPolicyMemberWarning": { - "message": "Non-compliant members will be placed in revoked status until they leave all other organizations. Administrators are exempt and can restore members once compliance is met." + "message": "I membri non conformi entreranno in stato 'revocato' finché non lasceranno tutte le altre organizzazioni. Gli amministratori sono esenti e possono ripristinare i membri una volta soddisfatta la conformità." }, "requireSso": { "message": "Richiedi autenticazione Single Sign-On" @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "I proprietari e gli amministratori dell'organizzazione sono esonerati dall'applicazione di questa politica." }, + "limitSendViews": { + "message": "Limita visualizzazioni" + }, + "limitSendViewsHint": { + "message": "Nessuno potrà vedere questo Send al raggiungimento del limite.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ visualizzazioni rimaste", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Dettagli Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Testo da condividere" + }, "sendTypeFile": { "message": "File" }, "sendTypeText": { "message": "Testo" }, + "sendPasswordDescV3": { + "message": "Richiedi ai destinatari una parola d'accesso opzionale per aprire questo Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Nuovo Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Elimina Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Sei sicuro di voler eliminare questo Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "Che tipo di Send è questo?", + "deleteSendPermanentConfirmation": { + "message": "Sicuro di voler eliminare definitivamente questo Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Data di eliminazione" }, - "deletionDateDesc": { - "message": "Il Send sarà eliminato definitivamente alla data e all'ora specificate.", + "deletionDateDescV2": { + "message": "Il Send sarà eliminato definitivamente in questa data.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Numero massimo di accessi" }, - "maxAccessCountDesc": { - "message": "Se impostata, gli utenti non potranno più accedere a questo Send una volta raggiunto il numero massimo di accessi.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Numero di accessi correnti" - }, - "sendPasswordDesc": { - "message": "Richiedi una password agli utenti per accedere a questo Send (facoltativo).", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Note private sul Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Disabilitato" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Sei sicuro di voler rimuovere la password?" }, - "hideEmail": { - "message": "Nascondi il mio indirizzo email dai destinatari." - }, - "disableThisSend": { - "message": "Disattiva il Send per renderlo inaccessibile.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Tutti i Send" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "In attesa di eliminazione" }, + "hideTextByDefault": { + "message": "Nascondi testo come default" + }, "expired": { "message": "Scaduto" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "Queste politiche dell'organizzazione sono attualmente in vigore:" - }, - "sendDisableHideEmailInEffect": { - "message": "Gli utenti non possono nascondere il loro indirizzo email dai destinatari quando creano o modificano un Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "modifiedPolicyId": { "message": "Politica $ID$ modificata.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Rimuovi la proprietà individuale per gli utenti dell'organizzazione" }, - "textHiddenByDefault": { - "message": "Quando si accede al Send, nascondi il testo per impostazione predefinita", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Un nome intuitivo per descrivere il Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Il testo che vuoi inviare." - }, - "sendFileDesc": { - "message": "Il file che vuoi inviare." - }, - "copySendLinkOnSave": { - "message": "Copia il link al Send negli appunti dopo averlo salvato." - }, - "sendLinkLabel": { - "message": "Link del Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "Si è verificato un errore durante il salvataggio delle date di eliminazione e scadenza." }, + "hideYourEmail": { + "message": "Nascondi il tuo indirizzo e-mail ai visualizzatori." + }, "webAuthnFallbackMsg": { "message": "Per verificare la tua 2FA clicca il pulsante qui sotto." }, "webAuthnAuthenticate": { "message": "Autenticazione WebAuthn" }, + "readSecurityKey": { + "message": "Leggi chiave di sicurezza" + }, + "awaitingSecurityKeyInteraction": { + "message": "In attesa di interazione con la chiave di sicurezza..." + }, "webAuthnNotSupported": { "message": "WebAuthn non è supportato da questo browser." }, @@ -5635,10 +5896,10 @@ "message": "Escluso, non applicabile per questa azione" }, "nonCompliantMembersTitle": { - "message": "Non-compliant members" + "message": "Membri non conformi" }, "nonCompliantMembersError": { - "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + "message": "I membri non conformi alla politica di accesso dell'organizzazione non possono essere ripristinati finché non aderiscono ai requisiti di policy" }, "fingerprint": { "message": "Impronta" @@ -5655,6 +5916,20 @@ "error": { "message": "Errore" }, + "decryptionError": { + "message": "Errore di decifrazione" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden non può decifrare gli elementi elencati di seguito." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contatta il cliente correttamente", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "per evitare ulteriori perdite di dati.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Gestisci utenti deve essere abilitato con il permesso di gestire il ripristino delle password" }, @@ -5848,7 +6123,7 @@ } }, "vaultTimeoutPolicyInEffect1": { - "message": "$HOURS$ hour(s) and $MINUTES$ minute(s) maximum.", + "message": "Max $HOURS$ ora/e e $MINUTES$ minuto/i.", "placeholders": { "hours": { "content": "$1", @@ -6292,7 +6567,7 @@ "message": "Visualizza token di sincronizzazione della fatturazione" }, "generateBillingToken": { - "message": "Generate billing token" + "message": "Genera token di fatturazione" }, "copyPasteBillingSync": { "message": "Copia e incolla questo token nelle impostazioni di sincronizzazione della fatturazione della tua organizzazione self-hosted." @@ -6301,7 +6576,7 @@ "message": "Il token di sincronizzazione della fatturazione può accedere e modificare le impostazioni di abbonamento di questa organizzazione." }, "manageBillingTokenSync": { - "message": "Manage Billing Token" + "message": "Gestisci token di fatturazione" }, "setUpBillingSync": { "message": "Configura sincronizzazione della fatturazione" @@ -6367,7 +6642,7 @@ "message": "Token di sincronizzazione della fatturazione" }, "automaticBillingSyncDesc": { - "message": "Automatic sync unlocks Families sponsorships and allows you to sync your license without uploading a file. After making updates in the Bitwarden cloud server, select Sync License to apply changes." + "message": "La sincronizzazione automatica sblocca le sponsorizzazioni delle famiglie e consente di sincronizzare la licenza senza caricare un file. Dopo aver effettuato gli aggiornamenti nel server cloud Bitwarden, seleziona 'Sincronizza licenza' per applicare le modifiche." }, "active": { "message": "Attivo" @@ -6437,7 +6712,7 @@ "message": "Obbligatorio se l'ID dell'entità non è un URL." }, "offerNoLongerValid": { - "message": "This offer is no longer valid. Contact your organization administrators for more information." + "message": "Questa offerta non è più valida. Contatta gli amministratori dell'organizzazione per maggiori informazioni." }, "openIdOptionalCustomizations": { "message": "Personalizzazioni facoltative" @@ -6516,23 +6791,14 @@ "message": "Generatore", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "Cosa vuoi generare?" - }, - "passwordType": { - "message": "Tipo di password" - }, - "regenerateUsername": { - "message": "Rigenera nome utente" - }, "generateUsername": { "message": "Genera nome utente" }, "generateEmail": { - "message": "Generate email" + "message": "Genera email" }, "spinboxBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$.", + "message": "Il valore deve essere compreso tra $MIN$ e $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6546,7 +6812,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "message": " Usa $RECOMMENDED$ o più caratteri per generare una password forte.", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -6556,7 +6822,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "message": " Usa $RECOMMENDED$ o più parole per generare una passphrase forte.", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Tipo di nome utente" - }, "plusAddressedEmail": { "message": "Indirizzo email alternativo", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Usa la casella di posta catch-all di dominio." }, + "useThisEmail": { + "message": "Usa questa e-mail" + }, "random": { "message": "Casuale", "description": "Generates domain-based username using random letters" @@ -6671,11 +6937,11 @@ "message": "Genera un alias email con un servizio di inoltro esterno." }, "forwarderDomainName": { - "message": "Email domain", + "message": "Dominio email", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Choose a domain that is supported by the selected service", + "message": "Scegli un dominio supportato dal servizio selezionato", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ ha rifiutato la tua richiesta. Contatta il tuo fornitore di servizi per assistenza.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ ha respinto la tua richiesta: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Impossibile ottenere l'ID dell'account email mascherato di $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Nome host", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "Token di accesso API" - }, "deviceVerification": { "message": "Verifica del dispositivo" }, @@ -6820,7 +7107,7 @@ "description": "the text, 'SCIM', is an acronym and should not be translated." }, "scimIntegrationDescription": { - "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "message": "Fornisci automaticamente a utenti e gruppi il tuo provider di identità preferito tramite il provisioning SCIM. Trova integrazioni supportate", "description": "the text, 'SCIM', is an acronym and should not be translated." }, "scimEnabledCheckboxDesc": { @@ -6942,10 +7229,10 @@ } }, "singleFieldNeedsAttention": { - "message": "1 field needs your attention." + "message": "1 campo richiede tua attenzione." }, "multipleFieldsNeedAttention": { - "message": "$COUNT$ fields need your attention.", + "message": "$COUNT$ campi richiedono la tua attenzione.", "placeholders": { "count": { "content": "$1", @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Per il tuo account è richiesta la verifica in due passaggi DUO." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "L'accesso Duo in due passaggi è richiesto per il tuo account. Segui i passaggi qui sotto per proseguire." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Segui i passaggi qui sotto per completare l'accesso." + }, "launchDuo": { "message": "Avvia DUO" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Numero di utenti" }, - "loggingInAs": { - "message": "Accedendo come" - }, - "notYou": { - "message": "Non sei tu?" - }, "pickAnAvatarColor": { "message": "Scegli un colore dell'avatar" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "Nessuna raccolta" }, - "canView": { - "message": "Può visualizzare" - }, - "canViewExceptPass": { - "message": "Può visualizzare, eccetto le password" - }, - "canEdit": { - "message": "Può modificare" - }, - "canEditExceptPass": { - "message": "Può modificare, eccetto le password" - }, "noCollectionsAdded": { "message": "Nessuna raccolta aggiunta" }, @@ -7802,7 +8077,7 @@ "message": "Carica file" }, "upload": { - "message": "Upload" + "message": "Carica" }, "acceptedFormats": { "message": "Formati accettati:" @@ -7814,13 +8089,13 @@ "message": "o" }, "unlockWithBiometrics": { - "message": "Unlock with biometrics" + "message": "Sblocca con i dati biometrici" }, "unlockWithPin": { - "message": "Unlock with PIN" + "message": "Sblocca con PIN" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "Sblocca con password principale" }, "licenseAndBillingManagement": { "message": "Licenza e gestione della fatturazione" @@ -7832,7 +8107,7 @@ "message": "Caricamento manuale" }, "manualBillingTokenUploadDesc": { - "message": "If you do not want to opt into billing sync, manually upload your license here. This will not automatically unlock Families sponsorships." + "message": "Se non vuoi usare la sincronizzazione di fatturazione, carica manualmente la licenza qui. Le sponsorizzazioni delle famiglie non saranno sbloccate automaticamente." }, "syncLicense": { "message": "Sincronizza licenza" @@ -8101,16 +8376,16 @@ "message": "Accesso avviato" }, "rememberThisDeviceToMakeFutureLoginsSeamless": { - "message": "Remember this device to make future logins seamless" + "message": "Ricorda questo dispositivo per rendere immediati i prossimi accessi" }, "deviceApprovalRequired": { "message": "Approvazione del dispositivo obbligatoria. Seleziona un'opzione di approvazione:" }, "deviceApprovalRequiredV2": { - "message": "Device approval required" + "message": "Approvazione dispositivo richiesta" }, "selectAnApprovalOptionBelow": { - "message": "Select an approval option below" + "message": "Seleziona un'opzione di approvazione" }, "rememberThisDevice": { "message": "Ricorda questo dispositivo" @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Richiedi approvazione dell'amministratore" }, - "approveWithMasterPassword": { - "message": "Approva con password principale" - }, "trustedDeviceEncryption": { "message": "Crittografia dispositivo fidato" }, "trustedDevices": { "message": "Dispositivi fidati" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Una volta autenticati, i membri decrittograferanno i dati della cassaforte usando una chiave memorizzata sul proprio dispositivo. La", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "I membri non avranno bisogno di una parola d'accesso principale durante l'accesso con SSO. La parola d'accesso principale è rimpiazzata da una chiave di crittografia memorizzata nel dispositivo, rendendo il dispositivo attendibile. Il primo dispositivo in cui un membro crea i propri account e accesso sarà attendibile. I nuovi dispositivi dovranno essere approvati da un dispositivo fidato esistente o da un amministratore. La", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "politica organizzazione unica", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "organizzazione singola", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": ", la", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "politica,", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "politica SSO obbligatorio", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO obbligatorio", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": ", e la", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "politica, e", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "politica amministrazione del recupero dell'account", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "Recupero account", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "con registrazione automatica si attiverà quando questa opzione è usata.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "la politica si attiverà quando questa opzione sarà utilizzata.", + "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": "Le autorizzazioni della tua organizzazione sono state aggiornate, obbligandoti a impostare una password principale.", @@ -8170,7 +8442,7 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "di $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approva richiesta" }, + "deviceApproved": { + "message": "Dispositivo approvato" + }, + "deviceRemoved": { + "message": "Dispositivo rimosso" + }, + "removeDevice": { + "message": "Rimuovi dispositivo" + }, + "removeDeviceConfirmation": { + "message": "Sei sicuro di voler rimuovere il dispositivo?" + }, "noDeviceRequests": { "message": "Nessuna richiesta da approvare" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "La tua richiesta è stata inviata al tuo amministratore." }, - "youWillBeNotifiedOnceApproved": { - "message": "Riceverai una notifica una volta approvato." - }, "troubleLoggingIn": { "message": "Problemi ad accedere?" }, @@ -8342,7 +8623,7 @@ "message": "Email utente mancante" }, "activeUserEmailNotFoundLoggingYouOut": { - "message": "Active user email not found. Logging you out." + "message": "Nessuna email attiva trovata per l'utente. Disconnessione in corso." }, "deviceTrusted": { "message": "Dispositivo fidato" @@ -8440,10 +8721,13 @@ "message": "Gestisci il comportamento delle raccolte per l'organizzazione" }, "limitCollectionCreationDesc": { - "message": "Limit collection creation to owners and admins" + "message": "Limita la creazione di raccolte ai proprietari e agli amministratori" }, "limitCollectionDeletionDesc": { - "message": "Limit collection deletion to owners and admins" + "message": "Limita l'eliminazione delle raccolte ai proprietari e agli amministratori" + }, + "limitItemDeletionDesc": { + "message": "Limita l'eliminazione di elementi ai membri con il permesso di gestione" }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Proprietari e amministratori possono gestire tutte le raccolte e gli elementi" @@ -8491,12 +8775,9 @@ "message": "URL del server" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "URL server self-hosted", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Dominio alias" - }, "alreadyHaveAccount": { "message": "Hai già un account?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "Non hai accesso alla gestione di questa raccolta." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Autorizzazione Può gestire mancante" + "grantManageCollectionWarningTitle": { + "message": "Permessi di gestione raccolte mancanti" }, - "grantAddAccessCollectionWarning": { - "message": "Concedi l'autorizzazione Può gestire per consentire la gestione completa della raccolta, inclusa l'eliminazione della raccolta." + "grantManageCollectionWarning": { + "message": "Concedi i permessi per la gestione completa della raccolta, inclusa l'eliminazione." }, "grantCollectionAccess": { "message": "Consenti a gruppi o membri di accedere a questa raccolta." @@ -8703,7 +8984,7 @@ "message": "Aggiungi campo" }, "editField": { - "message": "Edit field" + "message": "Campo 'Modifica'" }, "items": { "message": "Elementi" @@ -9018,47 +9299,62 @@ "message": "Usa l'SDK di Bitwarden Secrets Manager nei seguenti linguaggi di programmazione per creare le tue applicazioni." }, "ssoDescStart": { - "message": "Configure", + "message": "Configura", "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, "ssoDescEnd": { - "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "message": "per Bitwarden consultando la guida del tuo gestore di identità.", "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, "userProvisioning": { - "message": "User provisioning" + "message": "Provisioning utenti" }, "scimIntegration": { "message": "SCIM" }, "scimIntegrationDescStart": { - "message": "Configure ", + "message": "Configura ", "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, "scimIntegrationDescEnd": { - "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "message": "(System for Cross-domain Identity Management) per configurare automaticamente utenti e gruppi in Bitwarden consultando la guida del tuo gestore di identità.", "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, "bwdc": { "message": "Bitwarden Directory Connector" }, "bwdcDesc": { - "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." + "message": "Configura Bitwarden Directory Connector per configurare automaticamente utenti e gruppi in Bitwarden consultando la guida del tuo gestore di identità." }, "eventManagement": { - "message": "Event management" + "message": "Gestione eventi" }, "eventManagementDesc": { - "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." + "message": "Integra i log degli eventi Bitwarden con il tuo sistema SIEM (informazioni di sistema e gestione degli eventi) consultando la guida della tua piattaforma." }, "deviceManagement": { - "message": "Device management" + "message": "Gestione dispositivi" }, "deviceManagementDesc": { - "message": "Configure device management for Bitwarden using the implementation guide for your platform." + "message": "Configura la gestione dispositivi consultando la guida per la tua piattaforma." + }, + "deviceIdMissing": { + "message": "ID dispositivo mancante" + }, + "deviceTypeMissing": { + "message": "Tipo di dispositivo mancante" + }, + "deviceCreationDateMissing": { + "message": "Data di creazione del dispositivo mancante" + }, + "desktopRequired": { + "message": "Desktop richiesto" + }, + "reopenLinkOnDesktop": { + "message": "Riapri questo link dalla tua email su un computer portatile o fisso." }, "integrationCardTooltip": { - "message": "Launch $INTEGRATION$ implementation guide.", + "message": "Avvia la guida di $INTEGRATION$.", "placeholders": { "integration": { "content": "$1", @@ -9067,7 +9363,7 @@ } }, "smIntegrationTooltip": { - "message": "Set up $INTEGRATION$.", + "message": "Configura $INTEGRATION$.", "placeholders": { "integration": { "content": "$1", @@ -9076,7 +9372,7 @@ } }, "smSdkTooltip": { - "message": "View $SDK$ repository", + "message": "Visualizza repository $SDK$", "placeholders": { "sdk": { "content": "$1", @@ -9085,7 +9381,7 @@ } }, "integrationCardAriaLabel": { - "message": "open $INTEGRATION$ implementation guide in a new tab.", + "message": "apri la guida di $INTEGRATION$ in una nuova scheda.", "placeholders": { "integration": { "content": "$1", @@ -9094,7 +9390,7 @@ } }, "smSdkAriaLabel": { - "message": "view $SDK$ repository in a new tab.", + "message": "visualizza il repository $SDK$ in una nuova scheda.", "placeholders": { "sdk": { "content": "$1", @@ -9103,7 +9399,7 @@ } }, "smIntegrationCardAriaLabel": { - "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "message": "apri la guida di $INTEGRATION$ in una nuova scheda.", "placeholders": { "integration": { "content": "$1", @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "mese per membro" }, + "monthPerMemberBilledAnnually": { + "message": "mese per membro fatturato annualmente" + }, "seats": { "message": "Slot" }, @@ -9151,13 +9450,13 @@ "message": "Gestisci la fatturazione dal Portale del Fornitore" }, "continueSettingUpFreeTrial": { - "message": "Continue setting up your free trial of Bitwarden" + "message": "Continua a configurare la tua prova gratuita di Bitwarden" }, "continueSettingUpFreeTrialPasswordManager": { - "message": "Continue setting up your free trial of Bitwarden Password Manager" + "message": "Continua a configurare la tua prova gratuita di Bitwarden Password Manager" }, "continueSettingUpFreeTrialSecretsManager": { - "message": "Continue setting up your free trial of Bitwarden Secrets Manager" + "message": "Continua a configurare la tua prova gratuita di Bitwarden Secrets Manager" }, "enterTeamsOrgInfo": { "message": "Inserisci le informazioni dell'organizzazione del tuo team" @@ -9221,10 +9520,10 @@ "message": "Fornitore di servizi gestiti" }, "managedServiceProvider": { - "message": "Managed service provider" + "message": "Fornitore di servizi gestiti" }, "multiOrganizationEnterprise": { - "message": "Multi-organization enterprise" + "message": "Azienda multi-organizzazione" }, "orgSeats": { "message": "Slot dell'organizzazione" @@ -9272,16 +9571,16 @@ "message": "Informazioni fiscali aggiornate" }, "billingInvalidTaxIdError": { - "message": "Invalid tax ID, if you believe this is an error please contact support." + "message": "Codice fiscale non valido. Se credi che si tratti di un errore, contatta il supporto." }, "billingTaxIdTypeInferenceError": { - "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + "message": "Non è stato possibile convalidare il tuo codice fiscale. Se credi che si tratti di un errore, contatta il supporto." }, "billingPreviewInvalidTaxIdError": { - "message": "Invalid tax ID, if you believe this is an error please contact support." + "message": "Codice fiscale non valido. Se credi che si tratti di un errore, contatta il supporto." }, "billingPreviewInvoiceError": { - "message": "An error occurred while previewing the invoice. Please try again later." + "message": "Errore nella generazione dell'anteprima della ricevuta. Prova di nuovo." }, "unverified": { "message": "Non verificato" @@ -9488,68 +9787,74 @@ "message": "slot acquistati rimossi" }, "environmentVariables": { - "message": "Environment variables" + "message": "Variabili d'ambiente" }, "organizationId": { - "message": "Organization ID" + "message": "ID organizzazione" }, "projectIds": { - "message": "Project IDs" + "message": "ID progetto" }, "projectId": { - "message": "Project ID" + "message": "ID Progetto" }, "projectsAccessedByMachineAccount": { - "message": "The following projects can be accessed by this machine account." + "message": "I seguenti progetti possono essere accessibili da questo account macchina." }, "config": { - "message": "Config" + "message": "Configurazione" }, "learnMoreAboutEmergencyAccess": { - "message": "Learn more about emergency access" + "message": "Ulteriori informazioni sull'accesso d'emergenza" }, "learnMoreAboutMatchDetection": { - "message": "Learn more about match detection" + "message": "Maggiori informazioni sulla rilevazione di corrispondenza" }, "learnMoreAboutMasterPasswordReprompt": { - "message": "Learn more about master password re-prompt" + "message": "Informazioni sulla richiesta aggiuntiva di inserimento della password principale" }, "learnMoreAboutSearchingYourVault": { - "message": "Learn more about searching your vault" + "message": "Informazioni sulla ricerca nella cassaforte" }, "learnMoreAboutYourAccountFingerprintPhrase": { - "message": "Learn about your account fingerprint phrase" + "message": "Impara la frase di autenticazione del tuo account" }, "impactOfRotatingYourEncryptionKey": { - "message": "Impact of rotating your encryption key" + "message": "Impatto della modifica periodica della chiave crittografica" }, "learnMoreAboutEncryptionAlgorithms": { - "message": "Learn more about encryption algorithms" + "message": "Informazioni sugli algoritmi di crittografia" }, "learnMoreAboutKDFIterations": { - "message": "Learn more about KDF iterations" + "message": "Informazioni sulle iterazioni KDF" }, "learnMoreAboutLocalization": { - "message": "Learn more about localization" + "message": "Informazioni sulla localizzazione" }, "learnMoreAboutWebsiteIcons": { - "message": "Learn more about using website icons" + "message": "Ulteriori informazioni sull'utilizzo delle favicon dei siti Web" }, "learnMoreAboutUserAccess": { - "message": "Learn more about user access" + "message": "Informazioni sull'accesso utente" }, "learnMoreAboutMemberRoles": { - "message": "Learn more about member roles and permissions" + "message": "Ulteriori informazioni sui ruoli e i permessi dei membri" }, "whatIsACvvNumber": { - "message": "What is a CVV number?" + "message": "Cos'è un numero CVV?" }, "learnMoreAboutApi": { - "message": "Learn more about Bitwarden's API" + "message": "Info sulle API di Bitwarden" + }, + "fileSend": { + "message": "File Send" }, "fileSends": { "message": "Send File" }, + "textSend": { + "message": "Testo Send" + }, "textSends": { "message": "Send Testo" }, @@ -9641,28 +9946,37 @@ "message": "GB di spazio aggiuntivo" }, "sshKeyAlgorithm": { - "message": "Key algorithm" + "message": "Algoritmo chiave" + }, + "sshPrivateKey": { + "message": "Chiave privata" + }, + "sshPublicKey": { + "message": "Chiave pubblica" + }, + "sshFingerprint": { + "message": "Impronta digitale" }, "sshKeyFingerprint": { - "message": "Fingerprint" + "message": "Impronta" }, "sshKeyPrivateKey": { - "message": "Private key" + "message": "Chiave privata" }, "sshKeyPublicKey": { - "message": "Public key" + "message": "Chiave pubblica" }, "sshKeyAlgorithmED25519": { "message": "ED25519" }, "sshKeyAlgorithmRSA2048": { - "message": "RSA 2048-Bit" + "message": "RSA a 2048 bit" }, "sshKeyAlgorithmRSA3072": { - "message": "RSA 3072-Bit" + "message": "RSA a 3072 bit" }, "sshKeyAlgorithmRSA4096": { - "message": "RSA 4096-Bit" + "message": "RSA a 4096 bit" }, "premiumAccounts": { "message": "6 account premium" @@ -9707,7 +10021,7 @@ "message": "Attuale" }, "secretsManagerSubscriptionInfo": { - "message": "Your Secrets Manager subscription will upgrade based on the plan selected" + "message": "Il tuo abbonamento Secrets Manager sarà aggiornato in base al piano selezionato" }, "bitwardenPasswordManager": { "message": "Bitwarden Password Manager" @@ -9723,19 +10037,19 @@ "description": "The text, 'API', is an acronym and should not be translated." }, "showCharacterCount": { - "message": "Show character count" + "message": "Mostra conteggio caratteri" }, "hideCharacterCount": { - "message": "Hide character count" + "message": "Nascondi conteggio caratteri" }, "editAccess": { "message": "Modifica accesso" }, "textHelpText": { - "message": "Use text fields for data like security questions" + "message": "Usa campi di testo per dati come domande di sicurezza" }, "hiddenHelpText": { - "message": "Use hidden fields for sensitive data like a password" + "message": "Usa campi nascosti per dati sensibili come le password" }, "checkBoxHelpText": { "message": "Usa le caselle di controllo se vuoi riempire automaticamente una casella di controllo, come \"Rimani connesso\"" @@ -9744,10 +10058,10 @@ "message": "Usa un campo collegato quando si verificano problemi di riempimento automatico per un sito web specifico." }, "linkedLabelHelpText": { - "message": "Enter the the field's html id, name, aria-label, or placeholder." + "message": "Inserisci l'ID HTML, il nome, l'aria-label o il segnaposto del campo." }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "Includi caratteri maiuscoli", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -9755,7 +10069,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "Includi caratteri minuscoli", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -9763,7 +10077,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "Includi cifre", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -9771,40 +10085,36 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "Includi caratteri speciali", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { - "message": "Add attachment" + "message": "Aggiungi allegato" }, "maxFileSizeSansPunctuation": { - "message": "Maximum file size is 500 MB" + "message": "La dimensione massima del file è 500 MB" }, "permanentlyDeleteAttachmentConfirmation": { - "message": "Are you sure you want to permanently delete this attachment?" + "message": "Vuoi davvero eliminare definitivamente questo allegato?" }, "manageSubscriptionFromThe": { - "message": "Manage subscription from the", + "message": "Gestisci l'abbonamento dal", "description": "This represents the beginning of a sentence. The full sentence will be 'Manage subscription from the Provider Portal', but 'Provider Portal' will be a link and thus cannot be included in the translation file." }, "toHostBitwardenOnYourOwnServer": { - "message": "To host Bitwarden on your own server, you will need to upload your license file. To support Free Families plans and advanced billing capabilities for your self-hosted organization, you will need to set up automatic sync in your self-hosted organization." + "message": "Per ospitare Bitwarden sul tuo server, dovrai caricare il tuo file di licenza. Per supportare i piani Famiglie Libere e le funzionalità avanzate di fatturazione per la tua organizzazione self-hosted, dovrai impostare la sincronizzazione automatica." }, "selfHostingTitleProper": { - "message": "Self-Hosting" + "message": "Self-hosting" }, "claim-domain-single-org-warning": { - "message": "Claiming a domain will turn on the single organization policy." + "message": "La rivendicazione di un dominio attiverà la politica di organizzazione unica." }, "single-org-revoked-user-warning": { - "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." + "message": "I membri non conformi saranno revocati. Gli amministratori possono ripristinare i membri una volta che lasciano tutte le altre organizzazioni." }, "deleteOrganizationUser": { - "message": "Delete $NAME$", + "message": "Elimina $NAME$", "placeholders": { "name": { "content": "$1", @@ -9814,7 +10124,7 @@ } }, "deleteOrganizationUserWarningDesc": { - "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "message": "Tutti gli elementi di proprietà di $NAME$ saranno eliminati. Gli elementi delle raccolte non subiranno modifiche.", "description": "Warning description for the delete organization user dialog", "placeholders": { "name": { @@ -9824,11 +10134,11 @@ } }, "deleteManyOrganizationUsersWarningDesc": { - "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "message": "Tutti gli elementi di proprietà dei seguenti membri saranno eliminati. Gli elementi delle raccolte non subiranno modifiche.", "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { - "message": "Deleted $NAME$", + "message": "$NAME$ eliminato", "placeholders": { "name": { "content": "$1", @@ -9837,10 +10147,10 @@ } }, "organizationUserDeletedDesc": { - "message": "The user was removed from the organization and all associated user data has been deleted." + "message": "L'utente è stato rimosso dall'organizzazione e tutti i dati utente associati sono stati eliminati." }, "deletedUserId": { - "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "message": "Utente $ID$ eliminato da un proprietario o da un amministratore", "placeholders": { "id": { "content": "$1", @@ -9849,7 +10159,7 @@ } }, "userLeftOrganization": { - "message": "User $ID$ left organization", + "message": "L'utente $ID$ ha lasciato l'organizzazione", "placeholders": { "id": { "content": "$1", @@ -9858,7 +10168,7 @@ } }, "suspendedOrganizationTitle": { - "message": "The $ORGANIZATION$ is suspended", + "message": "L'organizzazione $ORGANIZATION$ è sospesa", "placeholders": { "organization": { "content": "$1", @@ -9867,52 +10177,61 @@ } }, "suspendedUserOrgMessage": { - "message": "Contact your organization owner for assistance." + "message": "Contatta il proprietario dell'organizzazione per ricevere assistenza." }, "suspendedOwnerOrgMessage": { - "message": "To regain access to your organization, add a payment method." + "message": "Per recuperare l'accesso alla tua organizzazione, aggiungi un metodo di pagamento." }, "deleteMembers": { - "message": "Delete members" + "message": "Elimina membri" }, "noSelectedMembersApplicable": { - "message": "This action is not applicable to any of the selected members." + "message": "Questa azione non è applicabile a nessuno dei membri selezionati." }, "deletedSuccessfully": { - "message": "Deleted successfully" + "message": "Eliminazione completata" }, "freeFamiliesSponsorship": { - "message": "Remove Free Bitwarden Families sponsorship" + "message": "Rimuovi la sponsorizzazione famiglie gratuita" }, "freeFamiliesSponsorshipPolicyDesc": { - "message": "Do not allow members to redeem a Families plan through this organization." + "message": "Non consentire ai membri di riscattare un piano Famiglia attraverso questa organizzazione." }, "verifyBankAccountWithStatementDescriptorWarning": { - "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + "message": "Il pagamento con un conto bancario è disponibile solo per i clienti negli Stati Uniti. Ti sarà richiesto di verificare il tuo conto corrente. Effettueremo un micro-deposito entro i prossimi 1-2 giorni lavorativi. Inserisci il codice descrittore di questo deposito (si trova sull'estratto conto) nella pagina di fatturazione dell'organizzazione per verificare il conto bancario. La mancata verifica del conto bancario comporterà una perdita di pagamento e la sospensione dell'abbonamento." }, "verifyBankAccountWithStatementDescriptorInstructions": { - "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + "message": "Abbiamo effettuato un micro-deposito sul tuo conto bancario (potrebbe richiedere 1-2 giorni lavorativi). Inserisci il codice a sei cifre che inizia con 'SM' trovato nella descrizione del movimento. La mancata verifica del conto bancario comporterà una perdita di pagamento e la sospensione dell'abbonamento." }, "descriptorCode": { - "message": "Descriptor code" + "message": "Codice descrittore" + }, + "cannotRemoveViewOnlyCollections": { + "message": "Non puoi rimuovere raccolte con i soli permessi di visualizzazione: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } }, "importantNotice": { - "message": "Important notice" + "message": "Avviso importante" }, "setupTwoStepLogin": { - "message": "Set up two-step login" + "message": "Imposta l'accesso in due passaggi" }, "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + "message": "Bitwarden invierà un codice all'email del tuo account per verificare gli accessi da nuovi dispositivi." }, "newDeviceVerificationNoticeContentPage2": { - "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + "message": "Puoi impostare l'accesso in due passaggi per proteggere il tuo account, oppure scegliere una email alla quale puoi accedere." }, "remindMeLater": { - "message": "Remind me later" + "message": "Ricordamelo più tardi" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", + "message": "Confermi di poter accedere all'email $EMAIL$?", "placeholders": { "email": { "content": "$1", @@ -9921,40 +10240,49 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "No" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" + "message": "Sì, ho accesso all'email" }, "turnOnTwoStepLogin": { - "message": "Turn on two-step login" + "message": "Attiva l'accesso in due passaggi" }, "changeAcctEmail": { - "message": "Change account email" + "message": "Cambia l'email dell'account" }, "removeMembers": { - "message": "Remove members" + "message": "Rimuovi membri" + }, + "devices": { + "message": "Dispositivi" + }, + "deviceListDescription": { + "message": "Il tuo account è stato connesso a ciascuno dei dispositivi qui sotto. Se non riconosci un dispositivo, rimuovilo ora." + }, + "deviceListDescriptionTemp": { + "message": "Il tuo account è stato connesso a ciascuno dei dispositivi qui sotto." }, "claimedDomains": { - "message": "Claimed domains" + "message": "Domini verificati" }, "claimDomain": { - "message": "Claim domain" + "message": "Verifica dominio" }, "reclaimDomain": { - "message": "Reclaim domain" + "message": "Rivendica dominio" }, "claimDomainNameInputHint": { - "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + "message": "Esempio: ilmiodominio.com. I sotto-domini richiedono voci separate." }, "automaticClaimedDomains": { - "message": "Automatic Claimed Domains" + "message": "Domini verificati automatici" }, "automaticDomainClaimProcess": { - "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + "message": "Bitwarden tenterà di verificare il dominio 3 volte durante le prossime 72 ore. Se il dominio non può essere acquisito, controlla il record DNS del tuo servizio di hosting e procedi manualmente. Il dominio sarà rimosso dall'organizzazione dopo 7 giorni se la procedura non andrà a buon fine." }, "domainNotClaimed": { - "message": "$DOMAIN$ not claimed. Check your DNS records.", + "message": "$DOMAIN$ non verificato. Controlla il record DNS.", "placeholders": { "DOMAIN": { "content": "$1", @@ -9963,19 +10291,19 @@ } }, "domainStatusClaimed": { - "message": "Claimed" + "message": "Verificato" }, "domainStatusUnderVerification": { - "message": "Under verification" + "message": "In attesa di verifica" }, "claimedDomainsDesc": { - "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + "message": "Richiedi un dominio per acquisire la proprietà di tutti gli account membri il cui indirizzo email corrisponde al dominio. I membri saranno in grado di saltare l'identificatore SSO durante l'accesso. Gli amministratori potranno anche eliminare gli account membri." }, "invalidDomainNameClaimMessage": { - "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + "message": "L'input non è in un formato valido. Formato: ilmiodominio.com. I sotto-domini richiedono voci separate." }, "domainClaimedEvent": { - "message": "$DOMAIN$ claimed", + "message": "$DOMAIN$ verificato", "placeholders": { "DOMAIN": { "content": "$1", @@ -9984,7 +10312,7 @@ } }, "domainNotClaimedEvent": { - "message": "$DOMAIN$ not claimed", + "message": "$DOMAIN$ non verificato", "placeholders": { "DOMAIN": { "content": "$1", @@ -9993,7 +10321,7 @@ } }, "updatedRevokeSponsorshipConfirmationForSentSponsorship": { - "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "message": "Se rimuovi $EMAIL$, la sponsorizzazione per questo piano Famiglia non potrà essere riscattata. Vuoi davvero procedere?", "placeholders": { "email": { "content": "$1", @@ -10002,7 +10330,7 @@ } }, "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { - "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "message": "Se rimuovi $EMAIL$, la sponsorizzazione per questo piano Famiglia finirà e sul metodo di pagamento impostato saranno addebitati 40 $ + imposte applicabile su $DATE$. Non sarai in grado di riscattare una nuova sponsorizzazione fino a $DATE$. Vuoi comunque procedere?", "placeholders": { "email": { "content": "$1", @@ -10015,13 +10343,75 @@ } }, "domainClaimed": { - "message": "Domain claimed" + "message": "Dominio verificato" }, "organizationNameMaxLength": { - "message": "Organization name cannot exceed 50 characters." + "message": "Il nome dell'organizzazione non può superare i 50 caratteri." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "La parola d'accesso inserita non è corretta." + }, + "importSshKey": { + "message": "Importa" + }, + "confirmSshKeyPassword": { + "message": "Conferma parola d'accesso" + }, + "enterSshKeyPasswordDesc": { + "message": "Inserisci la parola d'accesso per la chiave SSH." + }, + "enterSshKeyPassword": { + "message": "Inserisci parola d'accesso" + }, + "invalidSshKey": { + "message": "La chiave SSH non è valida" + }, + "sshKeyTypeUnsupported": { + "message": "Il tipo di chiave SSH non è supportato" + }, + "importSshKeyFromClipboard": { + "message": "Importa chiave dagli Appunti" + }, + "sshKeyImported": { + "message": "Chiave SSH importata correttamente" + }, + "copySSHPrivateKey": { + "message": "Copia chiave privata" + }, + "openingExtension": { + "message": "Apertura dell'estensione del browser Bitwarden" + }, + "somethingWentWrong": { + "message": "Si è verificato un problema..." + }, + "openingExtensionError": { + "message": "Non è stato possibile aprire l'estensione di Bitwarden. Riprova cliccando sul pulsante." + }, + "openExtension": { + "message": "Apri estensione" + }, + "doNotHaveExtension": { + "message": "Non hai l'estensione di Bitwarden installata nel tuo browser?" + }, + "installExtension": { + "message": "Installa estensione" + }, + "openedExtension": { + "message": "Estensione avviata" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "L'estensione di Bitwarden è installata e funzionante. Ora è possibile avere una panoramica delle password a rischio." + }, + "openExtensionManuallyPart1": { + "message": "Non è stato possibile aprire l'estensione di Bitwarden. Clicca sull'icona di Bitwarden", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "dalla barra degli strumenti.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Il tuo abbonamento sarà rinnovato a breve. Per assicurarti un servizio continuativo, contatta $RESELLER$ e conferma il tuo rinnovo prima del $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "La ricevuta per l'abbonamento è stata emessa il $ISSUED_DATE$. Per assicurarti un servizio continuativo, contatta $RESELLER$ e conferma il tuo rinnovo prima del $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "Non abbiamo ricevuto il pagamento per il tuo abbonamento. Per assicurarti un servizio continuativo, contatta $RESELLER$ e conferma il tuo rinnovo prima del $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Abbonamento organizzazione riavviato" + }, + "restartSubscription": { + "message": "Riavvia il tuo abbonamento" + }, + "suspendedManagedOrgMessage": { + "message": "Contatta $PROVIDER$ per assistenza.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Gli amministratori ora hanno la possibilità di eliminare gli account membri che appartengono a un dominio rivendicato." + }, + "deleteManagedUserWarningDesc": { + "message": "Questa azione eliminerà l'account membro includendo tutti gli elementi nella sua cassaforte. Questo sostituisce l'azione precedente Rimuovi." + }, + "deleteManagedUserWarning": { + "message": "Eliminare è una nuova azione!" + }, + "seatsRemaining": { + "message": "Hai $REMAINING$ posti di $TOTAL$ posti assegnati a quest'organizzazione. Contatta il provider per gestire l'abbonamento.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Organizzazione esistente" + }, + "selectOrganizationProviderPortal": { + "message": "Seleziona un'organizzazione da aggiungere al tuo portale fornitori." + }, + "noOrganizations": { + "message": "Non ci sono organizzazioni da elencare" + }, + "yourProviderSubscriptionCredit": { + "message": "Il tuo abbonamento fornitore riceverà un credito per qualsiasi tempo rimanente nell'abbonamento dell'organizzazione." + }, + "doYouWantToAddThisOrg": { + "message": "Vuoi aggiungere questa organizzazione a $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Aggiunta organizzazione esistente" + }, + "assignedExceedsAvailable": { + "message": "I posti assegnati superano i posti disponibili." + }, + "changeAtRiskPassword": { + "message": "Cambia parola d'accesso a rischio" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Rimuovi sblocco con PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Non consentire ai membri di sbloccare il proprio account con un PIN." + }, + "limitedEventLogs": { + "message": "I piani $PRODUCT_TYPE$ non hanno accesso ai registri degli eventi reali", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Ottieni pieno accesso ai registri degli eventi dell'organizzazione aggiornando a un piano Teams o Enterprise." + }, + "upgradeEventLogTitle": { + "message": "Aggiorna per i dati del registro eventi reali" + }, + "upgradeEventLogMessage": { + "message": "Questi eventi sono solo esempi e non riflettono eventi reali all'interno della tua organizzazione Bitwarden." + }, + "cannotCreateCollection": { + "message": "Le organizzazioni gratuite possono avere fino a 2 raccolte. Aggiorna ad un piano a pagamento per crearne di più." } } diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index 6b3b6f13b46..6860b30b360 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "きわめて重要なアプリ" }, + "noCriticalAppsAtRisk": { + "message": "危険にさらされた重要なアプリケーションはありません" + }, "accessIntelligence": { "message": "アクセス インテリジェンス" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "リスクがあるメンバー" }, + "atRiskMembersWithCount": { + "message": "危険な状態のメンバー ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "危険な状態のアプリ ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "これらのメンバーは、脆弱な、または流出したか再利用されたパスワードでアプリにログインしています。" + }, + "atRiskApplicationsDescription": { + "message": "これらのアプリで、脆弱な、または流出したか再利用されたパスワードを使用しています。" + }, + "atRiskMembersDescriptionWithApp": { + "message": "これらのメンバーは、脆弱な、または流出したか再利用されたパスワードで $APPNAME$ にログインしています。", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "合計メンバー数" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "合計アプリ数" }, + "unmarkAsCriticalApp": { + "message": "重要なアプリとしてのマークを解除" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "重要なアプリケーションのマークを解除しました" + }, "whatTypeOfItem": { "message": "このアイテムのタイプは何ですか?" }, @@ -159,6 +201,9 @@ "notes": { "message": "メモ" }, + "privateNote": { + "message": "非公開メモ" + }, "note": { "message": "メモ" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "ドラッグして並べ替え" }, + "dragToReorder": { + "message": "ドラッグして並べ替え" + }, "cfTypeText": { "message": "テキスト" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "フォルダーを編集" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "新しいフォルダー" + }, + "folderName": { + "message": "フォルダー名" + }, + "folderHintText": { + "message": "親フォルダーの名前の後に「/」を追加するとフォルダをネストします。例: ソーシャル/フォーラム" + }, + "deleteFolderPermanently": { + "message": "このフォルダーを完全に削除しますか?" + }, "baseDomain": { "message": "ベースドメイン", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "アイテム名" }, - "cannotRemoveViewOnlyCollections": { - "message": "表示のみの権限が与えられているコレクションを削除することはできません: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "例:", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "フィルター" }, - "moveSelectedToOrg": { - "message": "選択したものを組織に移動" - }, "deleteSelected": { "message": "選択したものを削除" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "選択したアイテムを $ORGNAME$ に移動しました", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "アイテムを $ORGNAME$ に移動しました", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "いいえ" }, + "location": { + "message": "場所" + }, "loginOrCreateNewAccount": { "message": "安全なデータ保管庫へアクセスするためにログインまたはアカウントを作成してください。" }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Bitwarden にログイン" }, + "enterTheCodeSentToYourEmail": { + "message": "メールアドレスに送信されたコードを入力してください" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "認証アプリに表示されているコードを入力してください" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "YubiKey を押して認証してください" + }, "authenticationTimeout": { "message": "認証のタイムアウト" }, "authenticationSessionTimedOut": { "message": "認証セッションの有効期限が切れました。ログイン操作を最初からやり直してください。" }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "本人確認" }, + "weDontRecognizeThisDevice": { + "message": "このデバイスは未確認です。本人確認のため、メールアドレスに送信されたコードを入力してください。" + }, + "continueLoggingIn": { + "message": "ログインを続ける" + }, + "whatIsADevice": { + "message": "デバイスとは何ですか?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "ログイン開始" }, + "logInRequestSent": { + "message": "リクエストが送信されました" + }, "submit": { "message": "送信" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "アカウントのメールアドレスを入力すると、パスワードのヒントが送信されます" }, - "passwordHint": { - "message": "パスワードのヒント" - }, - "enterEmailToGetHint": { - "message": "マスターパスワードのヒントを受信するアカウントのメールアドレスを入力してください。" - }, "getMasterPasswordHint": { "message": "マスターパスワードのヒントを取得する" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "デバイスに通知を送信しました。" }, + "notificationSentDevicePart1": { + "message": "デバイスまたは" + }, + "areYouTryingToAccessYourAccount": { + "message": "アカウントにアクセスしようとしていますか?" + }, + "accessAttemptBy": { + "message": "$EMAIL$ によるログインの試行", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "アクセスの確認" + }, + "denyAccess": { + "message": "アクセスを拒否" + }, + "notificationSentDeviceAnchor": { + "message": "ウェブアプリ" + }, + "notificationSentDevicePart2": { + "message": "上で、Bitwarden をロック解除してください。承認する前に、フィンガープリントフレーズが以下と一致していることを確認してください。" + }, + "notificationSentDeviceComplete": { + "message": "デバイス上で Bitwarden のロックを解除してください。フィンガープリントフレーズが下記のものと一致していることを確認してから承認してください。" + }, "aNotificationWasSentToYourDevice": { "message": "お使いのデバイスに通知が送信されました" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "アカウントがロック解除されていることと、フィンガープリントフレーズが他の端末と一致していることを確認してください" - }, "versionNumber": { "message": "バージョン $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "情報を保存する" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "このデバイスで30日間再表示しない" + }, "sendVerificationCodeEmailAgain": { "message": "確認コードをメールで再送" }, "useAnotherTwoStepMethod": { "message": "他の2段階認証方法を使用" }, + "selectAnotherMethod": { + "message": "別の方法を選択", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "リカバリーコードを使用する" + }, "insertYubiKey": { "message": "YubiKey を USB ポートに挿入し、ボタンをタッチしてください。" }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "2段階認証オプション" }, + "selectTwoStepLoginMethod": { + "message": "2段階認証の方法を選択" + }, "recoveryCodeDesc": { "message": "すべての2段階認証プロパイダにアクセスできなくなったときは、リカバリーコードを使用するとアカウントの2段階認証を無効化できます。" }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(FIDOから移行)" }, + "openInNewTab": { + "message": "新しいタブで開く" + }, "emailTitle": { "message": "メールアドレス" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "このアイテムを移動する組織を選択してください。組織に移動すると、アイテムの所有権はその組織に移ります。移動した後、あなたはこのアイテムの直接の所有者ではなくなります。" }, - "moveManyToOrgDesc": { - "message": "このアイテムを移動する組織を選択してください。組織に移動すると、アイテムの所有権がその組織に移行します。 このアイテムが移動された後、あなたはこのアイテムの直接の所有者にはなりません。" - }, "collectionsDesc": { "message": "このアイテムを共有するコレクションを編集します。共有したアイテムは、当該コレクションにアクセスできる組織ユーザーにのみ表示されます。" }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "$COUNT$ アイテムを選択しました。 $MOVEABLE_COUNT$ アイテムは組織に移動できます。 $NONMOVEABLE_COUNT$ アイテムはできません。", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "認証コード (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "あいまいな文字を避ける", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "パスワードの再生成" - }, "length": { "message": "長さ" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "ログインし直してください" }, + "currentSession": { + "message": "現在のセッション" + }, + "requestPending": { + "message": "保留中のリクエスト" + }, "logBackInOthersToo": { "message": "ログインし直してください。他のBitwardenのアプリを使用している場合、同様にログインし直してください。" }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "危険な操作" }, - "dangerZoneDesc": { - "message": "これらの操作はやり直せないため注意してください!" - }, - "dangerZoneDescSingular": { - "message": "この操作は元に戻せないため注意してください!" - }, "deauthorizeSessions": { "message": "セッションの承認を取り消す" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "次に進むと現在のセッションからログアウトし二段階認証を含め再度ログインが必要になります。他のデバイスでのセッションは1時間程度維持されます。" }, + "newDeviceLoginProtection": { + "message": "新しいデバイスからのログイン" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "全てのセッションを無効化" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "2段階認証を有効にすると Bitwarden アカウントから永久に閉め出されてしまうことがあります。リカバリーコードがあれば、通常の2段階認証プロバイダを使えなくなったとき (デバイスの紛失等) でもアカウントにアクセスできます。アカウントにアクセスできなくなっても Bitwarden はサポート出来ないため、リカバリーコードを書き出すか印刷し安全な場所で保管しておくことを推奨します。" }, + "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." + }, "viewRecoveryCode": { "message": "リカバリーコードを確認" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "管理" }, - "canManage": { - "message": "管理可能" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "アイテムを表示" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "アイテムを編集" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "無効化" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "アクセスを取り消す" }, + "revoke": { + "message": "取り消し" + }, "twoStepLoginProviderEnabled": { "message": "この二段階認証プロバイダは、あなたのアカウントで有効になっています。" }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "セキュリティキーの読み取り中に問題が発生しました。もう一度やり直して下さい。" }, - "twoFactorWebAuthnWarning": { - "message": "プラットフォームの制限により、WebAuthnはBitwardenの全てのアプリケーションで使用できるわけではありません。WebAuthnが使用できない場合に備えて、他の二段階認証プロバイダを有効化しておくことをおすすめします。サポートされているプラットフォーム:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "WebAuthn対応ブラウザ(FIDO U2F が有効な Chrome、Opera、Vivaldi、Firefox)を搭載したデスクトップ/ノートパソコンで、WebVaultとブラウザ拡張機能を使用します。" + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "二段階認証のリカバリーコード" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "このユーザーはアカウントを保護するため二段階認証を利用しています。" }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "ユーザー $ID$ にリンクされていない SSO", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "デバイス" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP アドレス" + }, + "confirmLogIn": { + "message": "ログインを確認" + }, + "denyLogIn": { + "message": "ログインを拒否" + }, + "thisRequestIsNoLongerValid": { + "message": "このリクエストは無効になりました。" + }, + "logInConfirmedForEmailOnDevice": { + "message": "$EMAIL$ に $DEVICE$ でのログインを承認しました", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "別のデバイスからのログイン試行を拒否しました。本当にあなたであった場合は、もう一度デバイスでログインしてみてください。" + }, + "loginRequestHasAlreadyExpired": { + "message": "ログインリクエストの有効期限が切れています。" + }, + "justNow": { + "message": "たった今" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "アカウント作成:" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "サポートされていないブラウザを使用しています。ウェブ保管庫が正しく動作しないかもしれません。" }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "無料体験はあと $COUNT$ 日で終了します。", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "もし通常の二段階認証の方法でアカウントにアクセスできなくなった場合、リカバリーコードにより全ての二段階認証プロバイダを無効化することができます。" }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "二段階認証ログインの回復" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "暗号化キーの更新を続行できません" }, + "editFieldLabel": { + "message": "$LABEL$ を編集", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "$LABEL$ の順序を変更します。矢印キーを押すとアイテムを上下に移動します。", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "暗号化キーを更新する際、フォルダーを復号できませんでした。 アップデートを続行するには、フォルダーを削除する必要があります。続行しても保管庫のアイテムは削除されません。" }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "マスターパスワードの強度に最低要件を設定する。" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "2段階認証が必要です" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "パスワード生成の最低要件を設定する。" }, - "passwordGeneratorPolicyInEffect": { - "message": "組織の要件がパスワード生成の設定に影響しています。" - }, "masterPasswordPolicyInEffect": { "message": "組織が求めるマスターパスワードの要件は:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "組織の所有者および管理者は、このポリシーの執行から除外されます。" }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "ファイル" }, "sendTypeText": { "message": "テキスト" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "新しい Send を作成", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Send を削除", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "この Send を削除してもよろしいですか?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "この Send の種類は何ですか?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "削除日時" }, - "deletionDateDesc": { - "message": "Send は指定された日時に完全に削除されます。", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "最大アクセス数" }, - "maxAccessCountDesc": { - "message": "設定されている場合、最大アクセス数に達するとユーザーはこの Send にアクセスできなくなります。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "現在のアクセス数" - }, - "sendPasswordDesc": { - "message": "必要に応じて、ユーザーがこの Send にアクセスするためのパスワードを要求します。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "この Send に関するプライベートメモ", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "無効" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "パスワードを削除してもよろしいですか?" }, - "hideEmail": { - "message": "受信者に自分のメールアドレスを表示しない" - }, - "disableThisSend": { - "message": "誰もアクセスできないように、この Send を無効にします。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "すべての Send" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "保留中の削除" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "期限切れ" }, @@ -5161,13 +5441,6 @@ "message": "Send を作成または編集するとき、常にメンバーのメールアドレスを受信者と一緒に表示します。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendOptionsPolicyInEffect": { - "message": "現在、以下の組織ポリシーが適用されています。" - }, - "sendDisableHideEmailInEffect": { - "message": "Send を作成または編集する際に、ユーザーのメールアドレスを受信者に非表示にすることはできません。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "modifiedPolicyId": { "message": "変更されたポリシー $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "組織ユーザーの個人所有権を無効にする" }, - "textHiddenByDefault": { - "message": "Send へのアクセス時に既定でテキストを非表示にする", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "この Send を説明するわかりやすい名前", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "送信したいテキスト" - }, - "sendFileDesc": { - "message": "送信するファイル" - }, - "copySendLinkOnSave": { - "message": "Send の保存時にクリップボードへリンクをコピーします。" - }, - "sendLinkLabel": { - "message": "Send リンク", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "削除と有効期限の保存中にエラーが発生しました。" }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "二段階認証を確認するには、下のボタンをクリックしてください。" }, "webAuthnAuthenticate": { "message": "WebAuthn の認証" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn はこのブラウザではサポートされていません。" }, @@ -5655,6 +5916,20 @@ "error": { "message": "エラー" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "ユーザーを管理するには、アカウントのリカバリ管理権限を付与する必要があります。" }, @@ -6516,15 +6791,6 @@ "message": "ジェネレーター", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "何を生成しますか?" - }, - "passwordType": { - "message": "パスワードの種類" - }, - "regenerateUsername": { - "message": "ユーザー名を再生成" - }, "generateUsername": { "message": "ユーザー名を生成" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "ユーザー名の種類" - }, "plusAddressedEmail": { "message": "プラス付きのメールアドレス", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "ドメインに設定されたキャッチオール受信トレイを使用します。" }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "ランダム", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "$SERVICENAME$ マスク済みメールアカウント ID を取得できませんでした。", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "ホスト名", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API アクセストークン" - }, "deviceVerification": { "message": "デバイス認証" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "アカウントには DUO 二段階認証が必要です。" }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "以下の手順に従ってログインを完了してください。" + }, "launchDuo": { "message": "DUO を起動" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "ユーザー数" }, - "loggingInAs": { - "message": "ログイン中:" - }, - "notYou": { - "message": "あなたではないですか?" - }, "pickAnAvatarColor": { "message": "アバターの色を選択" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "コレクションなし" }, - "canView": { - "message": "閲覧可能" - }, - "canViewExceptPass": { - "message": "パスワード以外は閲覧可能" - }, - "canEdit": { - "message": "編集可能" - }, - "canEditExceptPass": { - "message": "パスワード以外は編集可能" - }, "noCollectionsAdded": { "message": "コレクションが追加されていません" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "管理者の承認を要求する" }, - "approveWithMasterPassword": { - "message": "マスターパスワードで承認する" - }, "trustedDeviceEncryption": { "message": "信頼できるデバイスでの暗号化" }, "trustedDevices": { "message": "信頼できるデバイス" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "認証されると、メンバーはデバイスに保存されたキーを使って保管庫のデータを復号します。", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "単一組織", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "single organization", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": "ポリシー", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "policy,", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "SSO 必須", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO required", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "ポリシーと", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "policy, and", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "アカウント回復の管理", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "account recovery administration", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "自動登録のポリシーがオンになります。", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "組織の権限が更新され、マスターパスワードの設定が必要になりました。", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "リクエストを承認" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "デバイスリクエストはありません" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "要求を管理者に送信しました。" }, - "youWillBeNotifiedOnceApproved": { - "message": "承認されると通知されます。 " - }, "troubleLoggingIn": { "message": "ログインできない場合" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "コレクションの削除を所有者と管理者のみに制限" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "所有者と管理者はすべてのコレクションとアイテムを管理できます" }, @@ -8494,9 +8778,6 @@ "message": "自己ホスト型サーバー URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "エイリアスドメイン" - }, "alreadyHaveAccount": { "message": "既にアカウントをお持ちですか?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "このコレクションを管理する権限がありません。" }, - "grantAddAccessCollectionWarningTitle": { - "message": "「管理可能」権限がありません" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "コレクションの削除を含む完全なコレクション管理を許可するには「管理可能」権限を許可してください。" + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "グループまたはメンバーにこのコレクションへのアクセスを許可します。" @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "月/メンバーあたり" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "シート" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Bitwarden API の詳細" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "ファイル Send" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "テキスト Send" }, @@ -9641,16 +9946,25 @@ "message": "GB の追加ストレージ" }, "sshKeyAlgorithm": { - "message": "Key algorithm" + "message": "鍵アルゴリズム" + }, + "sshPrivateKey": { + "message": "秘密鍵" + }, + "sshPublicKey": { + "message": "公開鍵" + }, + "sshFingerprint": { + "message": "フィンガープリント" }, "sshKeyFingerprint": { - "message": "Fingerprint" + "message": "フィンガープリント" }, "sshKeyPrivateKey": { - "message": "Private key" + "message": "秘密鍵" }, "sshKeyPublicKey": { - "message": "Public key" + "message": "公開鍵" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -9732,7 +10046,7 @@ "message": "編集権限" }, "textHelpText": { - "message": "Use text fields for data like security questions" + "message": "セキュリティに関する質問などのデータにはテキストフィールドを使用します" }, "hiddenHelpText": { "message": "Use hidden fields for sensitive data like a password" @@ -9774,10 +10088,6 @@ "message": "特殊記号を含める", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "添付ファイルを追加" }, @@ -9795,7 +10105,7 @@ "message": "To host Bitwarden on your own server, you will need to upload your license file. To support Free Families plans and advanced billing capabilities for your self-hosted organization, you will need to set up automatic sync in your self-hosted organization." }, "selfHostingTitleProper": { - "message": "Self-Hosting" + "message": "セルフホスティング" }, "claim-domain-single-org-warning": { "message": "Claiming a domain will turn on the single organization policy." @@ -9804,7 +10114,7 @@ "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." }, "deleteOrganizationUser": { - "message": "Delete $NAME$", + "message": "$NAME$ を削除", "placeholders": { "name": { "content": "$1", @@ -9828,7 +10138,7 @@ "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { - "message": "Deleted $NAME$", + "message": "$NAME$ を削除しました", "placeholders": { "name": { "content": "$1", @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "表示のみの権限が与えられているコレクションを削除することはできません: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "メンバーを削除" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "入力されたパスワードが間違っています。" + }, + "importSshKey": { + "message": "インポート" + }, + "confirmSshKeyPassword": { + "message": "パスワードを確認" + }, + "enterSshKeyPasswordDesc": { + "message": "SSH キーのパスワードを入力します。" + }, + "enterSshKeyPassword": { + "message": "パスワードを入力" + }, + "invalidSshKey": { + "message": "SSH キーが無効です" + }, + "sshKeyTypeUnsupported": { + "message": "サポートされていない種類の SSH キーです" + }, + "importSshKeyFromClipboard": { + "message": "クリップボードからキーをインポート" + }, + "sshKeyImported": { + "message": "SSH キーのインポートに成功しました" + }, + "copySSHPrivateKey": { + "message": "秘密鍵をコピー" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "危険なパスワードの変更" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "PINによるロック解除を削除" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "PINによるアカウントのロック解除をメンバーに許可しません。" + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index cc4dc222103..f5f4bfb3ae1 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Critical applications" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "Რა სახის საგანია ეს?" }, @@ -159,6 +201,9 @@ "notes": { "message": "ჩანაწერები" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Note" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "გადაადგილე დასახარისხებლად" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "ტექსტი" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "საქაღალდის ჩასწორება" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "დომენის ბაზა", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "მაგ.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "ფილტრი" }, - "moveSelectedToOrg": { - "message": "გადაყვანა შერჩეულის ორგანიზაციაში" - }, "deleteSelected": { "message": "წაშლა შერჩეულის" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "მონიშნული საგანები გადაყვანილია $ORGNAME$-ში", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "არა" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "სისტემაში შედით ან შექმენით ახალი ანგარიში თქვენს დაცულ საცავთან საწვდომად." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "ავტორიზაცია დაწყებულია" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "გადაცემა" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "პაროლის მინიშნება" - }, - "enterEmailToGetHint": { - "message": "შეიყვანეთ თქვენი ანგარიშის ელ-ფოსტა რომ მიიღოთ თქვენი მთავარი პაროლის მინიშნება." - }, "getMasterPasswordHint": { "message": "მიიღეთ მთავარი პაროლის მინიშნება" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "შეტყობინება გამოიგზავნა თქვენი ტელეფონის მისამართით." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "ვერსია $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "დამიმახსოვრე" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "გამოგზავნა ერთჯერადი კოდის ელ-ფოსტაზე განმეორებით" }, "useAnotherTwoStepMethod": { "message": "გამოყენება სხვა ორსაფეხურიანი შესვლის მეთოდით" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "შეარჭეთ თქვენი YubiKey თქვენს კომპიუტერის USB პორტში, შემდგომ დაადეთ მის ღილაკს." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "ორსაფეხურიანი ავტორიზაციის პარამეტრები" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "დაკარგეთ წვდომა ყველა შენს ორსაფეხურიან პროვაიდერებთან? გამოიყენეთ თქვენი აღდგენის კოდი რომ გათიშოთ ყველა ორსაფეხურიანი ავტორიზაციის პროვაიდერები შენი ანგარიშიდან." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(გადმომიგრირდა FIDO-დან)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "ელ-ფოსტა" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." }, - "moveManyToOrgDesc": { - "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved." - }, "collectionsDesc": { "message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "თქვენ მონიშნეთ $COUNT$ საგანი(ები). $MOVEABLE_COUNT$ საგან(ები)-ს გადატანა შესაძლებელია ორგანიზაციაში, $NONMOVEABLE_COUNT$ ვერა", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "ერთჯერადი კოდი (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Regenerate password" - }, "length": { "message": "Length" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Danger zone" }, - "dangerZoneDesc": { - "message": "Careful, these actions are not reversible!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Deauthorize sessions" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Proceeding will also log you out of your current session, requiring you to log back in. You will also be prompted for two-step login again, if set up. Active sessions on other devices may continue to remain active for up to one hour." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "All sessions deauthorized" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Setting up two-step login can permanently lock you out of your Bitwarden account. A recovery code allows you to access your account in the event that you can no longer use your normal two-step login provider (example: you lose your device). Bitwarden support will not be able to assist you if you lose access to your account. We recommend you write down or print the recovery code and keep it in a safe place." }, + "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." + }, "viewRecoveryCode": { "message": "View recovery code" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Manage" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Turn off" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Revoke access" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "This two-step login provider is active on your account." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "There was a problem reading the security key. Try again." }, - "twoFactorWebAuthnWarning": { - "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used. Supported platforms:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Web vault and browser extensions on a desktop/laptop with a WebAuthn supported browser (Chrome, Opera, Vivaldi, or Firefox with FIDO U2F turned on)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Your Bitwarden two-step login recovery code" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "If you cannot access your account through your normal two-step login methods, you can use your two-step login recovery code to turn off all two-step providers on your account." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Recover account two-step login" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Set requirements for master password strength." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Require two-step login" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Set requirements for password generator." }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." - }, "masterPasswordPolicyInEffect": { "message": "One or more organization policies require your master password to meet the following requirements:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Organization owners and admins are exempt from this policy's enforcement." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "File" }, "sendTypeText": { "message": "Text" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Delete Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Are you sure you want to delete this Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "What type of Send is this?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maximum access count" }, - "maxAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Disabled" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Are you sure you want to remove the password?" }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, - "disableThisSend": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "All Sends" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Pending deletion" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Expired" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "The following organization policies are currently in effect:" - }, - "sendDisableHideEmailInEffect": { - "message": "Users are not allowed 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." - }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Remove individual ownership for organization users" }, - "textHiddenByDefault": { - "message": "When accessing the Send, hide the text by default", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to Send." - }, - "sendFileDesc": { - "message": "The file you want to Send." - }, - "copySendLinkOnSave": { - "message": "Copy the link to share this Send to my clipboard upon save." - }, - "sendLinkLabel": { - "message": "Send link", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "To verify your 2FA please click the button below." }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn is not supported in this browser." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, - "regenerateUsername": { - "message": "Regenerate username" - }, "generateUsername": { "message": "Generate username" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Username type" - }, "plusAddressedEmail": { "message": "Plus addressed email", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Hostname", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API access token" - }, "deviceVerification": { "message": "Device verification" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Number of users" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "pickAnAvatarColor": { "message": "Pick an avatar color" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "No collection" }, - "canView": { - "message": "Can view" - }, - "canViewExceptPass": { - "message": "Can view, except passwords" - }, - "canEdit": { - "message": "Can edit" - }, - "canEditExceptPass": { - "message": "Can edit, except passwords" - }, "noCollectionsAdded": { "message": "No collections added" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domain" - }, "alreadyHaveAccount": { "message": "Already have an account?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index 36ab0050700..f334131b69b 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Critical applications" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "What type of item is this?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Notes" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Note" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Drag to sort" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Text" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Edit folder" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "Base domain", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "ex.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filter" }, - "moveSelectedToOrg": { - "message": "Move selected to organization" - }, "deleteSelected": { "message": "Delete selected" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "Log in or create a new account to access your secure vault." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Submit" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Lost access to all of your two-step login providers? Use your recovery code to turn off all two-step login providers from your account." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Migrated from FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "Email" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." }, - "moveManyToOrgDesc": { - "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved." - }, "collectionsDesc": { "message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Verification code (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Regenerate password" - }, "length": { "message": "Length" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Danger zone" }, - "dangerZoneDesc": { - "message": "Careful, these actions are not reversible!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Deauthorize sessions" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Proceeding will also log you out of your current session, requiring you to log back in. You will also be prompted for two-step login again, if set up. Active sessions on other devices may continue to remain active for up to one hour." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "All sessions deauthorized" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Setting up two-step login can permanently lock you out of your Bitwarden account. A recovery code allows you to access your account in the event that you can no longer use your normal two-step login provider (example: you lose your device). Bitwarden support will not be able to assist you if you lose access to your account. We recommend you write down or print the recovery code and keep it in a safe place." }, + "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." + }, "viewRecoveryCode": { "message": "View recovery code" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Manage" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Turn off" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Revoke access" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "This two-step login provider is active on your account." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "There was a problem reading the security key. Try again." }, - "twoFactorWebAuthnWarning": { - "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used. Supported platforms:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Web vault and browser extensions on a desktop/laptop with a WebAuthn supported browser (Chrome, Opera, Vivaldi, or Firefox with FIDO U2F turned on)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Your Bitwarden two-step login recovery code" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "If you cannot access your account through your normal two-step login methods, you can use your two-step login recovery code to turn off all two-step providers on your account." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Recover account two-step login" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Set requirements for master password strength." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Require two-step login" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Set requirements for password generator." }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." - }, "masterPasswordPolicyInEffect": { "message": "One or more organization policies require your master password to meet the following requirements:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Organization owners and admins are exempt from this policy's enforcement." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "File" }, "sendTypeText": { "message": "Text" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Delete Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Are you sure you want to delete this Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "What type of Send is this?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maximum access count" }, - "maxAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Disabled" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Are you sure you want to remove the password?" }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, - "disableThisSend": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "All Sends" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Pending deletion" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Expired" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "The following organization policies are currently in effect:" - }, - "sendDisableHideEmailInEffect": { - "message": "Users are not allowed 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." - }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Remove individual ownership for organization users" }, - "textHiddenByDefault": { - "message": "When accessing the Send, hide the text by default", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to Send." - }, - "sendFileDesc": { - "message": "The file you want to Send." - }, - "copySendLinkOnSave": { - "message": "Copy the link to share this Send to my clipboard upon save." - }, - "sendLinkLabel": { - "message": "Send link", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "To verify your 2FA please click the button below." }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn is not supported in this browser." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, - "regenerateUsername": { - "message": "Regenerate username" - }, "generateUsername": { "message": "Generate username" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Username type" - }, "plusAddressedEmail": { "message": "Plus addressed email", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Hostname", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API access token" - }, "deviceVerification": { "message": "Device verification" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Number of users" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "pickAnAvatarColor": { "message": "Pick an avatar color" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "No collection" }, - "canView": { - "message": "Can view" - }, - "canViewExceptPass": { - "message": "Can view, except passwords" - }, - "canEdit": { - "message": "Can edit" - }, - "canEditExceptPass": { - "message": "Can edit, except passwords" - }, "noCollectionsAdded": { "message": "No collections added" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domain" - }, "alreadyHaveAccount": { "message": "Already have an account?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index e1fd4cc9ac9..dbf2cab3bb5 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Critical applications" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "ಇದು ಯಾವ ರೀತಿಯ ಐಟಂ?" }, @@ -159,6 +201,9 @@ "notes": { "message": "ಟಿಪ್ಪಣಿಗಳು" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Note" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "ವಿಂಗಡಿಸಲು ಎಳೆಯಿರಿ" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "ಪಠ್ಯ" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "ಫೋಲ್ಡರ್ ಸಂಪಾದಿಸಿ" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "ಮೂಲ ಡೊಮೇನ್", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "ಉದಾಹರಣೆ.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filter" }, - "moveSelectedToOrg": { - "message": "ಆಯ್ದ ಸಂಸ್ಥೆಗೆ ಸರಿಸಿ" - }, "deleteSelected": { "message": "ಆಯ್ಕೆಮಾಡಿದ ಅಳಿಸಿ" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "ಆಯ್ದ ವಸ್ತುಗಳನ್ನು $ORGNAME$ ಗೆ ಸರಿಸಲಾಗಿದೆ", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "ಇಲ್ಲ" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "ನಿಮ್ಮ ಸುರಕ್ಷಿತ ವಾಲ್ಟ್ ಅನ್ನು ಪ್ರವೇಶಿಸಲು ಲಾಗ್ ಇನ್ ಮಾಡಿ ಅಥವಾ ಹೊಸ ಖಾತೆಯನ್ನು ರಚಿಸಿ." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "ಒಪ್ಪಿಸು" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "ಪಾಸ್ವರ್ಡ್ ಸುಳಿವು" - }, - "enterEmailToGetHint": { - "message": "ವಿಸ್ತರಣೆಯನ್ನು ಪ್ರಾರಂಭಿಸಲು ಮೆನುವಿನಲ್ಲಿರುವ ಬಿಟ್‌ವಾರ್ಡೆನ್ ಐಕಾನ್ ಟ್ಯಾಪ್ ಮಾಡಿ." - }, "getMasterPasswordHint": { "message": "ಮಾಸ್ಟರ್ ಪಾಸ್ವರ್ಡ್ ಸುಳಿವನ್ನು ಪಡೆಯಿರಿ" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "ಆವೃತ್ತಿ $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "ನನ್ನನ್ನು ನೆನಪಿನಲ್ಲಿ ಇಡು" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "ಪರಿಶೀಲನೆ ಕೋಡ್ ಇಮೇಲ್ ಅನ್ನು ಮತ್ತೆ ಕಳುಹಿಸಿ" }, "useAnotherTwoStepMethod": { "message": "ಮತ್ತೊಂದು ಎರಡು-ಹಂತದ ಲಾಗಿನ್ ವಿಧಾನವನ್ನು ಬಳಸಿ" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "ನಿಮ್ಮ ಯುಬಿಕಿಯನ್ನು ನಿಮ್ಮ ಕಂಪ್ಯೂಟರ್‌ನ ಯುಎಸ್‌ಬಿ ಪೋರ್ಟ್ಗೆ ಸೇರಿಸಿ, ನಂತರ ಅದರ ಗುಂಡಿಯನ್ನು ಸ್ಪರ್ಶಿಸಿ." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "ಎರಡು ಹಂತದ ಲಾಗಿನ್ ಆಯ್ಕೆಗಳು" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "ನಿಮ್ಮ ಎಲ್ಲಾ ಎರಡು ಅಂಶ ಪೂರೈಕೆದಾರರಿಗೆ ಪ್ರವೇಶವನ್ನು ಕಳೆದುಕೊಂಡಿದ್ದೀರಾ? ನಿಮ್ಮ ಖಾತೆಯಿಂದ ಎಲ್ಲಾ ಎರಡು ಅಂಶ ಪೂರೈಕೆದಾರರನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲು ನಿಮ್ಮ ಮರುಪಡೆಯುವಿಕೆ ಕೋಡ್ ಬಳಸಿ." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(FIDO ನಿಂದ ವಲಸೆ ಬಂದಿದೆ)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "ಇಮೇಲ್" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "ಈ ಐಟಂ ಅನ್ನು ಸರಿಸಲು ನೀವು ಬಯಸುವ ಸಂಸ್ಥೆಯನ್ನು ಆರಿಸಿ. ಸಂಸ್ಥೆಗೆ ಹೋಗುವುದರಿಂದ ವಸ್ತುವಿನ ಮಾಲೀಕತ್ವವನ್ನು ಆ ಸಂಸ್ಥೆಗೆ ವರ್ಗಾಯಿಸುತ್ತದೆ. ಈ ಐಟಂ ಅನ್ನು ಸರಿಸಿದ ನಂತರ ನೀವು ಇನ್ನು ಮುಂದೆ ಅದರ ನೇರ ಮಾಲೀಕರಾಗಿರುವುದಿಲ್ಲ." }, - "moveManyToOrgDesc": { - "message": "ಈ ವಸ್ತುಗಳನ್ನು ಸರಿಸಲು ನೀವು ಬಯಸುವ ಸಂಸ್ಥೆಯನ್ನು ಆರಿಸಿ. ಸಂಸ್ಥೆಗೆ ಹೋಗುವುದರಿಂದ ವಸ್ತುಗಳ ಮಾಲೀಕತ್ವವನ್ನು ಆ ಸಂಸ್ಥೆಗೆ ವರ್ಗಾಯಿಸುತ್ತದೆ. ಈ ವಸ್ತುಗಳನ್ನು ಸರಿಸಿದ ನಂತರ ನೀವು ಇನ್ನು ಮುಂದೆ ಅವರ ನೇರ ಮಾಲೀಕರಾಗಿರುವುದಿಲ್ಲ." - }, "collectionsDesc": { "message": "ಈ ಐಟಂ ಹಂಚಿಕೊಳ್ಳುತ್ತಿರುವ ಸಂಗ್ರಹಗಳನ್ನು ಸಂಪಾದಿಸಿ. ಈ ಸಂಗ್ರಹಣೆಗಳಿಗೆ ಪ್ರವೇಶ ಹೊಂದಿರುವ ಸಂಸ್ಥೆಯ ಬಳಕೆದಾರರಿಗೆ ಮಾತ್ರ ಈ ಐಟಂ ಅನ್ನು ನೋಡಲು ಸಾಧ್ಯವಾಗುತ್ತದೆ." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "ನೀವು $COUNT$ ಐಟಂ (ಗಳನ್ನು) ಆಯ್ಕೆ ಮಾಡಿದ್ದೀರಿ. $MOVEABLE_COUNT$ ಐಟಂ (ಗಳನ್ನು) ಸಂಸ್ಥೆಗೆ ಸರಿಸಬಹುದು, $NONMOVEABLE_COUNT$ ಸಾಧ್ಯವಿಲ್ಲ.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "ಪರಿಶೀಲನಾ ಕೋಡ್‌ಗಳು (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "ಪಾಸ್ವರ್ಡ್ ಅನ್ನು ಪುನರುತ್ಪಾದಿಸಿ" - }, "length": { "message": "ಉದ್ದ" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "ದಯವಿಟ್ಟು ಮತ್ತೆ ಲಾಗ್ ಇನ್ ಮಾಡಿ." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "ದಯವಿಟ್ಟು ಮತ್ತೆ ಲಾಗ್ ಇನ್ ಮಾಡಿ. ನೀವು ಇತರ ಬಿಟ್‌ವಾರ್ಡೆನ್ ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಬಳಸುತ್ತಿದ್ದರೆ ಲಾಗ್ಔಟ್ ಮಾಡಿ ಮತ್ತು ಅವುಗಳಿಗೆ ಹಿಂತಿರುಗಿ." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "ಅಪಾಯ ವಲಯ" }, - "dangerZoneDesc": { - "message": "ಎಚ್ಚರಿಕೆಯಿಂದ, ಈ ಕ್ರಿಯೆಗಳು ಹಿಂತಿರುಗಿಸಲಾಗುವುದಿಲ್ಲ!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "ಸೆಷನ್‌ಗಳನ್ನು ಅನಧಿಕೃತಗೊಳಿಸಿ" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "ಮುಂದುವರಿಯುವುದರಿಂದ ನಿಮ್ಮ ಪ್ರಸ್ತುತ ಸೆಷನ್‌ನಿಂದ ನಿಮ್ಮನ್ನು ಲಾಗ್ಔಟ್ ಮಾಡುತ್ತದೆ, ನಿಮಗೆ ಮತ್ತೆ ಲಾಗ್ ಇನ್ ಆಗುವ ಅಗತ್ಯವಿರುತ್ತದೆ. ಸಕ್ರಿಯಗೊಳಿಸಿದ್ದರೆ ಮತ್ತೆ ಎರಡು-ಹಂತದ ಲಾಗಿನ್‌ಗೆ ನಿಮ್ಮನ್ನು ಕೇಳಲಾಗುತ್ತದೆ. ಇತರ ಸಾಧನಗಳಲ್ಲಿ ಸಕ್ರಿಯ ಸೆಷನ್‌ಗಳು ಒಂದು ಗಂಟೆಯವರೆಗೆ ಸಕ್ರಿಯವಾಗಿ ಮುಂದುವರಿಯಬಹುದು." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "ಎಲ್ಲಾ ಸೆಷನ್‌ಗಳು ಅನಧಿಕೃತವಾಗಿವೆ" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "ಎರಡು-ಹಂತದ ಲಾಗಿನ್ ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸುವುದರಿಂದ ನಿಮ್ಮ ಬಿಟ್‌ವಾರ್ಡನ್ ಖಾತೆಯಿಂದ ನಿಮ್ಮನ್ನು ಶಾಶ್ವತವಾಗಿ ಲಾಕ್ ಮಾಡಬಹುದು. ನಿಮ್ಮ ಸಾಮಾನ್ಯ ಎರಡು-ಹಂತದ ಲಾಗಿನ್ ಪೂರೈಕೆದಾರರನ್ನು ನೀವು ಇನ್ನು ಮುಂದೆ ಬಳಸಲಾಗದಿದ್ದಲ್ಲಿ ನಿಮ್ಮ ಖಾತೆಯನ್ನು ಪ್ರವೇಶಿಸಲು ಮರುಪಡೆಯುವಿಕೆ ಕೋಡ್ ನಿಮಗೆ ಅನುಮತಿಸುತ್ತದೆ (ಉದಾ. ನಿಮ್ಮ ಸಾಧನವನ್ನು ನೀವು ಕಳೆದುಕೊಳ್ಳುತ್ತೀರಿ). ನಿಮ್ಮ ಖಾತೆಗೆ ನೀವು ಪ್ರವೇಶವನ್ನು ಕಳೆದುಕೊಂಡರೆ ಬಿಟ್‌ವಾರ್ಡನ್ ಬೆಂಬಲವು ನಿಮಗೆ ಸಹಾಯ ಮಾಡಲು ಸಾಧ್ಯವಾಗುವುದಿಲ್ಲ. ಮರುಪಡೆಯುವಿಕೆ ಕೋಡ್ ಅನ್ನು ಬರೆಯಲು ಅಥವಾ ಮುದ್ರಿಸಲು ಮತ್ತು ಅದನ್ನು ಸುರಕ್ಷಿತ ಸ್ಥಳದಲ್ಲಿ ಇರಿಸಲು ನಾವು ಶಿಫಾರಸು ಮಾಡುತ್ತೇವೆ." }, + "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." + }, "viewRecoveryCode": { "message": "ಮರುಪಡೆಯುವಿಕೆ ಕೋಡ್ ವೀಕ್ಷಿಸಿ" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "ವ್ಯವಸ್ಥಾಪಕ" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "ನಿಷ್‌ಕ್ರಿಯೆಗೊಳಿಸಿ" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Revoke access" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "ಈ ಎರಡು ಹಂತದ ಲಾಗಿನ್ ಒದಗಿಸುವವರನ್ನು ನಿಮ್ಮ ಖಾತೆಯಲ್ಲಿ ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "ಭದ್ರತಾ ಕೀಲಿಯನ್ನು ಓದುವಲ್ಲಿ ಸಮಸ್ಯೆ ಇದೆ. ಮತ್ತೆ ಪ್ರಯತ್ನಿಸು." }, - "twoFactorWebAuthnWarning": { - "message": "ಪ್ಲಾಟ್‌ಫಾರ್ಮ್ ಮಿತಿಗಳ ಕಾರಣ, ಎಲ್ಲಾ ಬಿಟ್‌ವಾರ್ಡೆನ್ ಅಪ್ಲಿಕೇಶನ್‌ಗಳಲ್ಲಿ ವೆಬ್‌ಆಥ್ನ್ ಅನ್ನು ಬಳಸಲಾಗುವುದಿಲ್ಲ. ನೀವು ಇನ್ನೊಂದು ಎರಡು-ಹಂತದ ಲಾಗಿನ್ ಒದಗಿಸುವವರನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಬೇಕು ಇದರಿಂದ ವೆಬ್‌ಆಥ್ನ್ ಅನ್ನು ಬಳಸಲಾಗದಿದ್ದಾಗ ನಿಮ್ಮ ಖಾತೆಯನ್ನು ಪ್ರವೇಶಿಸಬಹುದು. ಬೆಂಬಲಿತ ವೇದಿಕೆಗಳು:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "ವೆಬ್‌ಆಥ್ನ್ ಶಕ್ತಗೊಂಡ ಬ್ರೌಸರ್‌ನೊಂದಿಗೆ ಡೆಸ್ಕ್‌ಟಾಪ್ / ಲ್ಯಾಪ್‌ಟಾಪ್‌ನಲ್ಲಿ ವೆಬ್ ವಾಲ್ಟ್ ಮತ್ತು ಬ್ರೌಸರ್ ವಿಸ್ತರಣೆಗಳು (ಕ್ರೋಮ್, ಒಪೇರಾ, ವಿವಾಲ್ಡಿ, ಅಥವಾ ಎಫ್‌ಐಡಿಒ ಯು 2 ಎಫ್ ಸಕ್ರಿಯಗೊಳಿಸಿದ ಫೈರ್‌ಫಾಕ್ಸ್)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "ನಿಮ್ಮ ಬಿಟ್‌ವಾರ್ಡೆನ್ ಎರಡು-ಹಂತದ ಲಾಗಿನ್ ಮರುಪಡೆಯುವಿಕೆ ಕೋಡ್" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "ಈ ಬಳಕೆದಾರರು ತಮ್ಮ ಖಾತೆಯನ್ನು ರಕ್ಷಿಸಲು ಎರಡು-ಹಂತದ ಲಾಗಿನ್ ಅನ್ನು ಬಳಸುತ್ತಿದ್ದಾರೆ." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "ಬಳಕೆದಾರ $ID$ ಗಾಗಿ ಲಿಂಕ್ ಮಾಡದ SSO.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "ಡಿವೈಸ್" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "ನೀವು ಬೆಂಬಲಿಸದ ವೆಬ್ ಬ್ರೌಸರ್ ಅನ್ನು ಬಳಸುತ್ತಿರುವಿರಿ. ವೆಬ್ ವಾಲ್ಟ್ ಸರಿಯಾಗಿ ಕಾರ್ಯನಿರ್ವಹಿಸದೆ ಇರಬಹುದು." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "ನಿಮ್ಮ ಸಾಮಾನ್ಯ ಎರಡು-ಹಂತದ ಲಾಗಿನ್ ವಿಧಾನಗಳ ಮೂಲಕ ನಿಮ್ಮ ಖಾತೆಯನ್ನು ಪ್ರವೇಶಿಸಲು ನಿಮಗೆ ಸಾಧ್ಯವಾಗದಿದ್ದರೆ, ನಿಮ್ಮ ಖಾತೆಯ ಎಲ್ಲಾ ಎರಡು-ಹಂತದ ಪೂರೈಕೆದಾರರನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲು ನಿಮ್ಮ ಎರಡು-ಹಂತದ ಲಾಗಿನ್ ಮರುಪಡೆಯುವಿಕೆ ಕೋಡ್ ಅನ್ನು ನೀವು ಬಳಸಬಹುದು." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "ಖಾತೆಯನ್ನು ಮರುಪಡೆಯಿರಿ ಎರಡು-ಹಂತದ ಲಾಗಿನ್" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "ಮಾಸ್ಟರ್ ಪಾಸ್‌ವರ್ಡ್ ಶಕ್ತಿಗಾಗಿ ಕನಿಷ್ಠ ಅವಶ್ಯಕತೆಗಳನ್ನು ಹೊಂದಿಸಿ." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Require two-step login" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "ಪಾಸ್ವರ್ಡ್ ಜನರೇಟರ್ ಕಾನ್ಫಿಗರೇಶನ್ಗಾಗಿ ಕನಿಷ್ಠ ಅವಶ್ಯಕತೆಗಳನ್ನು ಹೊಂದಿಸಿ." }, - "passwordGeneratorPolicyInEffect": { - "message": "ಒಂದು ಅಥವಾ ಹೆಚ್ಚಿನ ಸಂಸ್ಥೆ ನೀತಿಗಳು ನಿಮ್ಮ ಜನರೇಟರ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳ ಮೇಲೆ ಪರಿಣಾಮ ಬೀರುತ್ತವೆ." - }, "masterPasswordPolicyInEffect": { "message": "ಒಂದು ಅಥವಾ ಹೆಚ್ಚಿನ ಸಂಸ್ಥೆ ನೀತಿಗಳಿಗೆ ಈ ಕೆಳಗಿನ ಅವಶ್ಯಕತೆಗಳನ್ನು ಪೂರೈಸಲು ನಿಮ್ಮ ಮಾಸ್ಟರ್ ಪಾಸ್‌ವರ್ಡ್ ಅಗತ್ಯವಿದೆ:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "ಸಂಸ್ಥೆಯ ಮಾಲೀಕರು ಮತ್ತು ನಿರ್ವಾಹಕರು ಈ ನೀತಿಯ ಜಾರಿಯಿಂದ ವಿನಾಯಿತಿ ಪಡೆದಿದ್ದಾರೆ." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "ಫೈಲ್" }, "sendTypeText": { "message": "ಪಠ್ಯ" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "ಹೊಸ ಕಳುಹಿಸುವಿಕೆಯನ್ನು ರಚಿಸಿ", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "ಅಳಿಸಿ ಕಳುಹಿಸಿ", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "ಈ ಕಳುಹಿಸುವಿಕೆಯನ್ನು ಅಳಿಸಲು ನೀವು ಖಚಿತವಾಗಿ ಬಯಸುವಿರಾ?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "ಇದು ಯಾವ ರೀತಿಯ ಕಳುಹಿಸುತ್ತದೆ?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "ಅಳಿಸುವ ದಿನಾಂಕ" }, - "deletionDateDesc": { - "message": "ಕಳುಹಿಸಿದ ದಿನಾಂಕ ಮತ್ತು ಸಮಯದ ಮೇಲೆ ಕಳುಹಿಸುವಿಕೆಯನ್ನು ಶಾಶ್ವತವಾಗಿ ಅಳಿಸಲಾಗುತ್ತದೆ.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "ಗರಿಷ್ಠ ಪ್ರವೇಶ ಎಣಿಕೆ" }, - "maxAccessCountDesc": { - "message": "ಹೊಂದಿಸಿದ್ದರೆ, ಗರಿಷ್ಠ ಪ್ರವೇಶ ಎಣಿಕೆ ತಲುಪಿದ ನಂತರ ಬಳಕೆದಾರರಿಗೆ ಈ ಕಳುಹಿಸುವಿಕೆಯನ್ನು ಪ್ರವೇಶಿಸಲು ಸಾಧ್ಯವಾಗುವುದಿಲ್ಲ.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "ಪ್ರಸ್ತುತ ಪ್ರವೇಶ ಎಣಿಕೆ" - }, - "sendPasswordDesc": { - "message": "ಈ ಕಳುಹಿಸುವಿಕೆಯನ್ನು ಪ್ರವೇಶಿಸಲು ಬಳಕೆದಾರರಿಗೆ ಪಾಸ್‌ವರ್ಡ್ ಐಚ್ ಗತ್ಯವಿದೆ.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "ಈ ಕಳುಹಿಸುವ ಬಗ್ಗೆ ಖಾಸಗಿ ಟಿಪ್ಪಣಿಗಳು.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "ಪಾಸ್ವರ್ಡ್ ಅನ್ನು ತೆಗೆದುಹಾಕಲು ನೀವು ಖಚಿತವಾಗಿ ಬಯಸುವಿರಾ?" }, - "hideEmail": { - "message": "ಸ್ವೀಕರಿಸುವವರಿಂದ ನನ್ನ ಇಮೇಲ್ ವಿಳಾಸವನ್ನು ಮರೆಮಾಡಿ." - }, - "disableThisSend": { - "message": "ಇದನ್ನು ಕಳುಹಿಸುವುದನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ ಇದರಿಂದ ಯಾರೂ ಅದನ್ನು ಪ್ರವೇಶಿಸಲಾಗುವುದಿಲ್ಲ.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "ಎಲ್ಲಾ ಕಳುಹಿಸುತ್ತದೆ" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "ಅಳಿಸುವಿಕೆ ಬಾಕಿ ಉಳಿದಿದೆ" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "ಅವಧಿ ಮೀರಿದೆ" }, @@ -5161,13 +5441,6 @@ "message": "ಕಳುಹಿಸುವಿಕೆಯನ್ನು ರಚಿಸುವಾಗ ಅಥವಾ ಸಂಪಾದಿಸುವಾಗ ಬಳಕೆದಾರರು ತಮ್ಮ ಇಮೇಲ್ ವಿಳಾಸವನ್ನು ಸ್ವೀಕರಿಸುವವರಿಂದ ಮರೆಮಾಡಲು ಅನುಮತಿಸಬೇಡಿ.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendOptionsPolicyInEffect": { - "message": "ಕೆಳಗಿನ ಸಂಸ್ಥೆಯ ನೀತಿಗಳು ಪ್ರಸ್ತುತ ಜಾರಿಯಲ್ಲಿವೆ:" - }, - "sendDisableHideEmailInEffect": { - "message": "ಕಳುಹಿಸುವಿಕೆಯನ್ನು ರಚಿಸುವಾಗ ಅಥವಾ ಸಂಪಾದಿಸುವಾಗ ಬಳಕೆದಾರರು ತಮ್ಮ ಇಮೇಲ್ ವಿಳಾಸವನ್ನು ಸ್ವೀಕರಿಸುವವರಿಂದ ಮರೆಮಾಡಲು ಅನುಮತಿಸಲಾಗುವುದಿಲ್ಲ.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "modifiedPolicyId": { "message": "ಮಾರ್ಪಡಿಸಿದ ನೀತಿ $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "ಸಂಸ್ಥೆ ಬಳಕೆದಾರರಿಗಾಗಿ ವೈಯಕ್ತಿಕ ಮಾಲೀಕತ್ವವನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ" }, - "textHiddenByDefault": { - "message": "ಕಳುಹಿಸುವಿಕೆಯನ್ನು ಪ್ರವೇಶಿಸುವಾಗ, ಪಠ್ಯವನ್ನು ಪೂರ್ವನಿಯೋಜಿತವಾಗಿ ಮರೆಮಾಡಿ", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "ಇದನ್ನು ಕಳುಹಿಸಲು ವಿವರಿಸಲು ಸ್ನೇಹಪರ ಹೆಸರು.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "ನೀವು ಕಳುಹಿಸಲು ಬಯಸುವ ಪಠ್ಯ." - }, - "sendFileDesc": { - "message": "ನೀವು ಕಳುಹಿಸಲು ಬಯಸುವ ಫೈಲ್." - }, - "copySendLinkOnSave": { - "message": "ಇದನ್ನು ಹಂಚಿಕೊಳ್ಳಲು ಲಿಂಕ್ ಅನ್ನು ನಕಲಿಸಿ ಉಳಿಸಿದ ನಂತರ ನನ್ನ ಕ್ಲಿಪ್‌ಬೋರ್ಡ್‌ಗೆ ಕಳುಹಿಸಿ." - }, - "sendLinkLabel": { - "message": "ಲಿಂಕ್ ಕಳುಹಿಸಿ", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "ಕಳುಹಿಸಿ", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "ನಿಮ್ಮ ಅಳಿಸುವಿಕೆ ಮತ್ತು ಮುಕ್ತಾಯ ದಿನಾಂಕಗಳನ್ನು ಉಳಿಸುವಲ್ಲಿ ದೋಷ ಕಂಡುಬಂದಿದೆ." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "ನಿಮ್ಮ 2FA ಅನ್ನು ಪರಿಶೀಲಿಸಲು ದಯವಿಟ್ಟು ಕೆಳಗಿನ ಬಟನ್ ಕ್ಲಿಕ್ ಮಾಡಿ." }, "webAuthnAuthenticate": { "message": "WebAuthn ಅನ್ನು ಪ್ರಮಾಣಿಕರಿಸು" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "ಈ ಬ್ರೌಸರ್‌ನಲ್ಲಿ ವೆಬ್‌ಆಥ್ನ್ ಬೆಂಬಲಿಸುವುದಿಲ್ಲ." }, @@ -5655,6 +5916,20 @@ "error": { "message": "ದೋಷ" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, - "regenerateUsername": { - "message": "Regenerate username" - }, "generateUsername": { "message": "Generate username" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Username type" - }, "plusAddressedEmail": { "message": "Plus addressed email", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Hostname", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API access token" - }, "deviceVerification": { "message": "Device verification" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Number of users" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "pickAnAvatarColor": { "message": "Pick an avatar color" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "No collection" }, - "canView": { - "message": "Can view" - }, - "canViewExceptPass": { - "message": "Can view, except passwords" - }, - "canEdit": { - "message": "Can edit" - }, - "canEditExceptPass": { - "message": "Can edit, except passwords" - }, "noCollectionsAdded": { "message": "No collections added" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domain" - }, "alreadyHaveAccount": { "message": "Already have an account?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index 7966191f530..1f3bbda3736 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Critical applications" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "항목의 유형이 무엇입니까?" }, @@ -159,6 +201,9 @@ "notes": { "message": "메모" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Note" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "드래그하여 정렬" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "텍스트" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "폴더 편집" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "기본 도메인", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "예)", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "필터" }, - "moveSelectedToOrg": { - "message": "선택한 항목을 조직으로 이동함" - }, "deleteSelected": { "message": "선택 항목 삭제" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "선택한 항목이 $ORGNAME$(으)로 이동됨", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "아니오" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "안전 보관함에 접근하려면 로그인하거나 새 계정을 만드세요." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "보내기" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "비밀번호 힌트" - }, - "enterEmailToGetHint": { - "message": "마스터 비밀번호 힌트를 받으려면 계정의 이메일 주소를 입력하세요." - }, "getMasterPasswordHint": { "message": "마스터 비밀번호 힌트 얻기" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "기기에 알림이 전송되었습니다." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "버전 $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "기억하기" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "인증 코드 이메일 다시 보내기" }, "useAnotherTwoStepMethod": { "message": "다른 2단계 로그인 방법 사용" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "YubiKey를 컴퓨터의 USB 포트에 삽입하고 버튼을 누르세요." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "2단계 인증 옵션" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "모든 2단계 인증을 사용할 수 없는 상황인가요? 복구 코드를 사용하여 계정의 모든 2단계 인증을 비활성화할 수 있습니다." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(FIDO에서 이전됨)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "이메일" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "이 항목을 이동할 조직을 선택하십시오. 항목이 조직으로 이동되면 소유권이 조직으로 이전됩니다. 일단 이동되면, 더는 이동된 항목의 직접적인 소유자가 아니게 됩니다." }, - "moveManyToOrgDesc": { - "message": "이 항목을 이동할 조직을 선택하십시오. 항목이 조직으로 이동되면 소유권이 조직으로 이전됩니다. 일단 이동되면, 더는 이동된 항목의 직접적인 소유자가 아니게 됩니다." - }, "collectionsDesc": { "message": "이 항목이 공유될 콜렉션을 수정하십시오. 이 콜렉션에 접근할 수 있는 조직 사용자만 이 항목을 볼 수 있습니다." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "$COUNT$개의 항목을 선택하셨습니다. $MOVEABLE_COUNT$개의 항목은 조직으로 이동시킬 수 있지만 나머지 $NONMOVEABLE_COUNT$개의 항목은 이동시킬 수 없습니다.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "인증 코드 (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "비밀번호 재생성" - }, "length": { "message": "길이" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "다시 로그인해 주세요." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "다시 로그인해 주세요. 다른 Bitwarden 앱을 사용 중인 경우 해당 앱에서도 다시 로그인해야 합니다." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "위험 구역" }, - "dangerZoneDesc": { - "message": "주의, 이 행동들은 되돌릴 수 없음!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "세션 해제" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "계속 진행하면, 현재 세션 또한 로그아웃 되므로 다시 로그인하여야 합니다. 2단계 로그인이 활성화 된 경우 다시 요구하는 메세지가 표시됩니다. 다른 기기의 활성화 된 세션은 최대 1시간 동안 유지 될 수 있습니다." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "모든 세션 해제 됨" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "2단계 로그인을 활성화하면 Bitwarden 계정을 영원히 잠글 수 있습니다. 복구 코드를 사용하면 정상적인 2단계 로그인 제공자를 더 이상 사용할 수 없는 경우(예. 장치를 잃어버렸을 때) 계정에 액세스할 수 있습니다. 계정에 접근하지 못한다면 Bitwarden 지원팀은 어떤 도움도 줄 수 없습니다. 복구 코드를 기록하거나 출력하여 안전한 장소에 보관할 것을 권장합니다." }, + "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." + }, "viewRecoveryCode": { "message": "복구 코드" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "관리" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "비활성화" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Revoke access" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "이 2단계 로그인 제공자는 귀하의 계정에 사용 가능합니다." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "보안 키를 읽어오는데 문제가 발생했습니다. 다시 시도해보십시오." }, - "twoFactorWebAuthnWarning": { - "message": "플랫폼 제한으로 인해, 모든 Bitwarden 애플리케이션에서 WebAuthn을 사용할 수 없습니다. WebAuthn을 사용할 수 없을 때 계정에 접근할 수 있도록 다른 2단계 로그인 방법을 활성화하십시오. 지원하는 플랫폼:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "WebAuthn 지원 브라우저가 있는 데스크탑/랩탑의 웹 보관함 및 브라우저 확장 (WebAuthn이 활성화된 Chrome, Opera, Vivaldi 또는 Firefox 사용)" + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Bitwarden 2단계 로그인 복구 코드" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "이 사용자는 계정을 보호하기 위해 2단계 로그인을 사용하고 있습니다." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "$ID$ 사용자에 대한 SSO 연결이 해제되었습니다.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "기기" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "지원하지 않는 웹 브라우저를 사용하고 있습니다. 웹 보관함 기능이 제대로 동작하지 않을 수 있습니다." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "일반적인 2단계 로그인 방법을 통해 계정에 액세스할 수 없는 경우, 2단계 로그인 복구 코드를 사용하여 계정의 모든 2단계 제공자를 비활성화할 수 있습니다." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "계정 2단계 로그인 복구하기" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "마스터 비밀번호 강도에 대한 최소 요구 사항을 설정해주세요." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Require two-step login" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "비밀번호 생성기 설정에 최소 요구 사항을 설정해주세요." }, - "passwordGeneratorPolicyInEffect": { - "message": "하나 이상의 단체 정책이 생성기 설정에 영항을 미치고 있습니다." - }, "masterPasswordPolicyInEffect": { "message": "하나 이상의 단체 정책이 마스터 비밀번호가 다음 사항을 따르도록 요구합니다:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "조직 소유자와 관리자는 이 정책을 적용받지 않습니다." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "파일" }, "sendTypeText": { "message": "텍스트" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "새 Send 생성", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Send 삭제", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "정말 이 Send를 삭제하시겠습니까?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "어떤 유형의 Send인가요?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "삭제 날짜" }, - "deletionDateDesc": { - "message": "이 Send가 정해진 일시에 영구적으로 삭제됩니다.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "최대 접근 횟수" }, - "maxAccessCountDesc": { - "message": "설정할 경우, 최대 접근 횟수에 도달할 때 이 Send에 접근할 수 없게 됩니다.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "현재 접근 횟수" - }, - "sendPasswordDesc": { - "message": "이 Send에 접근하기 위해 암호를 입력하도록 선택적으로 요구합니다.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "이 Send에 대한 비공개 메모", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "비활성화됨" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "비밀번호를 제거하시겠습니까?" }, - "hideEmail": { - "message": "받는 사람으로부터 나의 이메일 주소 숨기기" - }, - "disableThisSend": { - "message": "이 Send를 비활성화하여 아무도 접근할 수 없게 합니다.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "모든 Send" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "삭제 대기 중" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "만료됨" }, @@ -5161,13 +5441,6 @@ "message": "사용자가 Send를 생성하거나 수정할 때 받는 사람으로부터 자신의 이메일 주소를 숨기지 못하게 합니다.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendOptionsPolicyInEffect": { - "message": "다음 단체 정책이 현재 영향을 미치고 있습니다:" - }, - "sendDisableHideEmailInEffect": { - "message": "사용자는 Send를 생성하거나 수정할 때 받는 사람으로부터 자신의 이메일 주소를 숨기지 못하게 됩니다.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "modifiedPolicyId": { "message": "$ID$ 정책을 편집했습니다.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "조직 사용자의 개인 소유권 비활성화" }, - "textHiddenByDefault": { - "message": "Send에 접근할 때 기본적으로 텍스트를 숨김", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "이 Send의 이름", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "전송하려는 텍스트" - }, - "sendFileDesc": { - "message": "전송하려는 파일" - }, - "copySendLinkOnSave": { - "message": "저장할 때 이 Send를 공유하기 위한 링크를 클립보드에 복사합니다." - }, - "sendLinkLabel": { - "message": "Send 링크", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "삭제 날짜와 만료 날짜를 저장하는 도중 오류가 발생했습니다." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "2단계 인증을 확인하려면 아래의 버튼을 클릭하십시오." }, "webAuthnAuthenticate": { "message": "WebAuthn 인증" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "이 브라우저에서는 WebAuthn이 지원되지 않습니다." }, @@ -5655,6 +5916,20 @@ "error": { "message": "오류" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, - "regenerateUsername": { - "message": "Regenerate username" - }, "generateUsername": { "message": "Generate username" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Username type" - }, "plusAddressedEmail": { "message": "Plus addressed email", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Hostname", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API access token" - }, "deviceVerification": { "message": "Device verification" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Number of users" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "pickAnAvatarColor": { "message": "Pick an avatar color" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "No collection" }, - "canView": { - "message": "Can view" - }, - "canViewExceptPass": { - "message": "Can view, except passwords" - }, - "canEdit": { - "message": "Can edit" - }, - "canEditExceptPass": { - "message": "Can edit, except passwords" - }, "noCollectionsAdded": { "message": "No collections added" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domain" - }, "alreadyHaveAccount": { "message": "Already have an account?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index 0b830e6dc9a..7c8c3b2831e 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Kritiskās lietotnes" }, + "noCriticalAppsAtRisk": { + "message": "Nav riskam pakļautu būtisku lietotņu" + }, "accessIntelligence": { "message": "Piekļuves inteliģence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "Riskam pakļautie dalībnieki" }, + "atRiskMembersWithCount": { + "message": "Riskam pakļautie dalībnieki ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Riskam pakļautas lietotnes ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "Šie dalībnieki piesakās lietotnēs ar vājām, atklātām vai atkārtoti izmantotām parolēm." + }, + "atRiskApplicationsDescription": { + "message": "Šīm lietotnēm ir vājas, atklātas vai atkārtoti izmantotas paroles." + }, + "atRiskMembersDescriptionWithApp": { + "message": "Šie dalībnieki piesakās $APPNAME$ ar vājām, atklātām vai atkārtoti izmantotām parolēm.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Kopējais dalībnieku skaits" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Kopējais lietotņu skaits" }, + "unmarkAsCriticalApp": { + "message": "Noņemt kritiskas lietontes atzīmi" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Kritiskas lietotnes atzīme sekmīgi noņemta" + }, "whatTypeOfItem": { "message": "Kāda veida vienums tas ir?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Piezīmes" }, + "privateNote": { + "message": "Personiska piezīme" + }, "note": { "message": "Piezīme" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Vilkt, lai kārtotu" }, + "dragToReorder": { + "message": "Vilkt, lai pārkārtotu" + }, "cfTypeText": { "message": "Teksts" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Labot mapi" }, + "editWithName": { + "message": "Labot $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "Jauna mape" + }, + "folderName": { + "message": "Mapes nosaukums" + }, + "folderHintText": { + "message": "Apakšmapes var izveidot, ja pievieno iekļaujošās mapes nosaukumu, aiz kura ir \"/\". Piemēram: Tīklošanās/Forumi" + }, + "deleteFolderPermanently": { + "message": "Vai tiešām neatgriezeniski izdzēst šo mapi?" + }, "baseDomain": { "message": "Pamata domēns", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Vienuma nosaukums" }, - "cannotRemoveViewOnlyCollections": { - "message": "Nevar noņemt krājumus ar tiesībām \"Tikai skatīt\": $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "piem.,", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Atlase" }, - "moveSelectedToOrg": { - "message": "Pārvietot atzīmēto uz apvienību" - }, "deleteSelected": { "message": "Izdzēst atlasītos" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Atzīmētie vienumi pārvietoti uz $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Vienumi pārvietoti uz $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "Nē" }, + "location": { + "message": "Atrašanās vieta" + }, "loginOrCreateNewAccount": { "message": "Jāpiesakās vai jāizveido jauns konts, lai piekļūtu drošajai glabātavai." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Pieteikties Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Jāievada e-pastā nosūtītais kods" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Jāievada kods no savas autentificētājlietotnes" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Jāpiespiež sava YubiKey ierīce, lai autentificētu" + }, "authenticationTimeout": { "message": "Autentificēšanās noildze" }, "authenticationSessionTimedOut": { "message": "Iestājās autentificēšanās sesijas noildze. Lūgums sākt pieteikšanos no jauna." }, - "verifyIdentity": { - "message": "Jāapliecina sava identitāte" + "verifyYourIdentity": { + "message": "Apliecināt savu identitāti" + }, + "weDontRecognizeThisDevice": { + "message": "Mēs neatpazīstam šo ierīci. Jāievada kods, kas tika nosūtīts e-pastā, lai apliecinātu savu identitāti." + }, + "continueLoggingIn": { + "message": "Turpināt pieteikšanos" + }, + "whatIsADevice": { + "message": "Kas ir ierīce?" + }, + "aDeviceIs": { + "message": "Ierīce ir atsevišķa uzstādīta Bitwarde lietotne, kurā ir veikta pieteikšanās. Atkārtota uzstādīšana, lietotnes datu vai sīkdatņu notīrīšana var beigties ar ierīces vairākkārtīgu parādīšanos." }, "logInInitiated": { "message": "Uzsākta pieteikšanās" }, + "logInRequestSent": { + "message": "Pieprasījums nosūtīts" + }, "submit": { "message": "Iesniegt" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Jāievada sava konta e-pasta adrese, un paroles norāde tiks nosūtīta" }, - "passwordHint": { - "message": "Paroles norāde" - }, - "enterEmailToGetHint": { - "message": "Norādīt konta e-pasta adresi, lai saņemtu galvenās paroles norādi." - }, "getMasterPasswordHint": { "message": "Saņemt galvenās paroles norādi" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "Uz ierīci ir nosūtīts paziņojums." }, + "notificationSentDevicePart1": { + "message": "Bitwarden jāatslēdz savā ierīcē vai " + }, + "areYouTryingToAccessYourAccount": { + "message": "Vai mēģini piekļūt savam kontam?" + }, + "accessAttemptBy": { + "message": "$EMAIL$ piekļuves mēģinājums", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Apstiprināt piekļuvi" + }, + "denyAccess": { + "message": "Noraidīt piekļuvi" + }, + "notificationSentDeviceAnchor": { + "message": "tīmekļa lietotnē" + }, + "notificationSentDevicePart2": { + "message": "Pirms apstiprināšanas jāpārliecinās, ka pirkstu nospieduma vārdkopa atbilst zemāk esošajai." + }, + "notificationSentDeviceComplete": { + "message": "Savā ierīcē jāatslēdz Bitwarden. Pirms apstiprināšanas jāpārliecinās, ka pirkstu nospieduma vārdkopa atbilst zemāk esošajai." + }, "aNotificationWasSentToYourDevice": { "message": "Uz ierīci tika nosūtīts paziņojums" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Lūgums pārliecināties, ka konts ir atslēgts un atpazīšanas vārdkopa ir tāda pati arī otrā ierīcē" - }, "versionNumber": { "message": "Laidiens $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Atcerēties mani" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Šajā ierīcē 30 dienas vairs nevaicāt" + }, "sendVerificationCodeEmailAgain": { "message": "Atkārtoti nosūtīt apstiprinājuma kodu" }, "useAnotherTwoStepMethod": { "message": "Izmantot citu divpakāpju pieteikšanās veidu" }, + "selectAnotherMethod": { + "message": "Atlasīt citu veidu", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Izmantot savu atkopes kodu" + }, "insertYubiKey": { "message": "Ievietojiet YubiKey datora USB portā un pēc tam pieskarieties tā pogai." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Divpakāpju pieteikšanās iespējas" }, + "selectTwoStepLoginMethod": { + "message": "Atlasīt divpakāpju pieteikšanās veidu" + }, "recoveryCodeDesc": { "message": "Zaudēta piekļuve visiem divpakāpju pieteikšanās nodrošinātājiem? Jāizmanto atkopšanas kods, lai izslēgtu visus sava konta divpakāpju pieteikšanās nodrošinātājus." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Pārgājis no FIDO)" }, + "openInNewTab": { + "message": "Atvērt jaunā cilnē" + }, "emailTitle": { "message": "E-pasts" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Jāizvēlas apvienība, uz kuru pārvietot šo vienumu. Pārvietošana nodod šī vienuma piederību apvienībai. Pēc šī vienuma pārvietošanas Tu vairs nebūsi tā tiešais īpašnieks." }, - "moveManyToOrgDesc": { - "message": "Jāizvēlas apvienība, uz kuru pārvietot šos vienumus. Pārvietošana nodod šo vienumu piederību apvienībai. Pēc šo vienumu pārvietošanas Tu vairs nebūsi to tiešais īpašnieks." - }, "collectionsDesc": { "message": "Labot krājumus, ar kuriem šis vienums ir kopīgots. Tikai apvienības lietotāji, kam ir piekļuve šiem krājumiem, redzēs šo vienumu." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "Ir atzīmēts(i) $COUNT$ vienums(i). $MOVEABLE_COUNT$ vienums(i) var tikt pārvietoti uz apvienību, bet $NONMOVEABLE_COUNT$ nē.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Apstiprinājuma kods (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Izvairīties no viegli sajaucamām rakstzīmēm", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Pārizveidot paroli" - }, "length": { "message": "Garums" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Lūgums pieteikties atkārtoti." }, + "currentSession": { + "message": "Pašreizējā sesija" + }, + "requestPending": { + "message": "Pieprasījums ir apstrādē" + }, "logBackInOthersToo": { "message": "Lūgums pieteikties atkārtoti. Ja tiek izmantotas citas Bitwarden lietotnes, ir nepieciešams atteikties un atkārtoti pieteikties arī tajās." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Bīstamā sadaļa" }, - "dangerZoneDesc": { - "message": "Piesardzību, šīs darbības nav atsaucamas!" - }, - "dangerZoneDescSingular": { - "message": "Uzmanīgi, šī darbība ir neatgriezeniska!" - }, "deauthorizeSessions": { "message": "Padarīt sesijas spēkā neesošas" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Tiks veikta atteikšanās no pašreizējās sesijas, un pēc tam būs nepieciešams atkārtoti pieteikties. Būs nepieciešama arī divpakāpju pieteikšanās, ja tā ir iespējota. Citās ierīcēs darbojošās sesijas var būt spēkā līdz vienai stundai." }, + "newDeviceLoginProtection": { + "message": "Pieteikšanās jaunā ierīcē" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Izslēgt pieteikšanās jaunā ierīcē aizsardzību" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Ieslēgt pieteikšanās jaunā ierīcē aizsardzību" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Turpināt zemāk, lai izslēgtu apliecināšanas e-pasta ziņojumus, ko Bitwarden sūta, kad notiek pieteikšanās jaunā ierīcē." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Turpināt zemāk, lai Bitwarden sūtītu apliecināšanas e-pasta ziņojumus, kad notiek pieteikšanās jaunā ierīcē." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "Ar izslēgtu pieteikšanās jaunā ierīcē aizsardzību ikviens ar Tavu galveno paroli var piekļūt kontam no jebkuras ierīces. Lai aizsargātu savu kontu bez apliecināšanas e-pasta ziņojumiem, jāiestata divpakāpju pieteikšanās." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "Pieteikšanās jaunā ierīcē aizsardzības izmaiņas saglabātas" + }, "sessionsDeauthorized": { "message": "Visu sesiju darbība ir atsaukta" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Divpakāpju pieteikšanās var pastāvīgi liegt piekļuvi Bitwarden kontam. Atkopšanas kods ļauj piekļūt tam gadījumā, kad vairs nav iespējams izmantot ierasto divpakāpju pieteikšanās nodrošinātāju (piemēram, ir pazaudēta ierīce). Bitwarden atbalsts nevarēs palīdzēt, ja tiks pazaudēta piekļuve kontam. Ir ieteicams, ka atkopšanas kods tiek pierakstīts vai izdrukāts un turēts drošā vietā." }, + "yourSingleUseRecoveryCode": { + "message": "Vienreizējas izmantošanas atkopes kodu var izmantot, lai izslēgtu divpakāpju pieteikšanos gadījumā, ja tiek zaudēta piekļuve savam divpakāpju pieteikšanās nodrošinātājam. Bitwarden iesaka pierakstīt atkopes kodu un glabāt to drošā vietā." + }, "viewRecoveryCode": { "message": "Skatīt atkopšanas kodu" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Pārvaldīt" }, - "canManage": { - "message": "Var pārvaldīt" + "manageCollection": { + "message": "Pārvaldīt krājumu" + }, + "viewItems": { + "message": "Apskatīt vienumus" + }, + "viewItemsHidePass": { + "message": "Apskatīt vienumus, paslēptās paroles" + }, + "editItems": { + "message": "Labot vienumus" + }, + "editItemsHidePass": { + "message": "Labot vienumus, paslēptās paroles" }, "disable": { "message": "Atspējot" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Atsaukt piekļuvi" }, + "revoke": { + "message": "Atsaukt" + }, "twoStepLoginProviderEnabled": { "message": "Kontā ir iespējots šis divpakāpju pieteikšanās nodrošinātājs." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "Radās sarežģījumi, nolasot drošības atslēgu. Jāmēģina vēlreiz." }, - "twoFactorWebAuthnWarning": { - "message": "Platformas ierobežojumu dēļ WebAuth nevar izmantot visās Bitwarden lietotnēs. Ir ieteicams iespējot vēl kādu divpakāpju pieteikšanās nodrošinātāju, lai varētu piekļūt kontam, kad nav iespējams izmantot WebAuth. Atbalstītās platformas:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Tīmekļa glabātava un pārlūku paplašinājums galddatorā/klēpjdatorā ar WebAuthn iespējotu pārlūku (Chrome, Opera, Vivaldi vai Firefox ar iespējotu FIDO U2F)." + "twoFactorWebAuthnWarning1": { + "message": "Platformas ierobežojumu dēļ WebAuth nevar izmantot visās Bitwarden lietotnēs. Ir ieteicams iespējot vēl kādu divpakāpju pieteikšanās nodrošinātāju, lai varētu piekļūt kontam, kad nav iespējams izmantot WebAuth." }, "twoFactorRecoveryYourCode": { "message": "Bitwarden divpakāpju pieteikšanās atkopšanas kods" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Tev ir atlicis 1 uzaicinājums." + }, + "inviteZeroEmailDesc": { + "message": "Tev ir atlikuši 0 uzaicinājumu." + }, "userUsingTwoStep": { "message": "Šis lietotājs izmanto divpakāpju pieteikšanos, lai aizsargātu savu kontu." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "SSO atsaistīta." + }, "unlinkedSsoUser": { "message": "Atsaistīta vienotā pieteikšanās lietotājam $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Ierīce" }, + "loginStatus": { + "message": "Pieteikšanās stāvoklis" + }, + "firstLogin": { + "message": "Pirmā pieteikšanās" + }, + "trusted": { + "message": "Uzticama" + }, + "needsApproval": { + "message": "Nepieciešams apstiprinājums" + }, + "areYouTryingtoLogin": { + "message": "Vai mēģini pieteikties?" + }, + "logInAttemptBy": { + "message": "$EMAIL$ pieteikšanās mēģinājums", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Ierīces veids" + }, + "ipAddress": { + "message": "IP adrese" + }, + "confirmLogIn": { + "message": "Apstiprināt pieteikšanos" + }, + "denyLogIn": { + "message": "Atteikt pieteikšanos" + }, + "thisRequestIsNoLongerValid": { + "message": "Šis pieprasījums vairs nav derīgs." + }, + "logInConfirmedForEmailOnDevice": { + "message": "$EMAIL$ pieteikšanās apstiprināta ierīcē $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "Tu noraidīji pieteikšanās mēģinājumu no citas ierīces. Ja tas tiešām biji Tu, mēģini pieteikties no ierīces vēlreiz!" + }, + "loginRequestHasAlreadyExpired": { + "message": "Pieteikšanās pieprasījuma derīgums jau ir beidzies." + }, + "justNow": { + "message": "Tikko" + }, + "requestedXMinutesAgo": { + "message": "Pieprasīts pirms $MINUTES$ minūtēm", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Tiek veidots konts" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "Tiek izmantots neatbalstīts tīmekļa pārlūks. Tīmekļa glabātava var nedarboties pareizi." }, + "youHaveAPendingLoginRequest": { + "message": "Ir neizskatīts pieteikšanās pieprasījums no citas ierīces." + }, + "reviewLoginRequest": { + "message": "Izskatīt pieteikšanās pieprasījumu" + }, "freeTrialEndPromptCount": { "message": "Bezmaksas izmēģinājums beigsies pēc $COUNT$ dienām.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "Ja kontam nevar piekļūt ar ierastajiem divpakāpju pieteikšanās veidiem, var izmantot atkopšanas kodu, lai atspējotu visus konta divpakāpju pieteikšanās nodrošinātājus." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Pieteikties ar vienreizējas izmantošanas atkopes kodu zemāk. Tas kontā izslēgs visus divpakāpju nodrošinātājus." + }, "recoverAccountTwoStep": { "message": "Atkopt konta divpakāpju pieteikšanos" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Šifrēšanas atslēgas atjaunināšana nav iespējama" }, + "editFieldLabel": { + "message": "Labot $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Pārkārtot $LABEL$. Jāizmanto bultas taustiņš, lai pārvietotu vienumu augšup vai lejup.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ pārvietots augšup, $INDEX$. no $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ pārvietots lejup, $INDEX$. no $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "Šifrēšanas atslēgas atjaunināšanas laikā mapes nevarēja atšifrēt. Lai turpinātu atjaunināšanu, mapes ir jāizdzēš. Glabātavas vienumi netiks izdzēsti, ja turpināsi." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Uzstādīt galvenās paroles stipruma mazākās izpildāmās prasības." }, + "passwordStrengthScore": { + "message": "Paroles stipruma novērtējums $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Pieprasīt divpakāpju pieteikšanos" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Uzstādīt paroļu veidotāja uzstādījumu mazākās izpildāmās prasības." }, - "passwordGeneratorPolicyInEffect": { - "message": "Viens vai vairāki apvienības nosacījumi ietekmē veidotāja iestatījumus." - }, "masterPasswordPolicyInEffect": { "message": "Vienā vai vairākos apvienības nosacījumos ir norādīts, ka galvenajai parolei ir jāatbilst šādām prasībām:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Uz apvienības īpašniekiem un pārvaldītājiem neattiecas šīs nosacījumu kopas piemērošana." }, + "limitSendViews": { + "message": "Ierobežot skatījumus" + }, + "limitSendViewsHint": { + "message": "Neviens nevar apskatīt šo Send pēc tam, kad ir sasniegts ierobežojums.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "Atlikuši $ACCESSCOUNT$ skatījumi", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Informācija par Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Kopīgojamais teksts" + }, "sendTypeFile": { "message": "Datne" }, "sendTypeText": { "message": "Teksts" }, + "sendPasswordDescV3": { + "message": "Pēc izvēles var pievienot paroli saņēmējiem, lai varētu piekļūt šim Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Jauns Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Izdzēst Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Vai tiešām izdzēst šo Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "Kāds ir šī Send veids?", + "deleteSendPermanentConfirmation": { + "message": "Vai tiešām neatgriezeniski izdzēst šo Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Dzēšanas datums" }, - "deletionDateDesc": { - "message": "Send tiks neatgriezeniski izdzēsts norādītajā datumā un laikā.", + "deletionDateDescV2": { + "message": "Send šajā datumā tiks neatgriezeniski izdzēsts.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Lielākais pieļaujamais piekļuves reižu skaits" }, - "maxAccessCountDesc": { - "message": "Ja iestatīts, lietotāji nevarēs piekļūt šim Send, kad tiks sasniegts lielākais pieļaujamais piekļūšanas reižu skaits.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Pašreizējais piekļuvju skaits" - }, - "sendPasswordDesc": { - "message": "Pēc izvēles pieprasīt paroli, lai lietotāji varētu piekļūt šim Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Personīgas piezīmes par šo Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Atspējots" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Vai tiešām noņemt paroli?" }, - "hideEmail": { - "message": "Slēpt e-pasta adresi no saņēmējiem." - }, - "disableThisSend": { - "message": "Izslēgt šo Send, lai neviens tam nevarētu piekļūt.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Visi Send" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Gaida dzēšanu" }, + "hideTextByDefault": { + "message": "Pēc noklusējuma paslēpt tekstu" + }, "expired": { "message": "Beidzies izmantošanas laiks" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "Ir spēkā zemāk uzskaitītie apvienības nosacījumi:" - }, - "sendDisableHideEmailInEffect": { - "message": "Lietotājiem nav ļauts slēpt e-pasta adresi no 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." - }, "modifiedPolicyId": { "message": "Nosacījums $ID$ izmainīts.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Atspējot personīgās īpašumtiesības apvienības lietotājiem" }, - "textHiddenByDefault": { - "message": "Pēc noklusējuma paslēpt tekstu, kad piekļūst Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Lasāms nosaukums, kas apraksta šo Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Teksts, kuru ir vēlme nosūtīt." - }, - "sendFileDesc": { - "message": "Datne, kuru ir vēlme nosūtīt." - }, - "copySendLinkOnSave": { - "message": "Saglabāšanas brīdī ievietot saiti starpliktuvē, lai kopīgotu šo Send." - }, - "sendLinkLabel": { - "message": "Send saite", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "Atgadījās kļūda, saglabājot dzēšanas un derīguma beigu datumus." }, + "hideYourEmail": { + "message": "Paslēpt e-pasta adresi no apskatītājiem." + }, "webAuthnFallbackMsg": { "message": "Lai apstiprinātu 2FA, lūgums klikšķināt uz zemāk esošās pogas." }, "webAuthnAuthenticate": { "message": "Autentificēt WebAuthn" }, + "readSecurityKey": { + "message": "Nolasīt drošības atslēgu" + }, + "awaitingSecurityKeyInteraction": { + "message": "Gaida mijiedarbību ar drošības atslēgu..." + }, "webAuthnNotSupported": { "message": "WebAuthn šajā pārlūkā netiek atbalstīts." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Kļūda" }, + "decryptionError": { + "message": "Atšifrēšanas kļūda" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nevarēja atšifrēt zemāk uzskaitītos glabātavas vienumus." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Jāsazinās ar klientu atbalstu,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "lai izvairītos no papildu datu zaudējumiem.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Lietotāju pārvaldīšanai ir jābūt iespējotai arī ar konta atkopšanas pārvaldīšanas atļauju" }, @@ -6516,15 +6791,6 @@ "message": "Veidotājs", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "Ko ir vēlme izveidot?" - }, - "passwordType": { - "message": "Paroles veids" - }, - "regenerateUsername": { - "message": "Pārizveidot lietotājvārdu" - }, "generateUsername": { "message": "Izveidot lietotājvārdu" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Lietotājvārda veids" - }, "plusAddressedEmail": { "message": "E-pasta adrese ar plusu", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Izmantot uzstādīto domēna visu tverošo iesūtni." }, + "useThisEmail": { + "message": "Izmantot šo e-pasta adresi" + }, "random": { "message": "Nejauši", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ atteica pieprasījumu. Lūgums sazināties ar savu pakalpojma nodrošinātāju, lai iegūtu palīdzību.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ atteica pieprasījumu: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Neizdevās iegūt $SERVICENAME$ aizsegta e-pasta konta Id.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Resursdatora nosaukums", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API piekļuves pilnvara" - }, "deviceVerification": { "message": "Ierīces apstiprināšana" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Kontam ir nepieciešama DUO divpakāpju pieteikšanās." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Ir nepieciešama Duo divpakāpju pieteikšanās, lai pieteiktos savā kontā. Jāseko zemāk esošajām norādēm, lai pabeigtu pieteikšanos." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Jāseko zemāk esošajām norādēm, lai pabeigtu pieteikšanos." + }, "launchDuo": { "message": "Palaist DUO" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Lietotāju skaits" }, - "loggingInAs": { - "message": "Piesakās kā" - }, - "notYou": { - "message": "Tas neesi Tu?" - }, "pickAnAvatarColor": { "message": "Izvēlēties iemiesojuma krāsu" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "Nav krājuma" }, - "canView": { - "message": "Var skatīt" - }, - "canViewExceptPass": { - "message": "Var skatīt, izņemot paroles" - }, - "canEdit": { - "message": "Var labot" - }, - "canEditExceptPass": { - "message": "Var labot, izņemot paroles" - }, "noCollectionsAdded": { "message": "Nav pievienotu krājumu" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Pieprasīt pārvaldītāja apstiprinājumu" }, - "approveWithMasterPassword": { - "message": "Apstiprināt ar galveno paroli" - }, "trustedDeviceEncryption": { "message": "Uzticamo ierīču šifrēšana" }, "trustedDevices": { "message": "Uzticamās ierīces" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Pēc pieteikšanās dalībnieki atšifrēs glabātavas saturu ar ierīcē glabātu atslēgu. ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Dalībniekiem nebūs nepieciešama galvenā parole, kad pieteiksies ar SSO. Galvenā parole ir aizvietota ar šifrēšanas atslēgu, kas tiek glabāta ierīcē, padarot to par uzticamu. Pirmā ierīce, kurā dalībnieks izveido savu kontu un tajā piesakās, būs uzticama. Jaunas ierīces būs nepieciešams apstiprināt ar esošu uzticamo ierīci vai pārvaldītājam. ", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "Vienas apvienības", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "nosacījums,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO nepieciešamības", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "nosacījums un", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "kontu atkopšanas pārvaldības", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "nosacījums ar automātisku ievietošanu sarakstā tiks ieslēgts, kad šī iespēja tiek izmantota.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "nosacījums tiks ieslēgts, kad šī iespēja tiks izmantota.", + "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": "Apvienības atļaujas tika atjauninātas, un tās pieprasa iestatīt galveno paroli.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Apstiprināt pieprasījumu" }, + "deviceApproved": { + "message": "Ierīce apstiprināta" + }, + "deviceRemoved": { + "message": "Ierīce noņemta" + }, + "removeDevice": { + "message": "Noņemt ierīci" + }, + "removeDeviceConfirmation": { + "message": "Vai tiešām noņemt šo ierīci?" + }, "noDeviceRequests": { "message": "Nav ierīču pieprasījumu" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Pieprasījums tika nosūtīts pārvaldītājam." }, - "youWillBeNotifiedOnceApproved": { - "message": "Tiks saņemts paziņojums, tiklīdz būs apstiprināts." - }, "troubleLoggingIn": { "message": "Neizdodas pieteikties?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Ļaut krājumu izdzēšanu tikai īpašniekiem un pārvaldītājiem" }, + "limitItemDeletionDesc": { + "message": "Ierobežot lietotājiem vienumu izdzēšanu ar atļauju \"Var pārvaldīt\"" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Īpašnieki un pārvaldnieki var pārvaldīt visus krājumus un vienumus" }, @@ -8494,9 +8778,6 @@ "message": "Pašmitināta servera URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Aizstājdomēns" - }, "alreadyHaveAccount": { "message": "Jau ir konts?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "Nav piekļuves pārvaldīt šo krājumu." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Trūkst atļaujas \"Var pārvaldīt\"" + "grantManageCollectionWarningTitle": { + "message": "Trūkst krājumu pārvaldīšanas atļaujas" }, - "grantAddAccessCollectionWarning": { - "message": "Jānodrošina atļauja \"Var pārvaldīt\", lai ļautu pilnu krājuma pārvaldību, tajā skaitā krājuma izdzēšanu." + "grantManageCollectionWarning": { + "message": "Jānodrošina atļauja \"Pārvaldīt kŗajumu\", lai ļautu pilnu krājuma pārvaldību, tajā skaitā krājuma izdzēšanu." }, "grantCollectionAccess": { "message": "Piešķirt kopām vai dalībniekiem piekļuvi šim krājumam." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Konfigurēt ierīču pārvaldību Bitwarden, izmantojot operētājsistēmai atbilstošas ieviešanas norādes." }, + "deviceIdMissing": { + "message": "Trūkst ierīces Id" + }, + "deviceTypeMissing": { + "message": "Trūkst ierīces veids" + }, + "deviceCreationDateMissing": { + "message": "Trūkst ierīces izveidošanas datums" + }, + "desktopRequired": { + "message": "Nepieciešams dators" + }, + "reopenLinkOnDesktop": { + "message": "Šī saite e-pastā atkārtoti jāatver datorā." + }, "integrationCardTooltip": { "message": "Palaist $INTEGRATION$ ieviešanas norādes.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "mēnesī par dalībnieku" }, + "monthPerMemberBilledAnnually": { + "message": "par dalībnieku mēnesī ar ikgadēju rēķinu" + }, "seats": { "message": "Vietas" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Uzzināt vairāk par Bitwarden API" }, + "fileSend": { + "message": "Datņu Send" + }, "fileSends": { "message": "Datņu Send" }, + "textSend": { + "message": "Teksta Send" + }, "textSends": { "message": "Teksta Send" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Atslēgas algoritms" }, + "sshPrivateKey": { + "message": "Privātā atslēga" + }, + "sshPublicKey": { + "message": "Publiskā atslēga" + }, + "sshFingerprint": { + "message": "Pirkstu nospiedums" + }, "sshKeyFingerprint": { "message": "Pirkstu nospiedums" }, @@ -9774,10 +10088,6 @@ "message": "Iekļaut īpašās rakstzīmes", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Pievienot pielikumu" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Apraksta kods" }, + "cannotRemoveViewOnlyCollections": { + "message": "Nevar noņemt krājumus ar tiesībām \"Tikai skatīt\": $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Svarīgs paziņojums" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Noņemt dalībniekus" }, + "devices": { + "message": "Ierīces" + }, + "deviceListDescription": { + "message": "Kontā ir notikusi pieteikšanās katrā no zemāk uzskaitītajām ierīcēm. Ja kāda no tām nav atpazīstama, tā ir uzreiz jānoņem." + }, + "deviceListDescriptionTemp": { + "message": "Ar kontu ir veikta pieteikšanās katrā no zemāk uzskaitītajām ierīcēm." + }, "claimedDomains": { "message": "Pieteiktie domēni" }, @@ -9993,7 +10321,7 @@ } }, "updatedRevokeSponsorshipConfirmationForSentSponsorship": { - "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "message": "Ja tiks noņemta $EMAIL$, atbalstītājdarbība šim Ģimenes plānam nevarēs izmantot. Vai tiešām turpināt?", "placeholders": { "email": { "content": "$1", @@ -10002,7 +10330,7 @@ } }, "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { - "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "message": "Ja tiks noņemta $EMAIL$, šī Ģimenes plāna atbalstītājdarbība beigsies, un $DATE$ ar saglabāto maksājumu veidu tiks veikta $40 apmaksa + piemērojamais nodoklis. Nebūs iespējams izmantot jaunu atbalstītājdarbību līdz $DATE$. Vai tiešām turpināt?", "placeholders": { "email": { "content": "$1", @@ -10020,7 +10348,69 @@ "organizationNameMaxLength": { "message": "Apvienības nosaukums nevar pārsniegt 50 rakstzīmes." }, - "resellerRenewalWarning": { + "sshKeyWrongPassword": { + "message": "Ievadītā parole ir nepareiza." + }, + "importSshKey": { + "message": "Ievietot" + }, + "confirmSshKeyPassword": { + "message": "Apstiprināt paroli" + }, + "enterSshKeyPasswordDesc": { + "message": "Ievadīt SSH atslēgas paroli." + }, + "enterSshKeyPassword": { + "message": "Ievadīt paroli" + }, + "invalidSshKey": { + "message": "SSH atslēga ir nederīga" + }, + "sshKeyTypeUnsupported": { + "message": "SSH atslēgas veids netiek atbalstīts" + }, + "importSshKeyFromClipboard": { + "message": "Ievietot atslēgu no starpliktuves" + }, + "sshKeyImported": { + "message": "SSH atslēga tika sekmīgi ievietota" + }, + "copySSHPrivateKey": { + "message": "Ievietot privāto atslēgu starpliktuvē" + }, + "openingExtension": { + "message": "Atver Bitwarden pārlūka paplašinājumu" + }, + "somethingWentWrong": { + "message": "Kaut kas nogāja greizi..." + }, + "openingExtensionError": { + "message": "Mums bija sarežģījumi Bitwarden pārlūka paplašinājuma atvēršanā. Klikšķināt uz pogas, lai to tagad atvērtu." + }, + "openExtension": { + "message": "Atvērt paplašinājumu" + }, + "doNotHaveExtension": { + "message": "Nav Bitwarden pārlūka paplašinājuma?" + }, + "installExtension": { + "message": "Uzstādīt paplašinājumu" + }, + "openedExtension": { + "message": "Atvērt pārlūka paplašinājumu" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Bitwarden pārlūka paplašinājums tika veiksmīgi atvērts. Tagad var pārskatīt savas riskam pakļautās paroles." + }, + "openExtensionManuallyPart1": { + "message": "Mums bija sarežģījumi Bitwarden pārlūka paplašinājuma atvēršanā. Rīkjoslā jāatver Bitwarden ikona", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "rīkjoslā.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { "message": "Abonements drīz tiks atjaunots. Lai nodrošinātu nepārtrauktu pakalpojumu, pirms $RENEWAL_DATE$ jāsazinās ar $RESELLER$, lai apstiprinātu atjaunošanu.", "placeholders": { "reseller": { @@ -10033,7 +10423,7 @@ } } }, - "resellerOpenInvoiceWarning": { + "resellerOpenInvoiceWarningMgs": { "message": "Rēķins par abonementu tika izdots $ISSUED_DATE$. Lai nodrošinātu nepārtrauktu pakalpojumu, pirms $DUE_DATE$ jāsazinās ar $RESELLER$, lai apstiprinātu atjaunošanu.", "placeholders": { "reseller": { @@ -10050,7 +10440,7 @@ } } }, - "resellerPastDueWarning": { + "resellerPastDueWarningMsg": { "message": "Rēķins par abonementu nav apmaksāts. Lai nodrošinātu nepārtrauktu pakalpojumu, pirms $GRACE_PERIOD_END$ jāsazināš ar $RESELLER$, lai apstiprinātu atjaunošanu.", "placeholders": { "reseller": { @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Apvienības abonements atsākts" + }, + "restartSubscription": { + "message": "Atsākt savu abonementu" + }, + "suspendedManagedOrgMessage": { + "message": "Jāsazinās ar $PROVIDER$, lai iegūtu palīdzību.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Pārvaldītājiem tagad ir iespēja izdzēst dalībnieku kontus, kas pieder pieteiktam domēnam." + }, + "deleteManagedUserWarningDesc": { + "message": "Šī darbība izdzēsīs dalībnieka kontu, tajā skaitā visus glabātavas vienumus. Tā aizstāj iepriekšējo darbību \"Noņemt\"." + }, + "deleteManagedUserWarning": { + "message": "\"Izdzēst\" ir jaunā darbība." + }, + "seatsRemaining": { + "message": "Ir atlikušas $REMAINING$ no apvienībai piesaistītajām $TOTAL$ vietām. Jāsazinās ar nodrošinātāju, lai pārvaldītu savu abonementu.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Esoša apvienība" + }, + "selectOrganizationProviderPortal": { + "message": "Jāatlasa apvienība, kuru pievienot savam nodrošinātāju portālam." + }, + "noOrganizations": { + "message": "Nav apvienību, ko uzskaitīt" + }, + "yourProviderSubscriptionCredit": { + "message": "Nodrošinātāja abonements saņemts kredītu par jebkuru atlikušo laiku apvienības abonementā." + }, + "doYouWantToAddThisOrg": { + "message": "Vai šo apvienību pievienot $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Pievienota esoša apvienība" + }, + "assignedExceedsAvailable": { + "message": "Piešķirtās vietas pārsniedz pieejamās vietas." + }, + "changeAtRiskPassword": { + "message": "Mainīt riskam pakļautu paroli" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Noņemt atslēgšanu ar PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Neļaut dalībniekiem atslēgt savu kontu ar PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plāniem nav piekļuve īstajiem notikumu žurnāliem", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Pilna piekļuve apvienības notikumu žurnāliem ir iegūstama, ja izmanto Komandu vai Uzņēmējdarbības plānu." + }, + "upgradeEventLogTitle": { + "message": "Uzlabot, lai piekļūtu īstajiem notikumu žurnāla datiem" + }, + "upgradeEventLogMessage": { + "message": "Šie notikumu ir tikai piemēri, un tie neatspoguļo īstus notikumus Bitwarden apvienībā." + }, + "cannotCreateCollection": { + "message": "Apvienībās, kuras izmanto Bitwarden bez maksas, var būt līdz 2 krājumiem. Jāpāriet uz maksas plānu, lai pievienotu vairāk krājumu." } } diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index c6f9aa8d853..46ce3b62533 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Critical applications" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "ഇത് ഏതു തരം ഇനം ആണ്?" }, @@ -159,6 +201,9 @@ "notes": { "message": "കുറിപ്പുകൾ" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Note" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "അടുക്കാൻ വലിച്ചിടുക" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "വാചകം" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "ഫോൾഡർ തിരുത്തുക" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "അടിസ്ഥാന ഡൊമെയ്ൻ", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "ഉദാഹരണം.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filter" }, - "moveSelectedToOrg": { - "message": "Move selected to organization" - }, "deleteSelected": { "message": "തിരഞ്ഞെടുത്തത് ഇല്ലാതാക്കുക" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "അല്ല" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "നിങ്ങളുടെ സുരക്ഷിത വാൾട്ടിലേക്ക് പ്രവേശിക്കാൻ ലോഗിൻ ചെയ്യുക അല്ലെങ്കിൽ ഒരു പുതിയ അക്കൗണ്ട് സൃഷ്ടിക്കുക." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "സമർപ്പിക്കുക" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "പാസ്‌വേഡ് സൂചനാ" - }, - "enterEmailToGetHint": { - "message": "നിങ്ങളുടെ പ്രാഥമിക പാസ്‌വേഡ് സൂചന ലഭിക്കുന്നതിന് നിങ്ങളുടെ അക്കൗണ്ട് ഇമെയിൽ വിലാസം നൽകുക." - }, "getMasterPasswordHint": { "message": "പ്രാഥമിക പാസ്‌വേഡ് സൂചന നേടുക" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "വേർഷൻ $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "എന്നെ ഓർക്കണം" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "സ്ഥിരീകരണ കോഡ് ഇമെയിൽ വഴി വീണ്ടും അയയ്ക്കുക" }, "useAnotherTwoStepMethod": { "message": "മറ്റൊരു രണ്ട് ഘട്ട പ്രവേശന രീതി ഉപയോഗിക്കുക" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "നിങ്ങളുടെ കമ്പ്യൂട്ടറിന്റെ യു‌എസ്‌ബി പോർട്ടിലേക്ക് YubiKey ഇടുക, തുടർന്ന് അതിന്റെ ബട്ടൺ അമർത്തുക." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "രണ്ട്-ഘട്ട പ്രവേശനം ഓപ്ഷനുകൾ" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "നിങ്ങളുടെ രണ്ട്-ഘടക ദാതാക്കളിലേക്കുള്ള ആക്‌സസ്സ് നഷ്‌ടപ്പെട്ടോ? നിങ്ങളുടെ അക്കൗണ്ടിൽ നിന്ന് രണ്ട്-ഘടക ദാതാക്കളെ പ്രവർത്തനരഹിതമാക്കാൻ നിങ്ങളുടെ റിക്കവറി കോഡ് ഉപയോഗിക്കുക." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Migrated from FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "ഇമെയിൽ" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." }, - "moveManyToOrgDesc": { - "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved." - }, "collectionsDesc": { "message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "സ്ഥിരീകരണ കോഡ് (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "പാസ്സ്‌വേഡ് വീണ്ടും സൃഷ്ടിക്കുക" - }, "length": { "message": "ദൈര്‍ഘ്യം" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "ദയവായി തിരികെ പ്രവേശിക്കുക." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "അപകട മേഖല" }, - "dangerZoneDesc": { - "message": "ശ്രദ്ധിക്കുക, ഈ പ്രവർത്തനങ്ങൾ മാറ്റാനാവില്ല!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Deauthorize Sessions" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Proceeding will also log you out of your current session, requiring you to log back in. You will also be prompted for two-step login again, if enabled. Active sessions on other devices may continue to remain active for up to one hour." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "എല്ലാ സെഷനും നിരസിച്ചു." }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Enabling two-step login can permanently lock you out of your Bitwarden account. A recovery code allows you to access your account in the event that you can no longer use your normal two-step login provider (ex. you lose your device). Bitwarden support will not be able to assist you if you lose access to your account. We recommend you write down or print the recovery code and keep it in a safe place." }, + "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." + }, "viewRecoveryCode": { "message": "View Recovery Code" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "നിയന്ത്രിക്കുക" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "പ്രവര്‍ത്തന രഹിതമാക്കുക" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Revoke access" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "This two-step login provider is enabled on your account." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "There was a problem reading the security key. Try again." }, - "twoFactorWebAuthnWarning": { - "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used. Supported platforms:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Web vault and browser extensions on a desktop/laptop with a WebAuthn supported browser (Chrome, Opera, Vivaldi, or Firefox with FIDO U2F turned on)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Your Bitwarden two-step login recovery code" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "ഈ ഉപയോക്താവ് അവരുടെ അക്കൗണ്ട് രണ്ട്-പ്രവേശനം ഉപയോഗിച്ച് സുരക്ഷിതമാക്കിയിരിക്കുന്നു." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "ഉപകരണം" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "If you cannot access your account through your normal two-step login methods, you can use your two-step login recovery code to disable all two-step providers on your account." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "അക്കൗണ്ടിന്റെ രണ്ട്-ഘട്ട പ്രവേശനം വീണ്ടെടുക്കുക" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Set minimum requirements for master password strength." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Require two-step login" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "പാസ്‌വേഡ് ജനറേറ്റർ കോൺഫിഗറേഷനായി മിനിമം ആവശ്യകതകൾ സജ്ജമാക്കുക." }, - "passwordGeneratorPolicyInEffect": { - "message": "ഒന്നോ അതിലധികമോ സംഘടന നയങ്ങൾ നിങ്ങളുടെ പാസ്സ്‌വേഡ് സൃഷ്ടാവിൻ്റെ ക്രമീകരണങ്ങളെ ബാധിക്കുന്നു." - }, "masterPasswordPolicyInEffect": { "message": "ഒന്നോ അതിലധികമോ ഓർഗനൈസേഷൻ നയങ്ങൾക്ക് ഇനിപ്പറയുന്ന ആവശ്യകതകൾ നിറവേറ്റുന്നതിന് നിങ്ങളുടെ മാസ്റ്റർ പാസ്‌വേഡ് ആവശ്യമാണ്:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "ഓർ‌ഗനൈസേഷൻ‌ ഉടമകളെയും രക്ഷാധികാരികളെയും ഈ നയം നടപ്പിലാക്കുന്നതിൽ‌ നിന്നും ഒഴിവാക്കിയിരിക്കുന്നു." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "ഫയൽ" }, "sendTypeText": { "message": "വാചകം" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "പുതിയ Send സൃഷ്‌ടിക്കുക", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Send ഇല്ലാതാക്കുക", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "ഈ Send ഇല്ലാതാക്കാൻ നിങ്ങൾ ആഗ്രഹിക്കുന്നുണ്ടോ?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "ഇത് ഏത് തരം അയയ്ക്കലാണ്?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "ഇല്ലാതാക്കൽ തീയതി" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "പരമാവധി ആക്സസ് എണ്ണം" }, - "maxAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "നിലവിലെ ആക്‌സസ്സ് എണ്ണം" - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "അപ്രാപ്‌തമാക്കി" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "പാസ്‌വേഡ് നീക്കംചെയ്യണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?" }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, - "disableThisSend": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "എല്ലാം Send-കൾ" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Pending deletion" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Expired" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "The following organization policies are currently in effect:" - }, - "sendDisableHideEmailInEffect": { - "message": "Users are not allowed 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." - }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Remove individual ownership for organization users" }, - "textHiddenByDefault": { - "message": "When accessing the Send, hide the text by default", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to Send." - }, - "sendFileDesc": { - "message": "The file you want to Send." - }, - "copySendLinkOnSave": { - "message": "Copy the link to share this Send to my clipboard upon save." - }, - "sendLinkLabel": { - "message": "Send link", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "To verify your 2FA please click the button below." }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn is not supported in this browser." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, - "regenerateUsername": { - "message": "Regenerate username" - }, "generateUsername": { "message": "Generate username" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Username type" - }, "plusAddressedEmail": { "message": "Plus addressed email", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Hostname", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API access token" - }, "deviceVerification": { "message": "Device verification" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Number of users" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "pickAnAvatarColor": { "message": "Pick an avatar color" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "No collection" }, - "canView": { - "message": "Can view" - }, - "canViewExceptPass": { - "message": "Can view, except passwords" - }, - "canEdit": { - "message": "Can edit" - }, - "canEditExceptPass": { - "message": "Can edit, except passwords" - }, "noCollectionsAdded": { "message": "No collections added" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domain" - }, "alreadyHaveAccount": { "message": "Already have an account?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index 36ab0050700..f334131b69b 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Critical applications" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "What type of item is this?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Notes" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Note" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Drag to sort" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Text" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Edit folder" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "Base domain", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "ex.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filter" }, - "moveSelectedToOrg": { - "message": "Move selected to organization" - }, "deleteSelected": { "message": "Delete selected" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "Log in or create a new account to access your secure vault." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Submit" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Lost access to all of your two-step login providers? Use your recovery code to turn off all two-step login providers from your account." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Migrated from FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "Email" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." }, - "moveManyToOrgDesc": { - "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved." - }, "collectionsDesc": { "message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Verification code (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Regenerate password" - }, "length": { "message": "Length" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Danger zone" }, - "dangerZoneDesc": { - "message": "Careful, these actions are not reversible!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Deauthorize sessions" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Proceeding will also log you out of your current session, requiring you to log back in. You will also be prompted for two-step login again, if set up. Active sessions on other devices may continue to remain active for up to one hour." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "All sessions deauthorized" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Setting up two-step login can permanently lock you out of your Bitwarden account. A recovery code allows you to access your account in the event that you can no longer use your normal two-step login provider (example: you lose your device). Bitwarden support will not be able to assist you if you lose access to your account. We recommend you write down or print the recovery code and keep it in a safe place." }, + "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." + }, "viewRecoveryCode": { "message": "View recovery code" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Manage" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Turn off" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Revoke access" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "This two-step login provider is active on your account." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "There was a problem reading the security key. Try again." }, - "twoFactorWebAuthnWarning": { - "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used. Supported platforms:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Web vault and browser extensions on a desktop/laptop with a WebAuthn supported browser (Chrome, Opera, Vivaldi, or Firefox with FIDO U2F turned on)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Your Bitwarden two-step login recovery code" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "If you cannot access your account through your normal two-step login methods, you can use your two-step login recovery code to turn off all two-step providers on your account." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Recover account two-step login" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Set requirements for master password strength." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Require two-step login" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Set requirements for password generator." }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." - }, "masterPasswordPolicyInEffect": { "message": "One or more organization policies require your master password to meet the following requirements:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Organization owners and admins are exempt from this policy's enforcement." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "File" }, "sendTypeText": { "message": "Text" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Delete Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Are you sure you want to delete this Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "What type of Send is this?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maximum access count" }, - "maxAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Disabled" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Are you sure you want to remove the password?" }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, - "disableThisSend": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "All Sends" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Pending deletion" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Expired" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "The following organization policies are currently in effect:" - }, - "sendDisableHideEmailInEffect": { - "message": "Users are not allowed 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." - }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Remove individual ownership for organization users" }, - "textHiddenByDefault": { - "message": "When accessing the Send, hide the text by default", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to Send." - }, - "sendFileDesc": { - "message": "The file you want to Send." - }, - "copySendLinkOnSave": { - "message": "Copy the link to share this Send to my clipboard upon save." - }, - "sendLinkLabel": { - "message": "Send link", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "To verify your 2FA please click the button below." }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn is not supported in this browser." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, - "regenerateUsername": { - "message": "Regenerate username" - }, "generateUsername": { "message": "Generate username" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Username type" - }, "plusAddressedEmail": { "message": "Plus addressed email", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Hostname", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API access token" - }, "deviceVerification": { "message": "Device verification" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Number of users" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "pickAnAvatarColor": { "message": "Pick an avatar color" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "No collection" }, - "canView": { - "message": "Can view" - }, - "canViewExceptPass": { - "message": "Can view, except passwords" - }, - "canEdit": { - "message": "Can edit" - }, - "canEditExceptPass": { - "message": "Can edit, except passwords" - }, "noCollectionsAdded": { "message": "No collections added" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domain" - }, "alreadyHaveAccount": { "message": "Already have an account?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index 36ab0050700..f334131b69b 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Critical applications" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "What type of item is this?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Notes" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Note" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Drag to sort" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Text" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Edit folder" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "Base domain", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "ex.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filter" }, - "moveSelectedToOrg": { - "message": "Move selected to organization" - }, "deleteSelected": { "message": "Delete selected" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "Log in or create a new account to access your secure vault." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Submit" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Lost access to all of your two-step login providers? Use your recovery code to turn off all two-step login providers from your account." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Migrated from FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "Email" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." }, - "moveManyToOrgDesc": { - "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved." - }, "collectionsDesc": { "message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Verification code (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Regenerate password" - }, "length": { "message": "Length" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Danger zone" }, - "dangerZoneDesc": { - "message": "Careful, these actions are not reversible!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Deauthorize sessions" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Proceeding will also log you out of your current session, requiring you to log back in. You will also be prompted for two-step login again, if set up. Active sessions on other devices may continue to remain active for up to one hour." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "All sessions deauthorized" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Setting up two-step login can permanently lock you out of your Bitwarden account. A recovery code allows you to access your account in the event that you can no longer use your normal two-step login provider (example: you lose your device). Bitwarden support will not be able to assist you if you lose access to your account. We recommend you write down or print the recovery code and keep it in a safe place." }, + "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." + }, "viewRecoveryCode": { "message": "View recovery code" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Manage" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Turn off" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Revoke access" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "This two-step login provider is active on your account." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "There was a problem reading the security key. Try again." }, - "twoFactorWebAuthnWarning": { - "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used. Supported platforms:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Web vault and browser extensions on a desktop/laptop with a WebAuthn supported browser (Chrome, Opera, Vivaldi, or Firefox with FIDO U2F turned on)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Your Bitwarden two-step login recovery code" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "If you cannot access your account through your normal two-step login methods, you can use your two-step login recovery code to turn off all two-step providers on your account." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Recover account two-step login" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Set requirements for master password strength." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Require two-step login" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Set requirements for password generator." }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." - }, "masterPasswordPolicyInEffect": { "message": "One or more organization policies require your master password to meet the following requirements:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Organization owners and admins are exempt from this policy's enforcement." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "File" }, "sendTypeText": { "message": "Text" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Delete Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Are you sure you want to delete this Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "What type of Send is this?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maximum access count" }, - "maxAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Disabled" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Are you sure you want to remove the password?" }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, - "disableThisSend": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "All Sends" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Pending deletion" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Expired" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "The following organization policies are currently in effect:" - }, - "sendDisableHideEmailInEffect": { - "message": "Users are not allowed 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." - }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Remove individual ownership for organization users" }, - "textHiddenByDefault": { - "message": "When accessing the Send, hide the text by default", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to Send." - }, - "sendFileDesc": { - "message": "The file you want to Send." - }, - "copySendLinkOnSave": { - "message": "Copy the link to share this Send to my clipboard upon save." - }, - "sendLinkLabel": { - "message": "Send link", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "To verify your 2FA please click the button below." }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn is not supported in this browser." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, - "regenerateUsername": { - "message": "Regenerate username" - }, "generateUsername": { "message": "Generate username" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Username type" - }, "plusAddressedEmail": { "message": "Plus addressed email", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Hostname", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API access token" - }, "deviceVerification": { "message": "Device verification" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Number of users" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "pickAnAvatarColor": { "message": "Pick an avatar color" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "No collection" }, - "canView": { - "message": "Can view" - }, - "canViewExceptPass": { - "message": "Can view, except passwords" - }, - "canEdit": { - "message": "Can edit" - }, - "canEditExceptPass": { - "message": "Can edit, except passwords" - }, "noCollectionsAdded": { "message": "No collections added" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domain" - }, "alreadyHaveAccount": { "message": "Already have an account?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index af26fc4df94..24285b8cb9f 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Critical applications" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -96,7 +99,7 @@ "message": "Apps marked as critical" }, "application": { - "message": "Application" + "message": "Program" }, "atRiskPasswords": { "message": "At-risk passwords" @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "Hva slags element er dette?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Notater" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Note" }, @@ -169,22 +214,22 @@ "message": "Kortholderens navn" }, "loginCredentials": { - "message": "Login credentials" + "message": "Legitimasjoner for innlogging" }, "personalDetails": { - "message": "Personal details" + "message": "Personlige detaljer" }, "identification": { - "message": "Identification" + "message": "Identifikasjon" }, "contactInfo": { - "message": "Contact info" + "message": "Kontaktinfo" }, "cardDetails": { - "message": "Card details" + "message": "Kortdetaljer" }, "cardBrandDetails": { - "message": "$BRAND$ details", + "message": "$BRAND$-detaljer", "placeholders": { "brand": { "content": "$1", @@ -193,19 +238,19 @@ } }, "itemHistory": { - "message": "Item history" + "message": "Gjenstandshistorikk" }, "authenticatorKey": { - "message": "Authenticator key" + "message": "Autentiseringsnøkkel" }, "autofillOptions": { - "message": "Autofill options" + "message": "Autoutfyllings-innstillinger" }, "websiteUri": { - "message": "Website (URI)" + "message": "Nettsted (URİ)" }, "websiteUriCount": { - "message": "Website (URI) $COUNT$", + "message": "Nettsted (URİ) $COUNT$", "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", "placeholders": { "count": { @@ -215,16 +260,16 @@ } }, "websiteAdded": { - "message": "Website added" + "message": "Nettsted lagt til" }, "addWebsite": { - "message": "Add website" + "message": "Legg til nettsted" }, "deleteWebsite": { - "message": "Delete website" + "message": "Slett nettsted" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "Standard ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -345,7 +390,7 @@ "message": "Dr․" }, "cardExpiredTitle": { - "message": "Expired card" + "message": "Utløpt kort" }, "cardExpiredMessage": { "message": "If you've renewed it, update the card's information" @@ -369,7 +414,7 @@ "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." }, "learnMoreAboutAuthenticators": { - "message": "Learn more about authenticators" + "message": "Lær mer om autentisering" }, "folder": { "message": "Mappe" @@ -383,6 +428,9 @@ "dragToSort": { "message": "Dra for å sortere" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Tekst" }, @@ -393,17 +441,17 @@ "message": "Boolsk verdi" }, "cfTypeCheckbox": { - "message": "Checkbox" + "message": "Avkryssingsboks" }, "cfTypeLinked": { "message": "Tilknyttet", "description": "This describes a field that is 'linked' (related) to another field." }, "fieldType": { - "message": "Field type" + "message": "Felttype" }, "fieldLabel": { - "message": "Field label" + "message": "Feltetikett" }, "remove": { "message": "Fjern" @@ -416,7 +464,7 @@ "description": "This is the folder for uncategorized items" }, "selfOwnershipLabel": { - "message": "You", + "message": "Du", "description": "Used as a label to indicate that the user is the owner of an item." }, "addFolder": { @@ -425,6 +473,31 @@ "editFolder": { "message": "Rediger mappen" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "Ny mappe" + }, + "folderName": { + "message": "Mappenavn" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "Grunndomene", "description": "Domain name. Example: website.com" @@ -469,7 +542,7 @@ "message": "Generer et passord" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "Generér passordfrase" }, "checkPassword": { "message": "Sjekk om passordet har blitt utsatt." @@ -572,7 +645,7 @@ "message": "Sikkert notat" }, "typeSshKey": { - "message": "SSH key" + "message": "SSH-nøkkel" }, "typeLoginPlural": { "message": "Innlogginger" @@ -605,7 +678,7 @@ "message": "Fullt navn" }, "address": { - "message": "Address" + "message": "Adresse" }, "address1": { "message": "Adresse 1" @@ -650,7 +723,7 @@ "message": "Vis elementet" }, "newItemHeader": { - "message": "New $TYPE$", + "message": "Ny $TYPE$", "placeholders": { "type": { "content": "$1", @@ -659,7 +732,7 @@ } }, "editItemHeader": { - "message": "Edit $TYPE$", + "message": "Rediger $TYPE$", "placeholders": { "type": { "content": "$1", @@ -684,19 +757,10 @@ "message": "Element" }, "itemDetails": { - "message": "Item details" + "message": "Gjenstandens detaljer" }, "itemName": { - "message": "Item name" - }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } + "message": "Gjenstandens navn" }, "ex": { "message": "f.eks.", @@ -722,7 +786,7 @@ } }, "copySuccessful": { - "message": "Copy Successful" + "message": "Kopiering lyktes" }, "copyValue": { "message": "Kopier verdien", @@ -737,7 +801,7 @@ "description": "Copy passphrase to clipboard" }, "passwordCopied": { - "message": "Password copied" + "message": "Passordet er kopiert" }, "copyUsername": { "message": "Kopier brukernavnet", @@ -756,7 +820,7 @@ "description": "Copy URI to clipboard" }, "copyCustomField": { - "message": "Copy $FIELD$", + "message": "Kopiér $FIELD$", "placeholders": { "field": { "content": "$1", @@ -765,34 +829,34 @@ } }, "copyWebsite": { - "message": "Copy website" + "message": "Kopiér nettsted" }, "copyNotes": { - "message": "Copy notes" + "message": "Kopiér notater" }, "copyAddress": { - "message": "Copy address" + "message": "Kopiér adresse" }, "copyPhone": { - "message": "Copy phone" + "message": "Kopiér telefonnummer" }, "copyEmail": { "message": "Copy email" }, "copyCompany": { - "message": "Copy company" + "message": "Kopiér firma" }, "copySSN": { - "message": "Copy Social Security number" + "message": "Kopiér fødselsnummer" }, "copyPassportNumber": { - "message": "Copy passport number" + "message": "Kopiér passnummer" }, "copyLicenseNumber": { - "message": "Copy license number" + "message": "Kopiér lisensnummer" }, "copyName": { - "message": "Copy name" + "message": "Kopiér navn" }, "me": { "message": "Meg" @@ -815,9 +879,6 @@ "filter": { "message": "Filter" }, - "moveSelectedToOrg": { - "message": "Flytt valgte til organisasjon" - }, "deleteSelected": { "message": "Slett de valgte" }, @@ -873,17 +934,8 @@ } } }, - "movedItemsToOrg": { - "message": "Valgte elementer flyttet til $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { - "message": "Items moved to $ORGNAME$", + "message": "Gjenstandene ble flyttet til $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -892,7 +944,7 @@ } }, "itemMovedToOrg": { - "message": "Item moved to $ORGNAME$", + "message": "Gjenstanden ble flyttet til $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -952,22 +1004,22 @@ "message": "Logget av" }, "loggedOutDesc": { - "message": "You have been logged out of your account." + "message": "Du har blitt logget ut av kontoen din." }, "loginExpired": { "message": "Din innloggingsøkt har utløpt." }, "restartRegistration": { - "message": "Restart registration" + "message": "Start registreringen på nytt" }, "expiredLink": { - "message": "Expired link" + "message": "Utløpt lenke" }, "pleaseRestartRegistrationOrTryLoggingIn": { "message": "Please restart registration or try logging in." }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "Du har kanskje allerede en konto" }, "logOutConfirmation": { "message": "Er du sikker på at du vil logge av?" @@ -984,6 +1036,9 @@ "no": { "message": "Nei" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "Logg på eller opprett en ny konto for å få tilgang til ditt sikre hvelv." }, @@ -994,7 +1049,7 @@ "message": "Logg på med enhet må settes opp i Bitwarden-innstillingene. Trenger du et annet alternativ?" }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "Trenger du et annet alternativ?" }, "loginWithMasterPassword": { "message": "Logg på med hovedpassord" @@ -1009,13 +1064,13 @@ "message": "Bruk en annen innloggingsmetode" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Logg inn med passnøkkel" }, "useSingleSignOn": { "message": "Use single sign-on" }, "welcomeBack": { - "message": "Welcome back" + "message": "Velkommen tilbake" }, "invalidPasskeyPleaseTryAgain": { "message": "Ugyldig Passkey. Vennligst prøv igjen." @@ -1099,10 +1154,10 @@ "message": "Opprett en konto" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Er du ny til Bitwarden?" }, "setAStrongPassword": { - "message": "Set a strong password" + "message": "Velg et sterkt passord" }, "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" @@ -1117,7 +1172,16 @@ "message": "Logg på" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Logg inn på Bitwarden" + }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" }, "authenticationTimeout": { "message": "Authentication timeout" @@ -1125,12 +1189,27 @@ "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Fortsett innloggingen" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Pålogging startet" }, + "logInRequestSent": { + "message": "Forespørsel sendt" + }, "submit": { "message": "Send inn" }, @@ -1184,23 +1263,17 @@ "message": "Innstillinger" }, "accountEmail": { - "message": "Account email" + "message": "Kontoens E-postadresse" }, "requestHint": { - "message": "Request hint" + "message": "Be om et hint" }, "requestPasswordHint": { - "message": "Request password hint" + "message": "Be om passordhint" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Passordhint" - }, - "enterEmailToGetHint": { - "message": "Skriv inn e-postadressen til kontoen din for å motta hintet til ditt hovedpassord." - }, "getMasterPasswordHint": { "message": "Få et hint om hovedpassordet" }, @@ -1233,10 +1306,10 @@ "message": "Din nye konto har blitt opprettet! Du kan nå logge på." }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "Den nye kontoen din er opprettet!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "Du har blitt logget inn!" }, "trialAccountCreated": { "message": "Kontoen ble opprettet." @@ -1254,10 +1327,10 @@ "message": "E-postadresse" }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "Hvelvet ditt er låst" }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "Kontoen din er låst" }, "uuid": { "message": "UUID" @@ -1320,11 +1393,38 @@ "notificationSentDevice": { "message": "Et varsel har blitt sendt til enheten din." }, - "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "nett-app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, + "aNotificationWasSentToYourDevice": { + "message": "Et varsel ble sendt til enheten din" }, "versionNumber": { "message": "Versjon $VERSION_NUMBER$", @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Husk på meg" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send E-posten med verifiseringskoden på nytt" }, "useAnotherTwoStepMethod": { "message": "Bruk en annen 2-trinnsinnloggingsmetode" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Sett inn din YubiKey i din datamaskins USB-uttak, og så trykk på dens knapp." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "2-trinnsinnloggingsalternativer" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Har du mistet tilgang til alle dine 2-trinnsleverandører? Bruk din gjenopprettingskode til å fjerne alle 2-trinnsleverandører fra din konto." }, @@ -1425,11 +1538,14 @@ "webAuthnMigrated": { "message": "(Migrert fra FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "E-post" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "Skriv inn koden du har fått tilsendt på E-post." }, "continue": { "message": "Fortsett" @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Velg en organisasjon som du ønsker å flytte dette elementet til. Flytting til en organisasjon overfører eierskap til den aktuelle organisasjonen. Du vil ikke lenger være den direkte eieren av dette elementet når det er flyttet." }, - "moveManyToOrgDesc": { - "message": "Velg en organisasjon som du ønsker å flytte dette elementet til. Flytting til en organisasjon overfører eierskap til den aktuelle organisasjonen. Du vil ikke lenger være den direkte eieren av dette elementet når det er flyttet." - }, "collectionsDesc": { "message": "Rediger samlingene som dette elementet blir delt med. Kun organisasjonsbrukere med tilgang til disse samlingene vil kunne se dette elementet." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "Du har valgt $COUNT$ element(er). $MOVEABLE_COUNT$ element(er) kan flyttes til en organisasjon, $NONMOVEABLE_COUNT$ kan ikke.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Verifiseringskode (TOTP)" }, @@ -1610,17 +1706,14 @@ "description": "deprecated. Use avoidAmbiguous instead." }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "Unngå forvekslingsbare tegn", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Regenerer passord" - }, "length": { "message": "Lengde" }, "passwordMinLength": { - "message": "Minimum password length" + "message": "Minimum passordlengde" }, "uppercase": { "message": "Store bokstaver (A-Z)", @@ -1658,10 +1751,10 @@ "message": "Passordhistorikk" }, "generatorHistory": { - "message": "Generator history" + "message": "Generatorhistorikk" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "Tøm generatorhistorikk" }, "cleargGeneratorHistoryDescription": { "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" @@ -1670,13 +1763,13 @@ "message": "Det er ingen passord å liste opp." }, "clearHistory": { - "message": "Clear history" + "message": "Tøm historikk" }, "nothingToShow": { - "message": "Nothing to show" + "message": "Ingenting å vise" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "Du har ikke generert noe i det siste" }, "clear": { "message": "Tøm", @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Vennligst logg på igjen." }, + "currentSession": { + "message": "Gjeldende økt" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Vennligst logg inn på nytt. Dersom du bruker andre Bitwarden-applikasjoner logg av og på på dem også." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Faresone" }, - "dangerZoneDesc": { - "message": "Vær forsiktig, disse handlingene kan ikke reverseres!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Fjern autorisering av økter" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Å fortsette vil også logge deg av din nåværende økt, og gjør at du vil måtte logge på igjen. Du vil også bli bedt om 2-trinnsinnlogging igjen, dersom det er aktivert. Aktive økter på andre enheter kan kanskje forbli aktive i opptil en time." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "Fjernet autoriseringen fra alle økter" }, @@ -1887,7 +2001,7 @@ "message": "Dataene har blitt vellykket importert inn i hvelvet ditt." }, "importSuccessNumberOfItems": { - "message": "A total of $AMOUNT$ items were imported.", + "message": "$AMOUNT$ gjenstander totalt ble importert.", "placeholders": { "amount": { "content": "$1", @@ -1917,16 +2031,16 @@ "message": "Feil under dekryptering av den eksporterte filen. Krypteringsnøkkelen samsvarte ikke med krypteringsnøkkelen som ble brukt eksport av data." }, "destination": { - "message": "Destination" + "message": "Destinasjon" }, "learnAboutImportOptions": { - "message": "Learn about your import options" + "message": "Lær mer om importalternativene dine" }, "selectImportFolder": { - "message": "Select a folder" + "message": "Velg en mappe" }, "selectImportCollection": { - "message": "Select a collection" + "message": "Velg en samling" }, "importTargetHint": { "message": "Select this option if you want the imported file contents moved to a $DESTINATION$", @@ -1939,7 +2053,7 @@ } }, "importUnassignedItemsError": { - "message": "File contains unassigned items." + "message": "Filen inneholder utildelte elementer." }, "selectFormat": { "message": "Velg formatet til importfilen" @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Å skru på 2-trinnsinnlogging kan låse deg permanent ut av din Bitwarden-konto. En gjenopprettingskode gir deg tilgang til kontoen din i det tilfellet at du ikke lenger kan bruke din vanlige 2-trinnsinnloggingsleverandør (f.eks. at du mister enheten din). Bitwarden-kundestøtten vil ikke kunne hjelpe deg dersom du mister tilgang til kontoen din. Vi anbefaler at du skriver ned eller skriver ut gjenopprettingskoden og legger den på en trygg plass." }, + "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." + }, "viewRecoveryCode": { "message": "Vis gjenopprettingskode" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Behandle" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Deaktiver" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Opphev tilgang" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "Denne 2-trinnsleverandøren er aktivert på din konto." }, @@ -2138,7 +2270,7 @@ "message": "You are leaving Bitwarden and launching an external website in a new window." }, "twoStepContinueToBitwardenUrlTitle": { - "message": "Continue to bitwarden.com?" + "message": "Vil du fortsette til 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." @@ -2153,7 +2285,7 @@ "message": "Nøkkel" }, "twoStepAuthenticatorEnterCodeV2": { - "message": "Verification code" + "message": "Verifiseringskode" }, "twoStepAuthenticatorReaddDesc": { "message": "Dersom du trenger å legge den til til en annen enhet, er QR-koden (eller -nøkkelen) som kreves av din autentiseringsapp nedenfor." @@ -2237,7 +2369,7 @@ "message": "Client Id" }, "twoFactorDuoClientSecret": { - "message": "Client Secret" + "message": "Klienthemmelighet" }, "twoFactorDuoApiHostname": { "message": "API-vertsnavn" @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "Det oppsto et problem med å lese sikkerhetsnøkkelen. Prøv igjen." }, - "twoFactorWebAuthnWarning": { - "message": "På grunn av plattformbegrensninger, kan ikke WebAuthn brukes på alle Bitwarden-apper. Du bør aktivere en annen 2-trinnsinnloggingsleverandør, slik at du kan få tilgang til kontoen din når WebAuthn ikke kan brukes. Støttede plattformer:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Netthvelv og nettleserutvidelser, på en datamaskin med en WebAuthn støttende nettleser (Chrome, Opera, Vivaldi, eller Firefox med FIDO U2F aktivert)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Din 2-trinnsinnloggingsgjenopprettingskode for Bitwarden" @@ -2881,7 +3010,7 @@ "message": "Ta kontakt med kundestøtte" }, "contactSupportShort": { - "message": "Contact Support" + "message": "Kontakt kundestøtte" }, "updatedPaymentMethod": { "message": "Oppdaterte betalingsmetoden." @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "Denne brukeren bruker 2-trinnsinnlogging til å beskytte kontoen sin." }, @@ -3342,7 +3477,7 @@ "message": "Netthvelv" }, "cli": { - "message": "CLI" + "message": "Ledetekst" }, "bitWebVault": { "message": "Bitwarden Web vault" @@ -3372,13 +3507,13 @@ "message": "Innloggingsforsøket mislyktes grunnet feil 2-trinnsinnlogging." }, "incorrectPassword": { - "message": "Incorrect password" + "message": "Feil passord" }, "incorrectCode": { - "message": "Incorrect code" + "message": "Feil kode" }, "incorrectPin": { - "message": "Incorrect PIN" + "message": "Feil PIN-kode" }, "pin": { "message": "PIN", @@ -3430,7 +3565,7 @@ } }, "viewAllLogInOptions": { - "message": "View all log in options" + "message": "Vis alle påloggingsalternativer" }, "viewAllLoginOptions": { "message": "Vis alle påloggingsalternativer" @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Koblet fra SSO for brukeren $ID$.", "placeholders": { @@ -3765,26 +3903,96 @@ "device": { "message": "Enhet" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Betrodd" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Prøver du å logge på?" + }, + "logInAttemptBy": { + "message": "Påloggingsforsøk av $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP-adresse" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Akkurat nå" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { - "message": "Creating account on" + "message": "Oppretter en konto på" }, "checkYourEmail": { - "message": "Check your email" + "message": "Sjekk E-postinnboksen din" }, "followTheLinkInTheEmailSentTo": { - "message": "Follow the link in the email sent to" + "message": "Følg lenken i E-postadressen som ble sendt til" }, "andContinueCreatingYourAccount": { "message": "and continue creating your account." }, "noEmail": { - "message": "No email?" + "message": "Ingen E-post?" }, "goBack": { - "message": "Go back" + "message": "Gå tilbake" }, "toEditYourEmailAddress": { - "message": "to edit your email address." + "message": "for å redigere E-postadressen din." }, "view": { "message": "Vis" @@ -3868,7 +4076,7 @@ "message": "Din E-postadresse har blitt bekreftet." }, "emailVerifiedV2": { - "message": "Email verified" + "message": "E-post bekreftet" }, "emailVerifiedFailed": { "message": "Klarte ikke å bekrefte E-postadressen din. Prøv å sende en ny bekreftelses-E-post." @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "Du bruker en ustøttet nettleser. Netthvelvet vil kanskje ikke fungere ordentlig." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "Hvis du ikke klarer å få tilgang til kontoen din gjennom dine vanlige 2-trinnsinnloggingsmetoder, kan du bruke din 2-trinnsinnloggingsgjenopprettingskode til å deaktivere alle 2-trinnsinnloggingsleverandører på din konto." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Få tilbake tilgangen til din kontos 2-trinnsinnlogging" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4325,10 +4594,10 @@ "message": "Unsubscribe" }, "atAnyTime": { - "message": "at any time." + "message": "når som helst." }, "byContinuingYouAgreeToThe": { - "message": "By continuing, you agree to the" + "message": "Ved å fortsette, samtykker du til" }, "and": { "message": "and" @@ -4352,7 +4621,7 @@ "message": "Pause for hvelvet" }, "vaultTimeout1": { - "message": "Timeout" + "message": "Tidsavbrudd" }, "vaultTimeoutDesc": { "message": "Velg når hvelvet ditt skal ta pause og utføre den valgte handlingen." @@ -4421,7 +4690,7 @@ "message": "Valgt" }, "recommended": { - "message": "Recommended" + "message": "Anbefalt" }, "ownership": { "message": "Eierskap" @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Sett minimumskrav til hovedpassordets styrke." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Krev 2-trinnsinnlogging" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Sett minimumskrav for konfigurasjon av passordgenerator." }, - "passwordGeneratorPolicyInEffect": { - "message": "En eller flere av organisasjonens vilkår påvirker generatorinnstillingene dine." - }, "masterPasswordPolicyInEffect": { "message": "En eller flere av organisasjonens vilkår krever hovedpassordet ditt for å oppfylle følgende krav:" }, @@ -4737,13 +5012,13 @@ "message": "Du kan nå lukke denne fanen og fortsette i utvidelsen." }, "youSuccessfullyLoggedIn": { - "message": "You successfully logged in" + "message": "Du har vellykket logget inn" }, "thisWindowWillCloseIn5Seconds": { "message": "This window will automatically close in 5 seconds" }, "youMayCloseThisWindow": { - "message": "You may close this window" + "message": "Du kan lukke dette vinduet" }, "includeAllTeamsFeatures": { "message": "Alle Lag funksjoner, plus:" @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Organisasjonens eiere og administratorer er unntatt fra denne policyens håndheving." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "Fil" }, "sendTypeText": { "message": "Tekst" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Lag ny Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Slett Send-en", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Er du sikker på at du vil slette denne Send-en?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "Hvilken type Send er dette?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Dato for sletting" }, - "deletionDateDesc": { - "message": "Send-en vil bli permanent slettet på angitt dato og klokkeslett.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maksimal antall tilganger" }, - "maxAccessCountDesc": { - "message": "Hvis satt, vil ikke brukere lenger ha tilgang til denne Send-en når maksimalt antall aksesseringer er nådd.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Antall nåværende tilganger" - }, - "sendPasswordDesc": { - "message": "Valgfritt passordkrav for å få tilgang til denne Send-en.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Private notater om denne Send-en.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Deaktivert" }, @@ -4908,7 +5192,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "copyLink": { - "message": "Copy link" + "message": "Kopier lenke" }, "copySendLink": { "message": "Kopier Send-lenke", @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Er du sikker på at du vil fjerne passordet?" }, - "hideEmail": { - "message": "Skjul min e-postadresse fra mottakere." - }, - "disableThisSend": { - "message": "Deaktiver denne Send-en slik at ingen får tilgang til den.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Alle Send-er" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Venter på sletting" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Utløpt" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "Følgende organisasjonspolicyer er for tiden i virkning:" - }, - "sendDisableHideEmailInEffect": { - "message": "Brukere kan ikke 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." - }, "modifiedPolicyId": { "message": "Modifisert policy $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Deaktiver personlig eierskap for organisasjonsbrukere" }, - "textHiddenByDefault": { - "message": "Når du åpner Send-en, er teksten skjult som standard", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Beskrivende navn for denne Send-en.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Teksten du ønsker å sende." - }, - "sendFileDesc": { - "message": "Filen du vil sende." - }, - "copySendLinkOnSave": { - "message": "Kopier lenken for å dele denne Send-en til utklippstavlen min ved lagring." - }, - "sendLinkLabel": { - "message": "Send lenke", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "Det oppstod en feil ved lagring av slettingen og utløpsdatoene." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "For å verifisere din 2FA vennligst klikk knappen nedenfor." }, "webAuthnAuthenticate": { "message": "Autentiser WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn støttes ikke i denne nettleseren." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Feil" }, + "decryptionError": { + "message": "Dekrypteringsfeil" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "for å unngå ytterligere datatap.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6319,7 +6594,7 @@ "message": "Rotasjon av faktureringssynkroniserings-token vil gjøre den forrige token ugyldig." }, "selfHostedServer": { - "message": "self-hosted" + "message": "selvbetjent" }, "customEnvironment": { "message": "Custom environment" @@ -6516,23 +6791,14 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "Hva vil du generere?" - }, - "passwordType": { - "message": "Passordtype" - }, - "regenerateUsername": { - "message": "Regenerer Brukernavn" - }, "generateUsername": { "message": "Generer brukernavn" }, "generateEmail": { - "message": "Generate email" + "message": "Generér E-post" }, "spinboxBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$.", + "message": "Verdien må være mellom $MIN$ og $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6546,7 +6812,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "message": " Bruk minst $RECOMMENDED$ tegn for å generere et sterkt passord.", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Brukernavntype" - }, "plusAddressedEmail": { "message": "Pluss-adressert e-post", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Bruk domenets konfigurerte catch-all innboks." }, + "useThisEmail": { + "message": "Bruk denne E-postadressen" + }, "random": { "message": "Vilkårlig", "description": "Generates domain-based username using random letters" @@ -6589,23 +6855,23 @@ "message": "Vilkårlig ord" }, "usernameGenerator": { - "message": "Username generator" + "message": "Brukernavngenerator" }, "useThisPassword": { - "message": "Use this password" + "message": "Bruk dette passordet" }, "useThisUsername": { - "message": "Use this username" + "message": "Bruk dette brukernavnet" }, "securePasswordGenerated": { "message": "Secure password generated! Don't forget to also update your password on the website." }, "useGeneratorHelpTextPartOne": { - "message": "Use the generator", + "message": "Bruk denne generatoren", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "useGeneratorHelpTextPartTwo": { - "message": "to create a strong unique password", + "message": "for å lage et sterkt og unikt passord", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "service": { @@ -6671,11 +6937,11 @@ "message": "Generer et e-postalias med en ekstern videresendingstjeneste." }, "forwarderDomainName": { - "message": "Email domain", + "message": "E-postdomene", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Choose a domain that is supported by the selected service", + "message": "Velg et domene som støttes av den valgte tjenesten", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { @@ -6693,11 +6959,11 @@ } }, "forwarderGeneratedBy": { - "message": "Generated by Bitwarden.", + "message": "Generert av Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "Nettsted: $WEBSITE$. Generert av Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -6707,7 +6973,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "Ugyldig $SERVICENAME$-API-sjetong", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -6717,7 +6983,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "Ugyldig $SERVICENAME$-API-sjetong: $ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6741,7 +7031,7 @@ } }, "forwarderNoDomain": { - "message": "Invalid $SERVICENAME$ domain.", + "message": "Ugyldig $SERVICENAME$-domene.", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -6761,7 +7051,7 @@ } }, "forwarderUnknownError": { - "message": "Unknown $SERVICENAME$ error occurred.", + "message": "Ukjent $SERVICENAME$-feil oppstod.", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -6784,9 +7074,6 @@ "message": "Vertsnavn", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API tilgangstoken" - }, "deviceVerification": { "message": "Enhetsverifisering" }, @@ -6903,7 +7190,7 @@ } }, "inputMinValue": { - "message": "Input value must be at least $MIN$.", + "message": "Inndataverdien må være minst $MIN$.", "placeholders": { "min": { "content": "$1", @@ -6912,7 +7199,7 @@ } }, "inputMaxValue": { - "message": "Input value must not exceed $MAX$.", + "message": "Inndataverdien kan ikke være mer enn $MAX$.", "placeholders": { "max": { "content": "$1", @@ -6942,10 +7229,10 @@ } }, "singleFieldNeedsAttention": { - "message": "1 field needs your attention." + "message": "1 felt trenger din oppmerksomhet." }, "multipleFieldsNeedAttention": { - "message": "$COUNT$ fields need your attention.", + "message": "$COUNT$ felter trenger din oppmerksomhet.", "placeholders": { "count": { "content": "$1", @@ -6962,8 +7249,14 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { - "message": "Launch Duo" + "message": "Start Duo" }, "turnOn": { "message": "Slå på" @@ -6972,7 +7265,7 @@ "message": "På" }, "off": { - "message": "Off" + "message": "Av" }, "members": { "message": "Medlemmer" @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Antall brukere" }, - "loggingInAs": { - "message": "Logger på som" - }, - "notYou": { - "message": "Ikke deg?" - }, "pickAnAvatarColor": { "message": "Velg en avatarfarge" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "Ingen samling" }, - "canView": { - "message": "Kan vise" - }, - "canViewExceptPass": { - "message": "Kan vise, bortsett fra passord" - }, - "canEdit": { - "message": "Kan redigere" - }, - "canEditExceptPass": { - "message": "Kan redigere, bortsett fra passord" - }, "noCollectionsAdded": { "message": "Ingen samlinger lagt til" }, @@ -7658,37 +7933,37 @@ "message": "Verification required for this action. Set a PIN to continue." }, "setPin": { - "message": "Set PIN" + "message": "Velg PIN" }, "verifyWithBiometrics": { - "message": "Verify with biometrics" + "message": "Bekreft med biometri" }, "awaitingConfirmation": { - "message": "Awaiting confirmation" + "message": "Avventer bekreftelse" }, "couldNotCompleteBiometrics": { - "message": "Could not complete biometrics." + "message": "Kunne ikke fullføre biometri." }, "needADifferentMethod": { - "message": "Need a different method?" + "message": "Trenger du en annen metode?" }, "useMasterPassword": { - "message": "Use master password" + "message": "Bruk hovedpassord" }, "usePin": { - "message": "Use PIN" + "message": "Bruk PIN-kode" }, "useBiometrics": { - "message": "Use biometrics" + "message": "Bruk biometri" }, "enterVerificationCodeSentToEmail": { - "message": "Enter the verification code that was sent to your email." + "message": "Skriv inn bekreftelseskoden som ble sendt til e-postadressen din." }, "resendCode": { - "message": "Resend code" + "message": "Send koden på nytt" }, "memberColumnHeader": { - "message": "Member" + "message": "Medlem" }, "groupSlashMemberColumnHeader": { "message": "Group/Member" @@ -7802,7 +8077,7 @@ "message": "Filopplasting" }, "upload": { - "message": "Upload" + "message": "Last opp" }, "acceptedFormats": { "message": "Aksepterte formater:" @@ -7814,13 +8089,13 @@ "message": "eller" }, "unlockWithBiometrics": { - "message": "Unlock with biometrics" + "message": "Lås opp med biometri" }, "unlockWithPin": { - "message": "Unlock with PIN" + "message": "Lås opp med PIN-kode" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "Lås opp med hovedpassord" }, "licenseAndBillingManagement": { "message": "Håndtering av lisens og fakturering" @@ -7916,10 +8191,10 @@ "message": "This user can access Secrets Manager" }, "important": { - "message": "Important:" + "message": "Viktig:" }, "viewAll": { - "message": "View all" + "message": "Vis alle" }, "showingPortionOfTotal": { "message": "Showing $PORTION$ of $TOTAL$", @@ -7935,10 +8210,10 @@ } }, "resolveTheErrorsBelowAndTryAgain": { - "message": "Resolve the errors below and try again." + "message": "Fiks feilene nedenfor og prøv igjen." }, "description": { - "message": "Description" + "message": "Beskrivelse" }, "errorReadingImportFile": { "message": "An error occurred when trying to read the import file" @@ -7957,7 +8232,7 @@ "description": "Software Development Kit" }, "createAnAccount": { - "message": "Create an account" + "message": "Opprett en konto" }, "createSecret": { "message": "Create a secret" @@ -7979,7 +8254,7 @@ "message": "Import secrets" }, "getStarted": { - "message": "Get started" + "message": "Kom i gang" }, "complete": { "message": "$COMPLETED$/$TOTAL$ Complete", @@ -8098,7 +8373,7 @@ "message": "Update KDF settings" }, "loginInitiated": { - "message": "Login initiated" + "message": "Innlogging igangsatt" }, "rememberThisDeviceToMakeFutureLoginsSeamless": { "message": "Remember this device to make future logins seamless" @@ -8107,25 +8382,22 @@ "message": "Device approval required. Select an approval option below:" }, "deviceApprovalRequiredV2": { - "message": "Device approval required" + "message": "Enhetsgodkjennelse kreves" }, "selectAnApprovalOptionBelow": { "message": "Select an approval option below" }, "rememberThisDevice": { - "message": "Remember this device" + "message": "Husk denne enheten" }, "uncheckIfPublicDevice": { "message": "Uncheck if using a public device" }, "approveFromYourOtherDevice": { - "message": "Approve from your other device" + "message": "Godkjenn fra en av dine andre enheter" }, "requestAdminApproval": { - "message": "Request admin approval" - }, - "approveWithMasterPassword": { - "message": "Approve with master password" + "message": "Be om administratorgodkjennelse" }, "trustedDeviceEncryption": { "message": "Trusted device encryption" @@ -8133,33 +8405,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8170,7 +8442,7 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "av $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -8188,7 +8460,7 @@ } }, "verificationRequired": { - "message": "Verification required", + "message": "Verifisering kreves", "description": "Default title for the user verification dialog." }, "recoverAccount": { @@ -8225,7 +8497,7 @@ "message": "Device info" }, "time": { - "message": "Time" + "message": "Tid" }, "denyAllRequests": { "message": "Deny all requests" @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8312,7 +8596,7 @@ } }, "next": { - "message": "Next" + "message": "Neste" }, "ssoLoginIsRequired": { "message": "SSO login is required" @@ -8327,16 +8611,13 @@ "message": "Admin approval requested" }, "adminApprovalRequestSentToAdmins": { - "message": "Your request has been sent to your admin." - }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." + "message": "Forespørselen din har blitt sendt til administratoren din." }, "troubleLoggingIn": { - "message": "Trouble logging in?" + "message": "Har du problemer med å logge inn?" }, "loginApproved": { - "message": "Login approved" + "message": "Innlogging godkjent" }, "userEmailMissing": { "message": "User email missing" @@ -8345,18 +8626,18 @@ "message": "Active user email not found. Logging you out." }, "deviceTrusted": { - "message": "Device trusted" + "message": "Enheten er betrodd" }, "sendsNoItemsTitle": { - "message": "No active Sends", + "message": "Ingen aktive Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "message": "Bruk Send til å dele kryptert informasjon med noen.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inviteUsers": { - "message": "Invite Users" + "message": "Inviter brukere" }, "secretsManagerForPlan": { "message": "Secrets Manager for $PLAN$", @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8476,7 +8760,7 @@ "message": "Max potential service account cost" }, "loggedInExclamation": { - "message": "Logged in!" + "message": "Innlogget!" }, "beta": { "message": "Beta" @@ -8488,32 +8772,29 @@ "message": "Edited collections" }, "baseUrl": { - "message": "Server URL" + "message": "Tjener-URL" }, "selfHostBaseUrl": { "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domain" - }, "alreadyHaveAccount": { - "message": "Already have an account?" + "message": "Har du allerede en konto?" }, "toggleSideNavigation": { "message": "Toggle side navigation" }, "skipToContent": { - "message": "Skip to content" + "message": "Hopp frem til innholdet" }, "managePermissionRequired": { "message": "At least one member or group must have can manage permission." }, "typePasskey": { - "message": "Passkey" + "message": "Passnøkkel" }, "passkeyNotCopied": { - "message": "Passkey will not be copied" + "message": "Passkoden vil ikke bli kopiert" }, "passkeyNotCopiedAlert": { "message": "The passkey will not be copied to the cloned item. Do you want to continue cloning this item?" @@ -8528,7 +8809,7 @@ } }, "seeDetailedInstructions": { - "message": "See detailed instructions on our help site at", + "message": "Se detaljerte instruksjoner på hjelpesidene våre på", "description": "This is followed a by a hyperlink to the help website." }, "installBrowserExtension": { @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -8577,7 +8858,7 @@ "message": "Service account access updated" }, "commonImportFormats": { - "message": "Common formats", + "message": "Vanlige formater", "description": "Label indicating the most common import formats" }, "maintainYourSubscription": { @@ -8654,7 +8935,7 @@ "message": "Provider Portal" }, "success": { - "message": "Success" + "message": "Suksess" }, "restrictedGroupAccess": { "message": "You cannot add yourself to groups." @@ -8663,10 +8944,10 @@ "message": "You cannot add yourself to collections." }, "assign": { - "message": "Assign" + "message": "Knytt" }, "assignToCollections": { - "message": "Assign to collections" + "message": "Legg til i samlinger" }, "assignToTheseCollections": { "message": "Assign to these collections" @@ -8678,7 +8959,7 @@ "message": "Only organization members with access to these collections will be able to see the items." }, "selectCollectionsToAssign": { - "message": "Select collections to assign" + "message": "Velg samlinger å tilordne" }, "noCollectionsAssigned": { "message": "No collections have been assigned" @@ -8700,25 +8981,25 @@ } }, "addField": { - "message": "Add field" + "message": "Legg til felt" }, "editField": { - "message": "Edit field" + "message": "Rediger felt" }, "items": { - "message": "Items" + "message": "Gjenstander" }, "assignedSeats": { "message": "Assigned seats" }, "assigned": { - "message": "Assigned" + "message": "Tildelt" }, "used": { - "message": "Used" + "message": "Brukt" }, "remaining": { - "message": "Remaining" + "message": "Gjenstår" }, "unlinkOrganization": { "message": "Unlink organization" @@ -8746,11 +9027,11 @@ "description": "A subscription status label." }, "pastDue": { - "message": "Past due", + "message": "Forbi måldatoen", "description": "A subscription status label" }, "subscriptionExpired": { - "message": "Subscription expired", + "message": "Abonnement har utløpt", "description": "The date header used when a subscription is past due." }, "pastDueWarningForChargeAutomatically": { @@ -9006,7 +9287,7 @@ "description": "The title for the section that deals with integrations and SDKs." }, "integrations": { - "message": "Integrations" + "message": "Integreringer" }, "integrationsDesc": { "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." @@ -9018,7 +9299,7 @@ "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, "ssoDescStart": { - "message": "Configure", + "message": "Sett opp", "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, "ssoDescEnd": { @@ -9032,7 +9313,7 @@ "message": "SCIM" }, "scimIntegrationDescStart": { - "message": "Configure ", + "message": "Sett opp ", "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, "scimIntegrationDescEnd": { @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9169,7 +9468,7 @@ "message": "Enter your Enterprise organization information" }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Vis gjenstander i $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -9179,7 +9478,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Tilbake til $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -9189,11 +9488,11 @@ } }, "back": { - "message": "Back", + "message": "Tilbake", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Fjern $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -9287,7 +9586,7 @@ "message": "Unverified" }, "verified": { - "message": "Verified" + "message": "Verifisert" }, "viewSecret": { "message": "View secret" @@ -9306,7 +9605,7 @@ "message": "Quickly view member access across the organization by upgrading to an Enterprise plan." }, "date": { - "message": "Date" + "message": "Dato" }, "exportClientReport": { "message": "Export client report" @@ -9333,13 +9632,13 @@ "message": "On" }, "memberAccessReportTwoFactorEnabledFalse": { - "message": "Off" + "message": "Av" }, "memberAccessReportAuthenticationEnabledTrue": { "message": "On" }, "memberAccessReportAuthenticationEnabledFalse": { - "message": "Off" + "message": "Av" }, "higherKDFIterations": { "message": "Higher KDF iterations can help protect your master password from being brute forced by an attacker." @@ -9384,7 +9683,7 @@ "message": "This action will remove your access to this secret." }, "invoice": { - "message": "Invoice" + "message": "Faktura" }, "unassignedSeatsAvailable": { "message": "You have $SEATS$ unassigned seats available.", @@ -9411,7 +9710,7 @@ "message": "Client details" }, "downloadCSV": { - "message": "Download CSV" + "message": "Last ned CSV" }, "monthlySubscriptionUserSeatsMessage": { "message": "Adjustments to your subscription will result in prorated charges to your billing totals on your next billing period. " @@ -9445,7 +9744,7 @@ "message": "Add to folder" }, "selectFolder": { - "message": "Select folder" + "message": "Velg mappe" }, "personalItemTransferWarningSingular": { "message": "1 item will be permanently transferred to the selected organization. You will no longer own this item." @@ -9497,13 +9796,13 @@ "message": "Project IDs" }, "projectId": { - "message": "Project ID" + "message": "Prosjekt-ID" }, "projectsAccessedByMachineAccount": { "message": "The following projects can be accessed by this machine account." }, "config": { - "message": "Config" + "message": "Oppsett" }, "learnMoreAboutEmergencyAccess": { "message": "Learn more about emergency access" @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,14 +9948,23 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Privat nøkkel" + }, + "sshPublicKey": { + "message": "Offentlig nøkkel" + }, + "sshFingerprint": { + "message": "Fingeravtrykk" + }, "sshKeyFingerprint": { - "message": "Fingerprint" + "message": "Fingeravtrykk" }, "sshKeyPrivateKey": { - "message": "Private key" + "message": "Privat nøkkel" }, "sshKeyPublicKey": { - "message": "Public key" + "message": "Offentlig nøkkel" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -9686,7 +10000,7 @@ "message": "Passwordless SSO" }, "accountRecovery": { - "message": "Account recovery" + "message": "Kontogjenoppretting" }, "customRoles": { "message": "Custom roles" @@ -9704,13 +10018,13 @@ "message": "Up to 20 machine accounts" }, "current": { - "message": "Current" + "message": "Nåværende" }, "secretsManagerSubscriptionInfo": { "message": "Your Secrets Manager subscription will upgrade based on the plan selected" }, "bitwardenPasswordManager": { - "message": "Bitwarden Password Manager" + "message": "Bitwarden passordbehandler" }, "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." @@ -9719,20 +10033,20 @@ "message": "File saved to device. Manage from your device downloads." }, "publicApi": { - "message": "Public API", + "message": "Offentlig API", "description": "The text, 'API', is an acronym and should not be translated." }, "showCharacterCount": { - "message": "Show character count" + "message": "Vis tegntelleren" }, "hideCharacterCount": { - "message": "Hide character count" + "message": "Skjul tegntelleren" }, "editAccess": { "message": "Edit access" }, "textHelpText": { - "message": "Use text fields for data like security questions" + "message": "Bruk tekstfelter for data som sikkerhetsspørsmål" }, "hiddenHelpText": { "message": "Use hidden fields for sensitive data like a password" @@ -9747,15 +10061,15 @@ "message": "Enter the the field's html id, name, aria-label, or placeholder." }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "Inkluder store bokstaver", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { - "message": "A-Z", + "message": "A-Å", "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "Inkluder små bokstaver", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -9763,7 +10077,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "Inkluder tall", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -9771,18 +10085,14 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "Inkluder spesialtegn", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { - "message": "Add attachment" + "message": "Legg til vedlegg" }, "maxFileSizeSansPunctuation": { - "message": "Maximum file size is 500 MB" + "message": "Maksimal filstørrelse er 500 MB" }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" @@ -9896,11 +10206,20 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { - "message": "Important notice" + "message": "Viktig melding" }, "setupTwoStepLogin": { - "message": "Set up two-step login" + "message": "Sett opp 2-trinnspålogging" }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." @@ -9909,7 +10228,7 @@ "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." }, "remindMeLater": { - "message": "Remind me later" + "message": "Minn meg på det senere" }, "newDeviceVerificationNoticePageOneFormContent": { "message": "Do you have reliable access to your email, $EMAIL$?", @@ -9927,14 +10246,23 @@ "message": "Yes, I can reliably access my email" }, "turnOnTwoStepLogin": { - "message": "Turn on two-step login" + "message": "Slå på 2-trinnsinnlogging" }, "changeAcctEmail": { - "message": "Change account email" + "message": "Endre kontoens E-postadresse" }, "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Enheter" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index e51fb3ced67..8183efd5f9b 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Critical applications" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "यो कस्तो प्रकारको वस्तु हो?" }, @@ -159,6 +201,9 @@ "notes": { "message": "नोटहरू" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Note" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "क्रमबद्ध गर्न तान्नुहोस्" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "पाठ" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Edit folder" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "Base domain", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "ex.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filter" }, - "moveSelectedToOrg": { - "message": "Move selected to organization" - }, "deleteSelected": { "message": "Delete selected" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "Log in or create a new account to access your secure vault." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Submit" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Lost access to all of your two-step login providers? Use your recovery code to turn off all two-step login providers from your account." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Migrated from FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "Email" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." }, - "moveManyToOrgDesc": { - "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved." - }, "collectionsDesc": { "message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Verification code (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Regenerate password" - }, "length": { "message": "Length" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Danger zone" }, - "dangerZoneDesc": { - "message": "Careful, these actions are not reversible!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Deauthorize sessions" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Proceeding will also log you out of your current session, requiring you to log back in. You will also be prompted for two-step login again, if set up. Active sessions on other devices may continue to remain active for up to one hour." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "All sessions deauthorized" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Setting up two-step login can permanently lock you out of your Bitwarden account. A recovery code allows you to access your account in the event that you can no longer use your normal two-step login provider (example: you lose your device). Bitwarden support will not be able to assist you if you lose access to your account. We recommend you write down or print the recovery code and keep it in a safe place." }, + "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." + }, "viewRecoveryCode": { "message": "View recovery code" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Manage" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Turn off" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Revoke access" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "This two-step login provider is active on your account." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "There was a problem reading the security key. Try again." }, - "twoFactorWebAuthnWarning": { - "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used. Supported platforms:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Web vault and browser extensions on a desktop/laptop with a WebAuthn supported browser (Chrome, Opera, Vivaldi, or Firefox with FIDO U2F turned on)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Your Bitwarden two-step login recovery code" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "If you cannot access your account through your normal two-step login methods, you can use your two-step login recovery code to turn off all two-step providers on your account." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Recover account two-step login" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Set requirements for master password strength." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Require two-step login" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Set requirements for password generator." }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." - }, "masterPasswordPolicyInEffect": { "message": "One or more organization policies require your master password to meet the following requirements:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Organization owners and admins are exempt from this policy's enforcement." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "File" }, "sendTypeText": { "message": "Text" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Delete Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Are you sure you want to delete this Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "What type of Send is this?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maximum access count" }, - "maxAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Disabled" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Are you sure you want to remove the password?" }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, - "disableThisSend": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "All Sends" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Pending deletion" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Expired" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "The following organization policies are currently in effect:" - }, - "sendDisableHideEmailInEffect": { - "message": "Users are not allowed 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." - }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Remove individual ownership for organization users" }, - "textHiddenByDefault": { - "message": "When accessing the Send, hide the text by default", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to Send." - }, - "sendFileDesc": { - "message": "The file you want to Send." - }, - "copySendLinkOnSave": { - "message": "Copy the link to share this Send to my clipboard upon save." - }, - "sendLinkLabel": { - "message": "Send link", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "To verify your 2FA please click the button below." }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn is not supported in this browser." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, - "regenerateUsername": { - "message": "Regenerate username" - }, "generateUsername": { "message": "Generate username" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Username type" - }, "plusAddressedEmail": { "message": "Plus addressed email", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Hostname", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API access token" - }, "deviceVerification": { "message": "Device verification" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Number of users" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "pickAnAvatarColor": { "message": "Pick an avatar color" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "No collection" }, - "canView": { - "message": "Can view" - }, - "canViewExceptPass": { - "message": "Can view, except passwords" - }, - "canEdit": { - "message": "Can edit" - }, - "canEditExceptPass": { - "message": "Can edit, except passwords" - }, "noCollectionsAdded": { "message": "No collections added" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domain" - }, "alreadyHaveAccount": { "message": "Already have an account?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index cd72a94251c..6b752b5a195 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Belangrijke applicaties" }, + "noCriticalAppsAtRisk": { + "message": "Geen belangrijke applicaties lopen risico" + }, "accessIntelligence": { "message": "Toegangsintelligentie" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "Leden in gevaar" }, + "atRiskMembersWithCount": { + "message": "Leden die risico lopen ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Applicaties die risico lopen ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "Deze leden loggen in op toepassingen met zwakke, blootgestelde of hergebruikte wachtwoorden." + }, + "atRiskApplicationsDescription": { + "message": "Deze applicaties hebben zwakke, blootgelegde of hergebruikte wachtwoorden." + }, + "atRiskMembersDescriptionWithApp": { + "message": "Deze leden loggen in op $APPNAME$ met zwakke, blootgestelde of hergebruikte wachtwoorden.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Totaal leden" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Totaal applicaties" }, + "unmarkAsCriticalApp": { + "message": "Markeren als belangrijke app ongedaan maken" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Markeren als belangrijke app ongedaan gemaakt" + }, "whatTypeOfItem": { "message": "Van welke categorie is dit item?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Notities" }, + "privateNote": { + "message": "Privénotitie" + }, "note": { "message": "Notitie" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Slepen om te sorteren" }, + "dragToReorder": { + "message": "Sleep om te herschikken" + }, "cfTypeText": { "message": "Tekst" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Map bewerken" }, + "editWithName": { + "message": "$ITEM$: $NAME$ bewerken", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "Nieuwe map" + }, + "folderName": { + "message": "Mapnaam" + }, + "folderHintText": { + "message": "Je kunt een map onderbrengen door het toevoegen van de naam van de bovenliggende map gevolgd door een \"/\". Voorbeeld: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Weet je zeker dat je deze map definitief wilt verwijderen?" + }, "baseDomain": { "message": "Basisdomein", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Itemnaam" }, - "cannotRemoveViewOnlyCollections": { - "message": "Je kunt verzamelingen niet verwijderen met alleen rechten voor weergeven: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "bijv.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filter" }, - "moveSelectedToOrg": { - "message": "Selectie naar organisatie verplaatsen" - }, "deleteSelected": { "message": "Selectie verwijderen" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Geselecteerde items verplaatst naar $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items verplaatst naar $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "Nee" }, + "location": { + "message": "Locatie" + }, "loginOrCreateNewAccount": { "message": "Log in of maak een nieuw account aan om toegang te krijgen tot je beveiligde kluis." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Inloggen op Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Voer de code in die naar je e-mailadres is verstuurd" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Voer de code uit je authenticatie-app in" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Druk op je YubiKey om te verifiëren" + }, "authenticationTimeout": { "message": "Authenticatie-timeout" }, "authenticationSessionTimedOut": { "message": "De verificatiesessie is verlopen. Start het inlogproces opnieuw op." }, - "verifyIdentity": { - "message": "Controleer je identiteit" + "verifyYourIdentity": { + "message": "Verifieer je identiteit" + }, + "weDontRecognizeThisDevice": { + "message": "We herkennen dit apparaat niet. Voer de code in die naar je e-mail is verzonden om je identiteit te verifiëren." + }, + "continueLoggingIn": { + "message": "Doorgaan met inloggen" + }, + "whatIsADevice": { + "message": "Wat is een apparaat?" + }, + "aDeviceIs": { + "message": "Een apparaat is een unieke installatie van de Bitwarden-app waar je bent ingelogd. Het opnieuw installeren, verwijderen van app-gegevens of het wissen van uw cookies kan zorgen voor het meerdere keren weergeven van dat apparaat." }, "logInInitiated": { "message": "Inloggen gestart" }, + "logInRequestSent": { + "message": "Verzoek verzonden" + }, "submit": { "message": "Versturen" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Als je het e-mailadres van je account invult, versturen we je je wachtwoordhint" }, - "passwordHint": { - "message": "Wachtwoordhint" - }, - "enterEmailToGetHint": { - "message": "Voer het e-mailadres van je account in om je hoofdwachtwoordhint te ontvangen." - }, "getMasterPasswordHint": { "message": "Hoofdwachtwoordhint opvragen" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "Er is een bericht naar je apparaat verstuurd." }, + "notificationSentDevicePart1": { + "message": "Ontgrendel Bitwarden op je apparaat of op de " + }, + "areYouTryingToAccessYourAccount": { + "message": "Probeer je toegang te krijgen tot je account?" + }, + "accessAttemptBy": { + "message": "Inlogpoging door $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Toegang bevestigen" + }, + "denyAccess": { + "message": "Toegang weigeren" + }, + "notificationSentDeviceAnchor": { + "message": "webapp" + }, + "notificationSentDevicePart2": { + "message": "Zorg ervoor dat de vingerafdrukzin overeenkomt met de onderstaande voor je deze goedkeurt." + }, + "notificationSentDeviceComplete": { + "message": "Ontgrendel Bitwarden op je apparaat. Zorg ervoor dat de vingerafdrukzin overeenkomt met de onderstaande voor je deze goedkeurt." + }, "aNotificationWasSentToYourDevice": { "message": "Er is een melding naar je apparaat verzonden" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Zorg ervoor dat je kluis is ontgrendeld en de vingerafdrukzin hetzelfde is op het andere apparaat" - }, "versionNumber": { "message": "Versie $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Mijn gegevens onthouden" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "30 dagen niet meer vragen op dit apparaat" + }, "sendVerificationCodeEmailAgain": { "message": "E-mail met verificatiecode opnieuw versturen" }, "useAnotherTwoStepMethod": { "message": "Gebruik een andere methode voor tweestapsaanmelding" }, + "selectAnotherMethod": { + "message": "Kies een andere methode", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Gebruik je herstelcode" + }, "insertYubiKey": { "message": "Plaats je YubiKey in de USB-poort van je computer en druk op de knop." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Opties voor tweestapsaanmelding" }, + "selectTwoStepLoginMethod": { + "message": "Kies methode voor tweestapsaanmelding" + }, "recoveryCodeDesc": { "message": "Ben je de toegang tot al je tweestapsaanbieders verloren? Gebruik dan je herstelcode om alle tweestapsaanbieders op je account uit te schakelen." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Gemigreerd van FIDO)" }, + "openInNewTab": { + "message": "Openen in nieuwe tab" + }, "emailTitle": { "message": "E-mailadres" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Kies een organisatie waarnaar je dit item wilt verplaatsen. Door het verplaatsen krijgt de organisatie de eigendomsrechten van het item. Je bent niet langer de directe eigenaar meer van het item als het is verplaatst." }, - "moveManyToOrgDesc": { - "message": "Kies een organisatie waarnaar je deze items wilt verplaatsen. Door het verplaatsen krijgt de organisatie de eigendomsrechten van deze items. Je bent niet langer de directe eigenaar meer van deze items als ze zijn verplaatst." - }, "collectionsDesc": { "message": "Wijzig de verzamelingen waarmee dit item gedeeld is. Alleen organisatiegebruikers met toegang tot deze verzamelingen kunnen dit item inzien." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "Je hebt $COUNT$ item(s) geselecteerd. Je kunt $MOVEABLE_COUNT$ verplaatsen naar een organisatie, $NONMOVEABLE_COUNT$ niet.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Verificatiecode (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Dubbelzinnige tekens vermijden", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Opnieuw genereren" - }, "length": { "message": "Lengte" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Log opnieuw in." }, + "currentSession": { + "message": "Huidige sessie" + }, + "requestPending": { + "message": "Verzoek in behandeling" + }, "logBackInOthersToo": { "message": "Svp opnieuw inloggen. Als je andere Bitwarden-applicaties gebruikt, dan moet je daar ook uit- en inloggen." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Gevarenzone" }, - "dangerZoneDesc": { - "message": "Waarschuwing - deze acties zijn niet terug te draaien!" - }, - "dangerZoneDescSingular": { - "message": "Waarschuwing - deze actie is niet terug te draaien!" - }, "deauthorizeSessions": { "message": "Sessie-autorisaties intrekken" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Doorgaan zal je huidige sessie uitloggen, waarna je opnieuw moet inloggen. Je moet ook je tweestapsaanmelding opnieuw doorlopen, als die is ingeschakeld. Actieve sessies op andere apparaten blijven mogelijk nog een uur actief." }, + "newDeviceLoginProtection": { + "message": "Nieuwe apparaat login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Inlogbescherming nieuwe apparaten uitschakelen" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Inlogbescherming nieuwe apparaten inschakelen" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Ga hieronder verder voor het uitschakelen van de verificatie e-mails die Bitwarden stuurt wanneer je inlogt vanaf een nieuw apparaat." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Ga hieronder verder om Bitwarden verificatie e-mails te sturen wanneer je inlogt vanaf een nieuw apparaat." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "Als je inlogbescherming voor nieuwe apparaten uitschakelt, kan iedereen op ieder apparaat met je hoofdwachtwoord inloggen. Stel tweestapsaanmelding in om je account te beschermen zonder e-mailverificatieberichten." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "Wijzigingen inlogbescherming nieuwe apparaten opgeslagen" + }, "sessionsDeauthorized": { "message": "Autorisatie van alle sessies ingetrokken" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Door aanmelden in twee stappen in te schakelen, kun je jezelf definitief buitensluiten van je Bitwarden-account. Een herstelcode geeft je toegang tot je account in het geval dat je je normale tweestapsaanmelding niet meer kunt gebruiken (bijv. als je je apparaat verliest). De Bitwarden-klantondersteuning kan je niet helpen als je de toegang tot je account verliest. We raden je met klem aan de herstelcode op te schrijven of af te drukken en op een veilige plaats te bewaren." }, + "yourSingleUseRecoveryCode": { + "message": "Met je herstelcode voor eenmalig gebruik kun je tweestapsaanmelding uitschakelen in het geval dat je toegang verliest tot je tweestapsaanmeldingsprovider. Bitwarden adviseert de herstelcode op te schrijven en op een veilige plaats te bewaren." + }, "viewRecoveryCode": { "message": "Herstelcode weergeven" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Beheren" }, - "canManage": { - "message": "Kan beheren" + "manageCollection": { + "message": "Collectie beheren" + }, + "viewItems": { + "message": "Items bekijken" + }, + "viewItemsHidePass": { + "message": "Items bekijken, verborgen wachtwoorden" + }, + "editItems": { + "message": "Items bewerken" + }, + "editItemsHidePass": { + "message": "Items bewerken, verborgen wachtwoorden" }, "disable": { "message": "Uitschakelen" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Toegang intrekken" }, + "revoke": { + "message": "Intrekken" + }, "twoStepLoginProviderEnabled": { "message": "Deze tweestapsaanmeldingsaanbieder is geactiveerd voor je account." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "Er was een probleem met het lezen van de beveiligingssleutel. Probeer het nogmaals." }, - "twoFactorWebAuthnWarning": { - "message": "Vanwege platformbeperkingen kan WebAuthn niet in alle Bitwarden applicaties gebruikt worden. Stel een andere tweestapsaanmeldingsaanbieder in zodat je je account kunt benaderen wanneer WebAuthn niet beschikbaar is. De volgende platformen worden ondersteund:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Webkluis en browser-extensies op een desktop/laptop met een browser met ondersteuning voor WebAuthn (Chrome, Opera, Vivaldi of Firefox met FIDO U2F ingeschakeld)." + "twoFactorWebAuthnWarning1": { + "message": "Vanwege platformbeperkingen kan WebAuthn niet in alle Bitwarden-applicaties gebruikt worden. Stel een andere tweestapsaanmeldingsaanbieder in zodat je je account kunt benaderen wanneer WebAuthn niet beschikbaar is." }, "twoFactorRecoveryYourCode": { "message": "Je herstelcode voor Bitwarden-tweestapsaanmelding" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Je hebt 1 uitnodiging over." + }, + "inviteZeroEmailDesc": { + "message": "Je hebt 0 uitnodigingen over." + }, "userUsingTwoStep": { "message": "Het account van deze gebruiker is beschermd met tweestapsaanmelding." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Ontkoppelde SSO." + }, "unlinkedSsoUser": { "message": "SSO ontkoppeld voor gebruiker $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Apparaat" }, + "loginStatus": { + "message": "Loginstatus" + }, + "firstLogin": { + "message": "Eerst inloggen" + }, + "trusted": { + "message": "Vertrouwd" + }, + "needsApproval": { + "message": "Heeft goedkeuring nodig" + }, + "areYouTryingtoLogin": { + "message": "Probeer je in te loggen?" + }, + "logInAttemptBy": { + "message": "Inlogpoging door $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Apparaattype" + }, + "ipAddress": { + "message": "IP-adres" + }, + "confirmLogIn": { + "message": "Inloggen bevestigen" + }, + "denyLogIn": { + "message": "Inloggen afwijzen" + }, + "thisRequestIsNoLongerValid": { + "message": "Dit verzoek is niet langer geldig." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Inloggen voor $EMAIL$ bevestigd op $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "Je hebt een inlogpoging vanaf een ander apparaat geweigerd. Als je dit toch echt zelf was, probeer dan opnieuw in te loggen met het apparaat." + }, + "loginRequestHasAlreadyExpired": { + "message": "Inlogverzoek is al verlopen." + }, + "justNow": { + "message": "Zojuist" + }, + "requestedXMinutesAgo": { + "message": "$MINUTES$ minuten geleden aangevraagd", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Account maken bij" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "Je maakt gebruik van webbrowser die we niet ondersteunen. De webkluis werkt mogelijk niet goed." }, + "youHaveAPendingLoginRequest": { + "message": "Je hebt inlogverzoek van een ander apparaat." + }, + "reviewLoginRequest": { + "message": "Inlogverzoek afhandelen" + }, "freeTrialEndPromptCount": { "message": "Je gratis proefperiode eindigt over $COUNT$ dagen.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "Als je geen toegang tot je account kunt krijgen via je normale tweestapsaanmeldingsmethodes, kun je met je herstelcode alle aanbieders van tweestapsaanmelding in je account uitschakelen." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log hieronder in met behulp van je eenmalige herstelcode. Dit zal alle tweestapsaanbieders op je account uitschakelen." + }, "recoverAccountTwoStep": { "message": "Tweestapsaanmelding herstellen" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Bijwerken encryptiesleutel kan niet verder gaan" }, + "editFieldLabel": { + "message": "$LABEL$ bewerken", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "$LABEL$ herschikken. Gebruik de pijltjestoets om het item omhoog of omlaag te verplaatsen.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ is naar boven verplaatst, positie $INDEX$ van $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ is naar boven verplaatst, positie $INDEX$ van $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "Bij het bijwerken van je encryptiesleutel konden we je mappen niet decoderen. Om door te gaan met de update, moeten je mappen worden verwijderd. Geen kluisitems worden verwijderd als je doorgaat." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Minimale eisen voor de sterkte van het hoofdwachtwoord instellen." }, + "passwordStrengthScore": { + "message": "Score wachtwoordsterkte $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Tweestapsaanmelding vereisen" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Stel minimale vereisten in voor de configuratie van het wachtwoord generator." }, - "passwordGeneratorPolicyInEffect": { - "message": "Een of meer organisatiebeleidseisen heeft invloed op de instellingen van je generator." - }, "masterPasswordPolicyInEffect": { "message": "Een of meer organisatiebeleidseisen stelt de volgende eisen aan je hoofdwachtwoord:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Eigenaren en beheerders van de organisatie zijn vrijgesteld van de handhaving van dit beleid." }, + "limitSendViews": { + "message": "Weergaven limiteren" + }, + "limitSendViewsHint": { + "message": "Niemand kan deze Send weergeven als de limiet is bereikt.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ weergaven over", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send-details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Te delen tekst" + }, "sendTypeFile": { "message": "Bestand" }, "sendTypeText": { "message": "Tekst" }, + "sendPasswordDescV3": { + "message": "Voeg een optioneel wachtwoord toe voor ontvangers om toegang te krijgen tot deze Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Nieuwe Send aanmaken", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Send verwijderen", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Weet je zeker dat je deze Send wilt verwijderen?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "Wat voor soort Send is dit?", + "deleteSendPermanentConfirmation": { + "message": "Weet je zeker dat je deze Send permanent wil verwijderen?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Verwijderingsdatum" }, - "deletionDateDesc": { - "message": "Deze Send wordt definitief verwijderd op de aangegeven datum en tijd.", + "deletionDateDescV2": { + "message": "Op deze datum wordt de Send definitief verwijderd.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maximum toegangsaantal" }, - "maxAccessCountDesc": { - "message": "Als dit is ingesteld kunnen gebruikers deze Send niet meer benaderen zodra het maximale aantal toegang is bereikt.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Huidige toegangsaantal" - }, - "sendPasswordDesc": { - "message": "Vereis optioneel een wachtwoord voor gebruikers om toegang te krijgen tot deze Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Privénotities over deze Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Uitgeschakeld" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Weet je zeker dat je dit wachtwoord wilt verwijderen?" }, - "hideEmail": { - "message": "Verberg mijn e-mailadres voor ontvangers." - }, - "disableThisSend": { - "message": "Schakel deze Send uit zodat niemand hem kan benaderen.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Alle Sends" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Wordt verwijderd" }, + "hideTextByDefault": { + "message": "Tekst standaard verbergen" + }, "expired": { "message": "Verlopen" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "Het volgende organisatiebeleid is momenteel van toepassing:" - }, - "sendDisableHideEmailInEffect": { - "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." - }, "modifiedPolicyId": { "message": "Bewerkt beleid $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Schakel persoonlijke eigendom uit voor organisatiegebruikers" }, - "textHiddenByDefault": { - "message": "Verberg de tekst standaard bij het gebruiken van de Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Een vriendelijke naam om deze Send te beschrijven.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "De tekst die je wilt versturen." - }, - "sendFileDesc": { - "message": "Het bestand dat je wilt versturen." - }, - "copySendLinkOnSave": { - "message": "Kopieer de link om deze Send te delen bij opslaan naar mijn klembord." - }, - "sendLinkLabel": { - "message": "Send-link", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "Er is een fout opgetreden bij het opslaan van je verwijder- en vervaldatum." }, + "hideYourEmail": { + "message": "Je e-mailadres voor ontvangers verbergen." + }, "webAuthnFallbackMsg": { "message": "Klik op onderstaande knop om je 2FA te verifiëren." }, "webAuthnAuthenticate": { "message": "Authenticeer WebAuthn" }, + "readSecurityKey": { + "message": "Beveiligingssleutel lezen" + }, + "awaitingSecurityKeyInteraction": { + "message": "Wacht op interactie met beveiligingssleutel..." + }, "webAuthnNotSupported": { "message": "WebAuthn wordt niet ondersteund in deze browser." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Fout" }, + "decryptionError": { + "message": "Ontsleutelingsfout" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden kon de onderstaande kluisitem(s) niet ontsleutelen." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Neem contact op met de klantenservice", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "om extra dataverlies te voorkomen.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Gebruikers beheren moet je ook accountherstel beheren toekennen" }, @@ -6516,15 +6791,6 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "Wat wil je genereren?" - }, - "passwordType": { - "message": "Type wachtwoord" - }, - "regenerateUsername": { - "message": "Gebruikersnaam opnieuw genereren" - }, "generateUsername": { "message": "Gebruikersnaam genereren" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Type gebruikersnaam" - }, "plusAddressedEmail": { "message": "E-mailadres-met-plus", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Gebruik de catch-all inbox van je domein." }, + "useThisEmail": { + "message": "Dit e-mailadres gebruiken" + }, "random": { "message": "Willekeurig", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ heeft je aanvraag geweigerd. Neem contact op met je serviceprovider voor hulp.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ heeft je aanvraag geweigerd: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Kan $SERVICENAME$ gemaskeerde e-mailaccount-ID niet verkrijgen.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Hostnaam", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API-toegangstoken" - }, "deviceVerification": { "message": "Apparaatverificatie" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "DUO tweestapsaanmelding is vereist voor jouw account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Jouw account vereist Duo-tweestapsaanmelding. Volg de onderstaande stappen om het inloggen te voltooien." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Volg de onderstaande stappen om in te loggen." + }, "launchDuo": { "message": "DUO starten" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Aantal gebruikers" }, - "loggingInAs": { - "message": "Inloggen als" - }, - "notYou": { - "message": "Ben jij dit niet?" - }, "pickAnAvatarColor": { "message": "Kies een kleur voor je avatar" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "Geen collectie" }, - "canView": { - "message": "Kan bekijken" - }, - "canViewExceptPass": { - "message": "Kan bekijken, behalve wachtwoorden" - }, - "canEdit": { - "message": "Kan bewerken" - }, - "canEditExceptPass": { - "message": "Kan bewerken, behalve wachtwoorden" - }, "noCollectionsAdded": { "message": "Geen collecties toegevoegd" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Goedkeuring van beheerder vragen" }, - "approveWithMasterPassword": { - "message": "Goedkeuren met hoofdwachtwoord" - }, "trustedDeviceEncryption": { "message": "Vertrouwde apparaat encryptie" }, "trustedDevices": { "message": "Vertrouwde apparaten" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Eenmaal ingelogd, ontsleutelen leden kluisgegevens met een op hun apparaat opgeslagen sleutel. Het", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Leden hebben geen hoofdwachtwoord nodig bij het inloggen met SSO. Het hoofdwachtwoord wordt vervangen door een encryptiesleutel die is opgeslagen op het apparaat, waardoor het apparaat is vertrouwd. Het eerste apparaat waarop een lid zijn/haar account aanmaakt en mee inlogt wordt vertrouwd. Nieuwe apparaten moeten worden goedgekeurd door een bestaand vertrouwde apparaat of door een beheerder. De", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "enkele organisatie", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "beleid,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO vereist", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "beleid en", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "accountherstel-administratie", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "beleid met automatische inschrijving wordt ingeschakeld wanneer je deze optie gebruikt.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "beleid zal worden ingeschakeld wanneer deze optie wordt gebruikt.", + "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": "De organisatierechten zijn bijgewerkt, je moet een hoofdwachtwoord instellen.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Aanvraag goedkeuren" }, + "deviceApproved": { + "message": "Apparaat goedgekeurd" + }, + "deviceRemoved": { + "message": "Apparaat verwijderd" + }, + "removeDevice": { + "message": "Apparaat verwijderen" + }, + "removeDeviceConfirmation": { + "message": "Weet je zeker dat je dit apparaat wilt verwijderen?" + }, "noDeviceRequests": { "message": "Geen apparaatverzoeken" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Je verzoek is naar je beheerder verstuurd." }, - "youWillBeNotifiedOnceApproved": { - "message": "Je krijgt een melding zodra je bent goedgekeurd." - }, "troubleLoggingIn": { "message": "Problemen met inloggen?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Verwijderen van collecties beperken tot eigenaren en managers" }, + "limitItemDeletionDesc": { + "message": "Beperk het verwijderen van items tot leden met het recht Kan beheren" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Eigenaren en beheerders kunnen alle collecties en items beheren" }, @@ -8494,9 +8778,6 @@ "message": "URL zelfgehoste server", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Aliasdomein" - }, "alreadyHaveAccount": { "message": "Heb je al een account?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "Je hebt geen toegang om deze collectie te beheren." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Ontbrekende Kan beheren machtigingen" + "grantManageCollectionWarningTitle": { + "message": "Recht voor het beheren van machtigingen ontbreekt" }, - "grantAddAccessCollectionWarning": { - "message": "Kan beheren machtigingen verlenen voor volledig verzamelingsbeheer, inclusief het verwijderen van verzamelingen." + "grantManageCollectionWarning": { + "message": "Beheren van machtigingen toekennen voor het toestaan van het volledig verzamelingenbeheer, inclusief het verwijderen van een verzameling." }, "grantCollectionAccess": { "message": "Groepen of mensen toegang tot deze collectie geven." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Apparaatbeheer voor Bitwarden configureren met behulp van de implementatiehandleiding voor jouw platform." }, + "deviceIdMissing": { + "message": "Apparaat-ID ontbreekt" + }, + "deviceTypeMissing": { + "message": "Apparaat-type ontbreekt" + }, + "deviceCreationDateMissing": { + "message": "Aanmaakdatum apparaat ontbreekt" + }, + "desktopRequired": { + "message": "Desktop vereist" + }, + "reopenLinkOnDesktop": { + "message": "Open deze link opnieuw vanaf je e-mail op een desktop." + }, "integrationCardTooltip": { "message": "$INTEGRATION$ implementatiehandleiding openen.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "maand per lid" }, + "monthPerMemberBilledAnnually": { + "message": "maand per lid jaarlijks gefactureerd" + }, "seats": { "message": "Personen" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Lees meer over Bitwarden's API" }, + "fileSend": { + "message": "Bestand Send" + }, "fileSends": { "message": "Bestand-Sends" }, + "textSend": { + "message": "Tekst Send" + }, "textSends": { "message": "Tekst-Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Sleutelalgoritme" }, + "sshPrivateKey": { + "message": "Privé sleutel" + }, + "sshPublicKey": { + "message": "Publieke sleutel" + }, + "sshFingerprint": { + "message": "Vingerafdruk" + }, "sshKeyFingerprint": { "message": "Vingerafdruk" }, @@ -9774,10 +10088,6 @@ "message": "Speciale tekens toevoegen", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Bijlage toevoegen" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Code bankafschrift" }, + "cannotRemoveViewOnlyCollections": { + "message": "Je kunt verzamelingen niet verwijderen met alleen rechten voor weergeven: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Belangrijke mededeling" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Leden verwijderen" }, + "devices": { + "message": "Apparaten" + }, + "deviceListDescription": { + "message": "Je account is op elk van de onderstaande apparaten aangemeld. Als je een apparaat niet herkent, verwijder het nu." + }, + "deviceListDescriptionTemp": { + "message": "Je account is op elk van de onderstaande apparaten aangemeld." + }, "claimedDomains": { "message": "Geverifieerde domeinen" }, @@ -10020,7 +10348,69 @@ "organizationNameMaxLength": { "message": "Organisatienaam mag niet langer zijn dan 50 tekens." }, - "resellerRenewalWarning": { + "sshKeyWrongPassword": { + "message": "Het door jou ingevoerde wachtwoord is onjuist." + }, + "importSshKey": { + "message": "Importeren" + }, + "confirmSshKeyPassword": { + "message": "Wachtwoord bevestigen" + }, + "enterSshKeyPasswordDesc": { + "message": "Voer het wachtwoord voor de SSH sleutel in." + }, + "enterSshKeyPassword": { + "message": "Wachtwoord invoeren" + }, + "invalidSshKey": { + "message": "De SSH-sleutel is ongeldig" + }, + "sshKeyTypeUnsupported": { + "message": "Het type SSH-sleutel is niet ondersteund" + }, + "importSshKeyFromClipboard": { + "message": "Sleutel van klembord importeren" + }, + "sshKeyImported": { + "message": "SSH-sleutel succesvol geïmporteerd" + }, + "copySSHPrivateKey": { + "message": "Privésleutel kopiëren" + }, + "openingExtension": { + "message": "Bitwarden-browserextensie openen" + }, + "somethingWentWrong": { + "message": "Er is iets fout gegaan…" + }, + "openingExtensionError": { + "message": "We konden de Bitwarden-browserextensie niet openen. Klik op de knop om deze nu te openen." + }, + "openExtension": { + "message": "Extensie openen" + }, + "doNotHaveExtension": { + "message": "Heb je de Bitwarden-browserextensie niet?" + }, + "installExtension": { + "message": "Extensie installeren" + }, + "openedExtension": { + "message": "Browserextensie geopend" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "De Bitwarden-browserextensie is geopend. Je kunt nu je risicovolle wachtwoorden bekijken." + }, + "openExtensionManuallyPart1": { + "message": "We konden de Bitwarden-browserextensie niet openen. Open het Bitwarden-pictogram", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "vanaf de werkbalk.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { "message": "Je abonnement wordt binnenkort verlengd. Neem voor $RENEWAL_DATE$ contact op met $RESELLER$ om je verlenging te bevestigen en een ononderbroken service te verzekeren.", "placeholders": { "reseller": { @@ -10033,7 +10423,7 @@ } } }, - "resellerOpenInvoiceWarning": { + "resellerOpenInvoiceWarningMgs": { "message": "Er is een factuur voor je abonnement aangemaakt op $ISSUED_DATE$. Neem contact op met $RESELLER$ voor $DUE_DATE$ om je verlenging te bevestigen en een ononderbroken service te verzekeren.", "placeholders": { "reseller": { @@ -10050,7 +10440,7 @@ } } }, - "resellerPastDueWarning": { + "resellerPastDueWarningMsg": { "message": "De factuur voor je abonnement is niet betaald. Neem contact op met $RESELLER$ voor $GRACE_PERIOD_END$ om je verlenging te bevestigen en een ononderbroken service te verzekeren.", "placeholders": { "reseller": { @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organisatieabonnement hervat" + }, + "restartSubscription": { + "message": "Abonnement hervatten" + }, + "suspendedManagedOrgMessage": { + "message": "Neem contact op met $PROVIDER$ voor hulp.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Beheerders hebben nu de mogelijkheid om leden van een geclaimd domein te verwijderen." + }, + "deleteManagedUserWarningDesc": { + "message": "Deze actie verwijdert het account van het lid, inclusief alle items in hun kluis. Dit vervangt de vorige verwijderactie." + }, + "deleteManagedUserWarning": { + "message": "Verwijderen is een nieuwe actie!" + }, + "seatsRemaining": { + "message": "Je hebt nog $REMAINING$ plaatsen van de $TOTAL$ plaatsen die aan deze organisatie zijn toegewezen. Neem contact op met je provider om je abonnement te beheren.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Bestaande organisatie" + }, + "selectOrganizationProviderPortal": { + "message": "Kies een organisatie om aan je providerportaal toe te voegen." + }, + "noOrganizations": { + "message": "Er zijn geen organisaties om weer te geven" + }, + "yourProviderSubscriptionCredit": { + "message": "Je providerabonnement zal een credit ontvangen voor de resterende tijd van het abonnement van de organisatie." + }, + "doYouWantToAddThisOrg": { + "message": "Wilt je deze organisatie toevoegen aan $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Bestaande organisatie toegevoegd" + }, + "assignedExceedsAvailable": { + "message": "Meer toegewezen dan beschikbare plaatsen." + }, + "changeAtRiskPassword": { + "message": "Risicovol wachtwoord wijzigen" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Ontgrendelen met PIN verwijderen" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Leden niet toestaan hun account te ontgrendelen met een pincode." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$-abonnementen hebben geen toegang tot echte event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Krijg volledige toegang tot event logs van de organisatie door te upgraden naar een Teams- of Enterprise-abonnement." + }, + "upgradeEventLogTitle": { + "message": "Upgrade voor echte event log gegevens" + }, + "upgradeEventLogMessage": { + "message": "Deze evenementen zijn alleen voorbeelden en weerspiegelen geen echte evenementen binnen je Bitwarden organisatie." + }, + "cannotCreateCollection": { + "message": "Gratis organisaties kunnen maximaal twee collecties hebben. Upgrade naar een betaald abonnement voor het toevoegen van meer collecties." } } diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index 0e3c134ba4d..23d4d3b3089 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Critical applications" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "Kva type oppføring er dette?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Notat" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Note" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Dra for å sortera" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Tekst" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Rediger mappe" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "Grunndomene", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "t.d.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filter" }, - "moveSelectedToOrg": { - "message": "Flytt markerte til organisasjon" - }, "deleteSelected": { "message": "Slett markerte" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Dei valde oppføringane vart flytta til $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "Log in or create a new account to access your secure vault." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Submit" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Passordvink" - }, - "enterEmailToGetHint": { - "message": "Skriv inn e-postadressa til kontoen din for å få vinket til hovudpassordet ditt." - }, "getMasterPasswordHint": { "message": "Få vink om hovudpassordet ditt" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Hugsa meg" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Bruk ein annan tofaktormetode for pålogging" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Lost access to all of your two-step login providers? Use your recovery code to turn off all two-step login providers from your account." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Migrated from FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "Email" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." }, - "moveManyToOrgDesc": { - "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved." - }, "collectionsDesc": { "message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Verification code (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Regenerate password" - }, "length": { "message": "Length" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Danger zone" }, - "dangerZoneDesc": { - "message": "Careful, these actions are not reversible!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Deauthorize sessions" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Proceeding will also log you out of your current session, requiring you to log back in. You will also be prompted for two-step login again, if set up. Active sessions on other devices may continue to remain active for up to one hour." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "All sessions deauthorized" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Setting up two-step login can permanently lock you out of your Bitwarden account. A recovery code allows you to access your account in the event that you can no longer use your normal two-step login provider (example: you lose your device). Bitwarden support will not be able to assist you if you lose access to your account. We recommend you write down or print the recovery code and keep it in a safe place." }, + "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." + }, "viewRecoveryCode": { "message": "View recovery code" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Manage" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Slå av" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Revoke access" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "This two-step login provider is active on your account." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "There was a problem reading the security key. Try again." }, - "twoFactorWebAuthnWarning": { - "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used. Supported platforms:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Web vault and browser extensions on a desktop/laptop with a WebAuthn supported browser (Chrome, Opera, Vivaldi, or Firefox with FIDO U2F turned on)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Your Bitwarden two-step login recovery code" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Eining" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "If you cannot access your account through your normal two-step login methods, you can use your two-step login recovery code to turn off all two-step providers on your account." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Recover account two-step login" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Set requirements for master password strength." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Require two-step login" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Set requirements for password generator." }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." - }, "masterPasswordPolicyInEffect": { "message": "One or more organization policies require your master password to meet the following requirements:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Organization owners and admins are exempt from this policy's enforcement." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "File" }, "sendTypeText": { "message": "Text" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Delete Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Are you sure you want to delete this Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "What type of Send is this?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maximum access count" }, - "maxAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Disabled" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Are you sure you want to remove the password?" }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, - "disableThisSend": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "All Sends" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Pending deletion" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Expired" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "The following organization policies are currently in effect:" - }, - "sendDisableHideEmailInEffect": { - "message": "Users are not allowed 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." - }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Remove individual ownership for organization users" }, - "textHiddenByDefault": { - "message": "When accessing the Send, hide the text by default", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to Send." - }, - "sendFileDesc": { - "message": "The file you want to Send." - }, - "copySendLinkOnSave": { - "message": "Copy the link to share this Send to my clipboard upon save." - }, - "sendLinkLabel": { - "message": "Send link", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "To verify your 2FA please click the button below." }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn is not supported in this browser." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, - "regenerateUsername": { - "message": "Regenerate username" - }, "generateUsername": { "message": "Generate username" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Username type" - }, "plusAddressedEmail": { "message": "Plus addressed email", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Hostname", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API access token" - }, "deviceVerification": { "message": "Device verification" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Number of users" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "pickAnAvatarColor": { "message": "Pick an avatar color" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "No collection" }, - "canView": { - "message": "Can view" - }, - "canViewExceptPass": { - "message": "Can view, except passwords" - }, - "canEdit": { - "message": "Can edit" - }, - "canEditExceptPass": { - "message": "Can edit, except passwords" - }, "noCollectionsAdded": { "message": "No collections added" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domain" - }, "alreadyHaveAccount": { "message": "Already have an account?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index 36ab0050700..f334131b69b 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Critical applications" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "What type of item is this?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Notes" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Note" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Drag to sort" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Text" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Edit folder" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "Base domain", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "ex.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filter" }, - "moveSelectedToOrg": { - "message": "Move selected to organization" - }, "deleteSelected": { "message": "Delete selected" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "Log in or create a new account to access your secure vault." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Submit" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Lost access to all of your two-step login providers? Use your recovery code to turn off all two-step login providers from your account." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Migrated from FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "Email" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." }, - "moveManyToOrgDesc": { - "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved." - }, "collectionsDesc": { "message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Verification code (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Regenerate password" - }, "length": { "message": "Length" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Danger zone" }, - "dangerZoneDesc": { - "message": "Careful, these actions are not reversible!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Deauthorize sessions" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Proceeding will also log you out of your current session, requiring you to log back in. You will also be prompted for two-step login again, if set up. Active sessions on other devices may continue to remain active for up to one hour." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "All sessions deauthorized" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Setting up two-step login can permanently lock you out of your Bitwarden account. A recovery code allows you to access your account in the event that you can no longer use your normal two-step login provider (example: you lose your device). Bitwarden support will not be able to assist you if you lose access to your account. We recommend you write down or print the recovery code and keep it in a safe place." }, + "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." + }, "viewRecoveryCode": { "message": "View recovery code" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Manage" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Turn off" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Revoke access" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "This two-step login provider is active on your account." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "There was a problem reading the security key. Try again." }, - "twoFactorWebAuthnWarning": { - "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used. Supported platforms:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Web vault and browser extensions on a desktop/laptop with a WebAuthn supported browser (Chrome, Opera, Vivaldi, or Firefox with FIDO U2F turned on)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Your Bitwarden two-step login recovery code" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "If you cannot access your account through your normal two-step login methods, you can use your two-step login recovery code to turn off all two-step providers on your account." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Recover account two-step login" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Set requirements for master password strength." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Require two-step login" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Set requirements for password generator." }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." - }, "masterPasswordPolicyInEffect": { "message": "One or more organization policies require your master password to meet the following requirements:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Organization owners and admins are exempt from this policy's enforcement." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "File" }, "sendTypeText": { "message": "Text" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Delete Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Are you sure you want to delete this Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "What type of Send is this?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maximum access count" }, - "maxAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Disabled" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Are you sure you want to remove the password?" }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, - "disableThisSend": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "All Sends" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Pending deletion" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Expired" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "The following organization policies are currently in effect:" - }, - "sendDisableHideEmailInEffect": { - "message": "Users are not allowed 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." - }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Remove individual ownership for organization users" }, - "textHiddenByDefault": { - "message": "When accessing the Send, hide the text by default", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to Send." - }, - "sendFileDesc": { - "message": "The file you want to Send." - }, - "copySendLinkOnSave": { - "message": "Copy the link to share this Send to my clipboard upon save." - }, - "sendLinkLabel": { - "message": "Send link", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "To verify your 2FA please click the button below." }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn is not supported in this browser." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, - "regenerateUsername": { - "message": "Regenerate username" - }, "generateUsername": { "message": "Generate username" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Username type" - }, "plusAddressedEmail": { "message": "Plus addressed email", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Hostname", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API access token" - }, "deviceVerification": { "message": "Device verification" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Number of users" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "pickAnAvatarColor": { "message": "Pick an avatar color" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "No collection" }, - "canView": { - "message": "Can view" - }, - "canViewExceptPass": { - "message": "Can view, except passwords" - }, - "canEdit": { - "message": "Can edit" - }, - "canEditExceptPass": { - "message": "Can edit, except passwords" - }, "noCollectionsAdded": { "message": "No collections added" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domain" - }, "alreadyHaveAccount": { "message": "Already have an account?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index 58e4de95317..9ef5c95bf86 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -3,22 +3,25 @@ "message": "Wszystkie aplikacje" }, "criticalApplications": { - "message": "Critical applications" + "message": "Krytyczne aplikacje" + }, + "noCriticalAppsAtRisk": { + "message": "Brak zagrożonych aplikacji krytycznych" }, "accessIntelligence": { - "message": "Access Intelligence" + "message": "Dostęp do informacji" }, "riskInsights": { - "message": "Risk Insights" + "message": "Spostrzeżenia dotyczące ryzyka" }, "passwordRisk": { - "message": "Password Risk" + "message": "Ryzyko związne z hasłem" }, "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." + "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." }, "dataLastUpdated": { - "message": "Data last updated: $DATE$", + "message": "Data ostatniej aktualizacji: $DATE$", "placeholders": { "date": { "content": "$1", @@ -30,16 +33,16 @@ "message": "Powiadomieni członkowie" }, "revokeMembers": { - "message": "Revoke members" + "message": "Unieważnij członk" }, "restoreMembers": { - "message": "Restore members" + "message": "Przywróć członków" }, "cannotRestoreAccessError": { - "message": "Cannot restore organization access" + "message": "Nie można przywrócić dostępu organizacji" }, "allApplicationsWithCount": { - "message": "All applications ($COUNT$)", + "message": "Wszystkie aplikacje ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -48,10 +51,10 @@ } }, "createNewLoginItem": { - "message": "Create new login item" + "message": "Utwórz nowy element logowania" }, "criticalApplicationsWithCount": { - "message": "Critical applications ($COUNT$)", + "message": "Aplikacje krytyczne ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -60,7 +63,7 @@ } }, "notifiedMembersWithCount": { - "message": "Notified members ($COUNT$)", + "message": "Powiadomieni członkowie ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -69,7 +72,7 @@ } }, "noAppsInOrgTitle": { - "message": "No applications found in $ORG NAME$", + "message": "Nie znaleziono aplikacji w $ORG NAME$", "placeholders": { "org name": { "content": "$1", @@ -78,49 +81,88 @@ } }, "noAppsInOrgDescription": { - "message": "As users save logins, applications appear here, showing any at-risk passwords. Mark critical apps and notify users to update passwords." + "message": "Jako użytkownicy zapisują logowania, pojawiają się tutaj aplikacje, pokazujące wszelkie hasła zagrożone. Zaznacz kluczowe aplikacje i powiadamiaj użytkowników o potrzebie aktualizacji haseł." }, "noCriticalAppsTitle": { - "message": "You haven't marked any applications as a Critical" + "message": "Nie oznaczyłeś żadnych aplikacji jako krytycznych" }, "noCriticalAppsDescription": { - "message": "Select your most critical applications to discover at-risk passwords, and notify users to change those passwords." + "message": "Wybierz najbardziej krytyczne aplikacje, aby odkryć hasła zagrożone i poinformuj użytkowników, by zmienili te hasła." }, "markCriticalApps": { - "message": "Mark critical apps" + "message": "Oznacz krytyczne aplikacje" }, "markAppAsCritical": { - "message": "Mark app as critical" + "message": "Oznacz aplikację jako krytyczną" }, "appsMarkedAsCritical": { - "message": "Apps marked as critical" + "message": "Aplikacje oznaczone jako krytyczne" }, "application": { "message": "Aplikacja" }, "atRiskPasswords": { - "message": "At-risk passwords" + "message": "Zagrożone hasła" }, "requestPasswordChange": { - "message": "Request password change" + "message": "Poproś o zmianę hasła" }, "totalPasswords": { - "message": "Total passwords" + "message": "Wszystkie hasła" }, "searchApps": { - "message": "Search applications" + "message": "Wyszukaj aplikacje" }, "atRiskMembers": { - "message": "At-risk members" + "message": "Zagrożeni użytkownicy" + }, + "atRiskMembersWithCount": { + "message": "Zagrożeni członkowie ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Zagrożone aplikacje ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "Ci członkowie logują się do aplikacji ze słabymi, ujawnionymi lub ponownie używanymi hasłami." + }, + "atRiskApplicationsDescription": { + "message": "Te aplikacje mają słabe, ujawnione lub ponownie użyte hasła." + }, + "atRiskMembersDescriptionWithApp": { + "message": "Ci użytkownicy logują się do $APPNAME$ ze słabymi, ujawnionymi lub ponownie używanymi hasłami.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } }, "totalMembers": { - "message": "Total members" + "message": "Wszyscy członkowie" }, "atRiskApplications": { - "message": "At-risk applications" + "message": "Zagrożone aplikacje" }, "totalApplications": { - "message": "Total applications" + "message": "Wszystkie aplikacje" + }, + "unmarkAsCriticalApp": { + "message": "Odznacz jako krytyczną aplikację" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Krytyczna aplikacja została pomyślnie odznaczona" }, "whatTypeOfItem": { "message": "Jakiego rodzaju jest to element?" @@ -159,6 +201,9 @@ "notes": { "message": "Notatki" }, + "privateNote": { + "message": "Prywatna notatka" + }, "note": { "message": "Notatka" }, @@ -178,7 +223,7 @@ "message": "Tożsamość" }, "contactInfo": { - "message": "Daje kontaktowe" + "message": "Dane kontaktowe" }, "cardDetails": { "message": "Szczegóły karty" @@ -383,6 +428,9 @@ "dragToSort": { "message": "Przeciągnij, aby posortować" }, + "dragToReorder": { + "message": "Przeciągnij, aby zmienić kolejność" + }, "cfTypeText": { "message": "Tekst" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Edytuj folder" }, + "editWithName": { + "message": "Edytuj $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "Nowy folder" + }, + "folderName": { + "message": "Nazwa folderu" + }, + "folderHintText": { + "message": "Zagnieżdżaj foldery dodając nazwę folderu nadrzędnego, a następnie “/”. Przykład: Społeczne/Fora" + }, + "deleteFolderPermanently": { + "message": "Czy na pewno chcesz trwale usunąć ten folder?" + }, "baseDomain": { "message": "Domena podstawowa", "description": "Domain name. Example: website.com" @@ -469,7 +542,7 @@ "message": "Wygeneruj hasło" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "Wygeneruj hasło wyrazowe" }, "checkPassword": { "message": "Sprawdź, czy hasło zostało ujawnione." @@ -572,7 +645,7 @@ "message": "Bezpieczna notatka" }, "typeSshKey": { - "message": "SSH key" + "message": "Klucz SSH" }, "typeLoginPlural": { "message": "Dane logowania" @@ -668,7 +741,7 @@ } }, "viewItemType": { - "message": "Zobacz $TYPE$", + "message": "Zobacz $ITEMTYPE$", "placeholders": { "itemtype": { "content": "$1", @@ -689,15 +762,6 @@ "itemName": { "message": "Nazwa elementu" }, - "cannotRemoveViewOnlyCollections": { - "message": "Nie można usunąć kolekcji z uprawnieniami tylko do przeglądania: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "np.", "description": "Short abbreviation for 'example'." @@ -733,11 +797,11 @@ "description": "Copy password to clipboard" }, "copyPassphrase": { - "message": "Copy passphrase", + "message": "Skopiuj hasło wyrazowe", "description": "Copy passphrase to clipboard" }, "passwordCopied": { - "message": "Password copied" + "message": "Hasło zostało skopiowane" }, "copyUsername": { "message": "Kopiuj nazwę użytkownika", @@ -815,9 +879,6 @@ "filter": { "message": "Filtr" }, - "moveSelectedToOrg": { - "message": "Przenieś zaznaczone do organizacji" - }, "deleteSelected": { "message": "Usuń zaznaczone" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Zaznaczone elementy zostały przeniesione do organizacji $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Elementy przeniesione do $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "Nie" }, + "location": { + "message": "Lokalizacja" + }, "loginOrCreateNewAccount": { "message": "Zaloguj się lub utwórz nowe konto, aby uzyskać dostęp do Twojego bezpiecznego sejfu." }, @@ -994,7 +1049,7 @@ "message": "Logowanie za pomocą urządzenia musi być włączone w ustawieniach aplikacji Bitwarden. Potrzebujesz innej opcji?" }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "Potrzebujesz innego sposobu?" }, "loginWithMasterPassword": { "message": "Logowanie hasłem głównym" @@ -1009,13 +1064,13 @@ "message": "Użyj innej metody logowania" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Logowanie się za pomocą Passkey" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "Użyj jednokrotnego logowania" }, "welcomeBack": { - "message": "Welcome back" + "message": "Witaj ponownie" }, "invalidPasskeyPleaseTryAgain": { "message": "Błędny passkey. Spróbuj ponownie." @@ -1099,7 +1154,7 @@ "message": "Utwórz konto" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Nowy na Bitwarden?" }, "setAStrongPassword": { "message": "Ustaw silne hasło" @@ -1117,20 +1172,44 @@ "message": "Zaloguj się" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Zaloguj do Bitwarden" + }, + "enterTheCodeSentToYourEmail": { + "message": "Wpisz kod wysłany na Twój adres e-mail" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Wpisz kod z aplikacji uwierzytelniającej" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Naciśnij YubiKey aby uwierzytelnić" }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "Limit czasu uwierzytelniania" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "Upłynął limit czasu uwierzytelniania. Uruchom ponownie proces logowania." }, - "verifyIdentity": { - "message": "Zweryfikuj swoją tożsamość" + "verifyYourIdentity": { + "message": "Potwierdź swoją tożsamość" + }, + "weDontRecognizeThisDevice": { + "message": "Nie rozpoznajemy tego urządzenia. Wpisz kod wysłany na Twój e-mail, aby zweryfikować tożsamość." + }, + "continueLoggingIn": { + "message": "Kontynuuj logowanie" + }, + "whatIsADevice": { + "message": "Czym jest urządzenie?" + }, + "aDeviceIs": { + "message": "Urządzenie to unikalna instalacja aplikacji Bitwarden, w której się zalogowano. Ponowna instalacja, wyczyszczenie danych aplikacji lub usunięcie plików cookie może spowodować, że urządzenie pojawi się wielokrotnie." }, "logInInitiated": { "message": "Logowanie rozpoczęte" }, + "logInRequestSent": { + "message": "Żądanie wysłane" + }, "submit": { "message": "Wyślij" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Wprowadź adres e-mail swojego konta, a podpowiedź hasła zostanie wysłana do Ciebie" }, - "passwordHint": { - "message": "Podpowiedź do hasła" - }, - "enterEmailToGetHint": { - "message": "Wpisz adres e-mail powiązany z kontem, aby otrzymać podpowiedź do hasła głównego." - }, "getMasterPasswordHint": { "message": "Uzyskaj podpowiedź do hasła głównego" }, @@ -1254,10 +1327,10 @@ "message": "Adres e-mail" }, "yourVaultIsLockedV2": { - "message": "Twój sejf jest zablokowany." + "message": "Twój sejf jest zablokowany" }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "Twoje konto jest zablokowane" }, "uuid": { "message": "UUID" @@ -1294,7 +1367,7 @@ "message": "Nie masz uprawnień do przeglądania wszystkich elementów w tej kolekcji." }, "youDoNotHavePermissions": { - "message": "You do not have permissions to this collection" + "message": "Nie masz uprawnień do tej kolekcji" }, "noCollectionsInList": { "message": "Brak kolekcji do wyświetlenia." @@ -1320,11 +1393,38 @@ "notificationSentDevice": { "message": "Powiadomienie zostało wysłane na urządzenie." }, - "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "notificationSentDevicePart1": { + "message": "Odblokuj Bitwarden na swoim urządzeniu lub w" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "areYouTryingToAccessYourAccount": { + "message": "Czy próbujesz uzyskać dostęp do swojego konta?" + }, + "accessAttemptBy": { + "message": "Próba dostępu przez $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Potwierdź dostęp" + }, + "denyAccess": { + "message": "Odmów dostępu" + }, + "notificationSentDeviceAnchor": { + "message": "aplikacji internetowej" + }, + "notificationSentDevicePart2": { + "message": "Upewnij się, że fraza odcisku palca zgadza się z tą poniżej, zanim zatwierdzisz." + }, + "notificationSentDeviceComplete": { + "message": "Odblokuj Bitwarden na swoim urządzeniu. Przed zatwierdzeniem upewnij się, że fraza odcisku palca pasuje do tej poniżej." + }, + "aNotificationWasSentToYourDevice": { + "message": "Powiadomienie zostało wysłane na twoje urządzenie" }, "versionNumber": { "message": "Wersja $VERSION_NUMBER$", @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Zapamiętaj mnie" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Nie pytaj ponownie na tym urządzeniu przez 30 dni" + }, "sendVerificationCodeEmailAgain": { "message": "Wyślij ponownie wiadomość z kodem weryfikacyjnym" }, "useAnotherTwoStepMethod": { "message": "Użyj innej metody logowania dwustopniowego" }, + "selectAnotherMethod": { + "message": "Wybierz inną metodę", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Użyj kodu odzyskiwania" + }, "insertYubiKey": { "message": "Włóż klucz YubiKey do portu USB komputera, a następnie dotknij jego przycisku." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Opcje logowania dwustopniowego" }, + "selectTwoStepLoginMethod": { + "message": "Wybierz metodę logowania dwustopniowego" + }, "recoveryCodeDesc": { "message": "Utraciłeś dostęp do wszystkich swoich mechanizmów dwustopniowego logowania? Użyj kodów odzyskiwania, aby wyłączyć dwustopniowe logowanie na Twoim koncie." }, @@ -1417,7 +1530,7 @@ "message": "Klucz bezpieczeństwa FIDO U2F" }, "webAuthnTitle": { - "message": "FIDO2 WebAuthn" + "message": "Klucz dostępu" }, "webAuthnDesc": { "message": "Użyj dowolnego klucza bezpieczeństwa WebAuthn, aby uzyskać dostęp do swojego konta." @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(przeniesiony z FIDO)" }, + "openInNewTab": { + "message": "Otwórz w nowej karcie" + }, "emailTitle": { "message": "Adres e-mail" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Wybierz organizację, do której chcesz przenieść ten element. Ta czynność spowoduje utratę własności elementu i przenosi te uprawnienia do organizacji." }, - "moveManyToOrgDesc": { - "message": "Wybierz organizację, do której chcesz przenieść te elementy. Ta czynność spowoduje utratę własności elementów i przenosi te uprawnienia do organizacji." - }, "collectionsDesc": { "message": "Edytuj kolekcje zawierające ten element. Tylko użytkownicy organizacji posiadający dostęp do tych kolekcji będą mogli zobaczyć ten element." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "Zaznaczone elementy: $COUNT$\nElementy możliwe do przeniesienia: $MOVEABLE_COUNT$\nElementy niemożliwe do przeniesienia: $NONMOVEABLE_COUNT$", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Kod weryfikacyjny (TOTP)" }, @@ -1610,12 +1706,9 @@ "description": "deprecated. Use avoidAmbiguous instead." }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "Unikaj niejednoznacznych znaków", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Wygeneruj ponownie hasło" - }, "length": { "message": "Długość" }, @@ -1658,25 +1751,25 @@ "message": "Historia hasła" }, "generatorHistory": { - "message": "Generator history" + "message": "Historia generatora" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "Wyczyść historię generatora" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "Jeśli zatwierdzisz, wszystkie wygenerowane hasła zostaną usunięte z historii generatora. Czy chcesz kontynuować mimo to?" }, "noPasswordsInList": { "message": "Brak haseł." }, "clearHistory": { - "message": "Clear history" + "message": "Wyczyść historię" }, "nothingToShow": { - "message": "Nothing to show" + "message": "Brak zawartości do pokazania" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "Nic nie zostało wygenerowane przez ciebie w ostatnim czasie" }, "clear": { "message": "Wyczyść", @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Zaloguj się ponownie." }, + "currentSession": { + "message": "Aktualna sesja" + }, + "requestPending": { + "message": "Zapytanie oczekuje" + }, "logBackInOthersToo": { "message": "Zaloguj się ponownie. Jeśli używasz innych aplikacji Bitwarden, wyloguj się i zaloguj ponownie również w nich." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Niebezpieczna strefa" }, - "dangerZoneDesc": { - "message": "Uwaga - te operacje są nieodwracalne!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Zakończ sesje" }, @@ -1797,11 +1890,32 @@ "deauthorizeSessionsWarning": { "message": "Ta czynność spowoduje wylogowanie z bieżącej sesji, przez co konieczne będzie ponowne zalogowanie się. Zostaniesz również poproszony o ponowne logowanie dwustopniowe, jeśli masz włączoną tę opcję. Aktywne sesje na innych urządzeniach mogą pozostać aktywne przez maksymalnie godzinę." }, + "newDeviceLoginProtection": { + "message": "Logowanie nowego urządzenia" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Wyłącz ochronę logowania na nowym urządzeniu" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Włącz ochronę logowania na nowym urządzeniu" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Przejdź poniżej, aby wyłączyć e-maile weryfikacyjne wysyłane przez Bitwarden podczas logowania z nowego urządzenia." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Przejdź poniżej, aby Bitwarden wysyłał Ci e-maile weryfikacyjne podczas logowania z nowego urządzenia." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "Po wyłączeniu ochrony logowania na nowym urządzeniu, każdy, kto zna Twoje hasło główne, może uzyskać dostęp do Twojego konta z dowolnego urządzenia. Aby chronić swoje konto bez e-maili weryfikacyjnych, skonfiguruj dwuetapowe logowanie." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "Zapisano nowe zmiany ochrony logowania" + }, "sessionsDeauthorized": { "message": "Wszystkie sesje zostały zakończone" }, "accountIsOwnedMessage": { - "message": "This account is owned by $ORGANIZATIONNAME$", + "message": "To konto jest własnością $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -1866,7 +1980,7 @@ "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLoginLink": { - "message": "new login", + "message": "Nowe dane logowania", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new login instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsPartTwoNoOrgs": { @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Włączenie logowania dwustopniowego można trwale zablokować konto Bitwarden. Kod odzyskiwania pozwala na dostęp do konta w przypadku, gdy nie będziesz mógł skorzystać ze standardowego dostawcy logowania dwustopniowego (np. w przypadku utraty urządzenia). Pomoc techniczna Bitwarden nie będzie w stanie Ci pomóc, jeśli stracisz dostęp do swojego konta. Zalecamy zapisanie lub wydrukowanie kodu odzyskiwania i przechowywanie go w bezpiecznym miejscu." }, + "yourSingleUseRecoveryCode": { + "message": "Jednorazowy kod odzyskiwania może być użyty do wyłączenia dwuetapowego logowania w przypadku utraty dostępu do dostawcy logowania dwuetapowego. Bitwarden zaleca zapisanie kodu odzyskiwania i przechowywanie go w bezpiecznym miejscu." + }, "viewRecoveryCode": { "message": "Wyświetl kod odzyskiwania" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Zarządzaj" }, - "canManage": { - "message": "Może zarządzać" + "manageCollection": { + "message": "Zarządzaj kolekcją" + }, + "viewItems": { + "message": "Zobacz elementy" + }, + "viewItemsHidePass": { + "message": "Zobacz elementy, ukryte hasła" + }, + "editItems": { + "message": "Edytuj elementy" + }, + "editItemsHidePass": { + "message": "Edytuj elementy, ukryte hasła" }, "disable": { "message": "Wyłącz" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Unieważnij dostęp" }, + "revoke": { + "message": "Unieważnij" + }, "twoStepLoginProviderEnabled": { "message": "Ten dostawca logowania dwustopniowego jest już włączony na koncie." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "Wystąpił problem z odczytem klucza bezpieczeństwa. Spróbuj ponownie." }, - "twoFactorWebAuthnWarning": { - "message": "Z powodu ograniczeń platformy, klucze WebAuthn nie mogą być używane we wszystkich aplikacjach Bitwarden. Musisz włączyć inną metodę logowania dwustopniowego, aby zachować dostęp do konta w pozostałych sytuacjach. Wspierane platformy:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Sejf internetowy i rozszerzenia przeglądarki na komputerze/laptopie z przeglądarką obsługującą WebAuthn (Chrome, Opera, Vivaldi lub Firefox z włączoną obsługą FIDO U2F)." + "twoFactorWebAuthnWarning1": { + "message": "Z powodu ograniczeń platformy, klucze WebAuthn nie mogą być używane we wszystkich aplikacjach Bitwarden. Musisz włączyć inną metodę logowania dwustopniowego, aby zachować dostęp do konta w pozostałych sytuacjach." }, "twoFactorRecoveryYourCode": { "message": "Kod odzyskiwania konta Bitwarden" @@ -2405,7 +2534,7 @@ "message": "Sprawdź ujawnione hasła" }, "timesExposed": { - "message": "Times exposed" + "message": "Ujawniono" }, "exposedXTimes": { "message": "Ujawnione $COUNT$ raz(y)", @@ -2442,7 +2571,7 @@ "message": "Brak elementów zawierających słabe hasła." }, "weakness": { - "message": "Weakness" + "message": "Słabe" }, "reusedPasswordsReport": { "message": "Identyczne hasła" @@ -2470,7 +2599,7 @@ "message": "Nie znaleźliśmy identycznych haseł w sejfie." }, "timesReused": { - "message": "Times reused" + "message": "Ponownie użyto" }, "reusedXTimes": { "message": "Wykorzystane $COUNT$ razy", @@ -2770,7 +2899,7 @@ "message": "Pobierz licencję" }, "viewBillingToken": { - "message": "View Billing Token" + "message": "Pokaż token płatności" }, "updateLicense": { "message": "Zaktualizuj licencję" @@ -2819,10 +2948,10 @@ "message": "Faktury" }, "noUnpaidInvoices": { - "message": "No unpaid invoices." + "message": "Brak nieopłaconych faktur." }, "noPaidInvoices": { - "message": "No paid invoices." + "message": "Brak opłaconych faktur." }, "paid": { "message": "Zapłacono", @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Pozostało Ci 1 zaproszenie." + }, + "inviteZeroEmailDesc": { + "message": "Pozostało Ci 0 zaproszeń." + }, "userUsingTwoStep": { "message": "Ten użytkownik korzysta z logowania dwustopniowego, aby chronić swoje konto." }, @@ -3300,7 +3435,7 @@ "message": "Użytkownik" }, "userDesc": { - "message": "Standardowy użytkownik, posiadający dostęp do kolekcji w Twojej organizacji." + "message": "Standardowy użytkownik, posiadający dostęp do kolekcji w Twojej organizacji" }, "all": { "message": "Wszyscy" @@ -3430,7 +3565,7 @@ } }, "viewAllLogInOptions": { - "message": "View all log in options" + "message": "Zobacz wszystkie sposoby logowania" }, "viewAllLoginOptions": { "message": "Zobacz wszystkie sposoby logowania" @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Odłączone SSO" + }, "unlinkedSsoUser": { "message": "Odłącz logowanie jednokrotne SSO dla użytkownika %$ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Urządzenie" }, + "loginStatus": { + "message": "Status zalogowania" + }, + "firstLogin": { + "message": "Pierwsze logowanie" + }, + "trusted": { + "message": "Zaufane" + }, + "needsApproval": { + "message": "Wymaga zatwierdzenia" + }, + "areYouTryingtoLogin": { + "message": "Próbujesz się zalogować?" + }, + "logInAttemptBy": { + "message": "Próba logowania przez $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Typ urządenia" + }, + "ipAddress": { + "message": "Adres IP" + }, + "confirmLogIn": { + "message": "Potwierdź logowanie" + }, + "denyLogIn": { + "message": "Odrzuć logowanie" + }, + "thisRequestIsNoLongerValid": { + "message": "To żądanie jest już nieważne." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Logowanie potwierdzone dla $EMAIL$ na $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "Odrzucono próby logowania z innego urządzenia. Jeśli to naprawdę Ty, spróbuj ponownie zalogować się za pomocą urządzenia." + }, + "loginRequestHasAlreadyExpired": { + "message": "Prośba logowania wygasła." + }, + "justNow": { + "message": "Teraz" + }, + "requestedXMinutesAgo": { + "message": "Poproszono $MINUTES$ minut temu", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Tworzenie konta na" }, @@ -3883,13 +4091,19 @@ "message": "Aktualizuj przeglądarkę" }, "generatingRiskInsights": { - "message": "Generating your risk insights..." + "message": "Generowanie informacji o ryzyku..." }, "updateBrowserDesc": { "message": "Używasz nieobsługiwanej przeglądarki. Sejf internetowy może działać niewłaściwie." }, + "youHaveAPendingLoginRequest": { + "message": "Masz oczekujące żądanie logowania z innego urządzenia." + }, + "reviewLoginRequest": { + "message": "Przejrzyj żądanie logowania" + }, "freeTrialEndPromptCount": { - "message": "Your free trial ends in $COUNT$ days.", + "message": "Twój okres próbny kończy się za $COUNT$ dni.", "placeholders": { "count": { "content": "$1", @@ -3898,7 +4112,7 @@ } }, "freeTrialEndPromptMultipleDays": { - "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "message": "$ORGANIZATION$, twoja darmowa wersja próbna kończy się za $COUNT$ dni.", "placeholders": { "count": { "content": "$2", @@ -3911,7 +4125,7 @@ } }, "freeTrialEndPromptTomorrow": { - "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "message": "$ORGANIZATION$, twoja darmowa wersja próbna kończy się jutro", "placeholders": { "organization": { "content": "$1", @@ -3920,10 +4134,10 @@ } }, "freeTrialEndPromptTomorrowNoOrgName": { - "message": "Your free trial ends tomorrow." + "message": "Twój okres próbny kończy się jutro." }, "freeTrialEndPromptToday": { - "message": "$ORGANIZATION$, your free trial ends today.", + "message": "$ORGANIZATION$, twoja darmowa wersja próbna kończy się dzisiaj", "placeholders": { "organization": { "content": "$1", @@ -3932,10 +4146,10 @@ } }, "freeTrialEndingTodayWithoutOrgName": { - "message": "Your free trial ends today." + "message": "Twój okres próbny kończy się dzisiaj." }, "clickHereToAddPaymentMethod": { - "message": "Click here to add a payment method." + "message": "Kliknij tutaj, aby dodać metodę płatności." }, "joinOrganization": { "message": "Dołącz do organizacji" @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "Jeśli nie możesz uzyskać dostępu do konta poprzez standardowe metody logowania dwustopniowego, skorzystaj z kodu odzyskiwania, aby wyłączyć wszystkich dostawców logowania dwustopniowego na swoim koncie." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Zaloguj się poniżej, używając jednorazowego kodu odzyskiwania. Spowoduje to wyłączenie wszystkich dostawców dwuetapowych na Twoim koncie." + }, "recoverAccountTwoStep": { "message": "Przywróć logowanie dwustopniowe do konta" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Aktualizacja klucza szyfrowania nie może być kontynuowana" }, + "editFieldLabel": { + "message": "Edytuj $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Zmień kolejność $LABEL$. Użyj klawiszy ze strzałkami, aby przenieść element w górę lub w dół.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ przesunięto w górę, pozycja $INDEX$ z $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ przesunięto w dół, pozycja $INDEX$ z $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "Podczas aktualizacji klucza szyfrowania, folderów nie można odszyfrować. Aby kontynuować aktualizację, foldery muszą zostać usunięte. Żadne elementy sejfu nie zostaną usunięte." }, @@ -4492,7 +4761,7 @@ "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "youWillBeNotifiedOnceTheRequestIsApproved": { - "message": "You will be notified once the request is approved" + "message": "Zostaniesz powiadomiony po zatwierdzeniu prośby" }, "free": { "message": "Darmowy", @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Ustaw minimalne wymagania dla hasła głównego." }, + "passwordStrengthScore": { + "message": "Siła hasła: $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Wymagaj logowania dwustopniowego" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Ustaw minimalne wymagania dla generatora hasła." }, - "passwordGeneratorPolicyInEffect": { - "message": "Co najmniej jedna zasada organizacji wpływa na ustawienia generatora." - }, "masterPasswordPolicyInEffect": { "message": "Co najmniej jedna zasada organizacji wymaga, aby hasło główne spełniało następujące wymagania:" }, @@ -4725,10 +5000,10 @@ "message": "Zaloguj się za pomocą logowania jednokrotnego SSO swojej organizacji. Aby rozpocząć, wpisz swój identyfikator organizacji." }, "singleSignOnEnterOrgIdentifier": { - "message": "Enter your organization's SSO identifier to begin" + "message": "Wprowadź identyfikator SSO swojej organizacji, aby rozpocząć" }, "singleSignOnEnterOrgIdentifierText": { - "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + "message": "Aby zalogować się za pomocą dostawcy SSO, wprowadź identyfikator SSO organizacji, aby rozpocząć. Może być konieczne wprowadzenie identyfikatora SSO podczas logowania z nowego urządzenia." }, "enterpriseSingleSignOn": { "message": "Logowanie jednokrotne" @@ -4798,7 +5073,7 @@ "message": "Zablokuj użytkownikom możliwość dołączania do innych organizacji." }, "singleOrgPolicyDesc": { - "message": "Restrict members from joining other organizations. This policy is required for organizations that have enabled domain verification." + "message": "Ogranicz członków do dołączania do innych organizacji. Ta polityka jest wymagana dla organizacji, które włączyły weryfikację domeny." }, "singleOrgBlockCreateMessage": { "message": "Twoja obecna organizacja posiada zasady, które nie pozwalają na dołączanie do więcej niż jednej organizacji. Skontaktuj się z administratorami swojej organizacji lub zarejestruj się z innego konta Bitwarden." @@ -4807,7 +5082,7 @@ "message": "Członkowie organizacji, którzy nie są właścicielami lub administratorami i są już członkami innej organizacji zostaną usunięci z Twojej organizacji." }, "singleOrgPolicyMemberWarning": { - "message": "Non-compliant members will be placed in revoked status until they leave all other organizations. Administrators are exempt and can restore members once compliance is met." + "message": "Niezgodni członkowie zostaną objęci cofniętym statusem do czasu opuszczenia przez nich wszystkich innych organizacji. Administratorzy są zwolnieni i mogą przywrócić członków po osiągnięciu zgodności." }, "requireSso": { "message": "Uwierzytelnianie logowaniem jednokrotnym" @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Właściciele i administratorzy organizacji są zwolnieni z przestrzegania wymagań zasad." }, + "limitSendViews": { + "message": "Limit wyświetleń" + }, + "limitSendViewsHint": { + "message": "Nikt nie może wyświetlić Wysyłki po przekroczeniu limitu.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "Pozostało wyświetleń: $ACCESSCOUNT$", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Szczegóły Wysyłki", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Tekst do udostępnienia" + }, "sendTypeFile": { "message": "Plik" }, "sendTypeText": { "message": "Tekst" }, + "sendPasswordDescV3": { + "message": "Zabezpiecz tę Wysyłkę hasłem, które będzie wymagane, aby uzyskać do niej dostęp.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Nowa wysyłka", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Usuń wysyłkę", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Czy na pewno chcesz usunąć tę wysyłkę?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "Jakiego typu jest to wysyłka?", + "deleteSendPermanentConfirmation": { + "message": "Czy na pewno chcesz trwale usunąć tę Wysyłkę?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Data usunięcia" }, - "deletionDateDesc": { - "message": "Wysyłka zostanie trwale usunięta w określonym czasie.", + "deletionDateDescV2": { + "message": "Wysyłka zostanie trwale usunięte w tej dacie.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maksymalna liczba dostępów" }, - "maxAccessCountDesc": { - "message": "Jeśli funkcja jest włączona, po osiągnięciu maksymalnej liczby dostępów, użytkownicy nie będą mieli dostępu do tej wysyłki.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Obecna liczba dostępów" - }, - "sendPasswordDesc": { - "message": "Opcjonalne hasło dla użytkownika, aby uzyskać dostęp do wysyłki.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Prywatne notatki o tej wysyłce.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Wyłączone" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Czy na pewno chcesz usunąć hasło?" }, - "hideEmail": { - "message": "Ukryj mój adres e-mail przed odbiorcami." - }, - "disableThisSend": { - "message": "Wyłącz wysyłkę, aby nikt nie miał do niej dostępu.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Wszystkie wysyłki" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Oczekiwanie na usunięcie" }, + "hideTextByDefault": { + "message": "Domyślnie ukryj tekst" + }, "expired": { "message": "Wygasła" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "Obecnie obowiązujące zasady organizacji:" - }, - "sendDisableHideEmailInEffect": { - "message": "Użytkownicy nie mogą ukrywać swoich 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." - }, "modifiedPolicyId": { "message": "Zasada $ID$ została zaktualizowana.", "placeholders": { @@ -5187,7 +5460,7 @@ "message": "Niestandardowe" }, "customDesc": { - "message": "Umożliwia zaawansowaną kontrolę uprawnień użytkownika." + "message": "Umożliwia zaawansowaną kontrolę uprawnień użytkownika" }, "customDescNonEnterpriseStart": { "message": "Role niestandardowe to ", @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Wyłącz opcję własności osobistej dla użytkowników organizacji" }, - "textHiddenByDefault": { - "message": "Ukryj domyślnie tekst wysyłki", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Nazwa wysyłki.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Tekst, który chcesz wysłać." - }, - "sendFileDesc": { - "message": "Plik, który chcesz wysłać." - }, - "copySendLinkOnSave": { - "message": "Po zapisaniu wysyłki, skopiuj link do schowka." - }, - "sendLinkLabel": { - "message": "Link wysyłki", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Wyślij", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "Wystąpił błąd podczas zapisywania dat usunięcia i wygaśnięcia." }, + "hideYourEmail": { + "message": "Ukryj mój adres e-mail przed oglądającymi." + }, "webAuthnFallbackMsg": { "message": "Aby zweryfikować logowanie dwustopniowe, kliknij przycisk poniżej." }, "webAuthnAuthenticate": { "message": "Uwierzytelnianie WebAuthn" }, + "readSecurityKey": { + "message": "Odczytaj klucz bezpieczeństwa" + }, + "awaitingSecurityKeyInteraction": { + "message": "Oczekiwanie na interakcję z kluczem bezpieczeństwa..." + }, "webAuthnNotSupported": { "message": "Ta przeglądarka nie obsługuje uwierzytelniania WebAuthn." }, @@ -5533,7 +5794,7 @@ "message": "Hasło zostało zresetowane!" }, "resetPasswordEnrollmentWarning": { - "message": "Rejestracja zezwala administratorom organizacji na zmianę Twojego hasła głównego. Czy na pewno chcesz się zarejestrować?" + "message": "Rejestracja zezwala administratorom organizacji na zmianę Twojego hasła głównego" }, "accountRecoveryPolicy": { "message": "Administracja odzyskiwaniem konta" @@ -5632,13 +5893,13 @@ "message": "Przywrócono dostęp do organizacji" }, "bulkFilteredMessage": { - "message": "Wykluczono, nie dotyczy tej akcji." + "message": "Wykluczono, nie dotyczy tej akcji" }, "nonCompliantMembersTitle": { - "message": "Non-compliant members" + "message": "Niezgodni członkowie" }, "nonCompliantMembersError": { - "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + "message": "Niezgodni członkowie z jedną organizacją lub dwustopniową regułą logowania nie mogą zostać przywróceni, dopóki nie będą przestrzegali wymogów polityki" }, "fingerprint": { "message": "Unikalny identyfikator konta" @@ -5655,6 +5916,20 @@ "error": { "message": "Błąd" }, + "decryptionError": { + "message": "Błąd odszyfrowywania" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nie mógł odszyfrować elementów sejfu wymienionych poniżej." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Skontaktuj się z działem obsługi klienta,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "aby uniknąć dalszej utraty danych.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Zarządzanie użytkownikami musi być również przyznane z uprawnieniem do zarządzania odzyskiwaniem kont" }, @@ -5671,7 +5946,7 @@ "message": "Nazwa dostawcy" }, "providerSetup": { - "message": "Dostawca został skonfigurowany." + "message": "Dostawca został skonfigurowany" }, "clients": { "message": "Klienci" @@ -6226,11 +6501,11 @@ "message": "Key Connector" }, "memberDecryptionKeyConnectorDescStart": { - "message": "Połącz logowanie za pomocą SSO z Twoim serwerem kluczy odszyfrowania. Używając tej opcji, członkowie nie będą musieli używać swoich haseł głównych, aby odszyfrować dane sejfu.", + "message": "Połącz logowanie za pomocą SSO z Twoim serwerem kluczy odszyfrowania. Używając tej opcji, członkowie nie będą musieli używać swoich haseł głównych, aby odszyfrować dane sejfu", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Connect login with SSO to your self-hosted decryption key server. Using this option, members won’t need to use their master passwords to decrypt vault data. The require SSO authentication and single organization policies are required to set up Key Connector decryption. Contact Bitwarden Support for set up assistance.'" }, "memberDecryptionKeyConnectorDescLink": { - "message": "Wymaga uwierzytelniania SSO i polityki jednej organizacji", + "message": "wymaga uwierzytelniania SSO i polityki jednej organizacji", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Connect login with SSO to your self-hosted decryption key server. Using this option, members won’t need to use their master passwords to decrypt vault data. The require SSO authentication and single organization policies are required to set up Key Connector decryption. Contact Bitwarden Support for set up assistance.'" }, "memberDecryptionKeyConnectorDescEnd": { @@ -6292,7 +6567,7 @@ "message": "Pokaż token synchronizacji płatności" }, "generateBillingToken": { - "message": "Generate billing token" + "message": "Wygeneruj token płatności" }, "copyPasteBillingSync": { "message": "Skopiuj i wklej ten token do ustawień synchronizacji rozliczeniowej swojej organizacji." @@ -6301,7 +6576,7 @@ "message": "Twój token synchronizacji płatności może edytować ustawienia subskrypcji tej organizacji." }, "manageBillingTokenSync": { - "message": "Manage Billing Token" + "message": "Zarządzaj tokenami płatności" }, "setUpBillingSync": { "message": "Skonfiguruj synchronizację płatności" @@ -6367,7 +6642,7 @@ "message": "Token synchronizacji płatności" }, "automaticBillingSyncDesc": { - "message": "Automatic sync unlocks Families sponsorships and allows you to sync your license without uploading a file. After making updates in the Bitwarden cloud server, select Sync License to apply changes." + "message": "Automatyczna synchronizacja odblokowuje sponsorowanie rodzin i pozwala na synchronizację licencji bez przesyłania pliku. Po aktualizacjach w chmurze Bitwarden, wybierz licencję synchronizacji aby zastosować zmiany." }, "active": { "message": "Aktywny" @@ -6437,7 +6712,7 @@ "message": "Wymagane, jeśli identyfikatorem podmiotu nie jest adres URL." }, "offerNoLongerValid": { - "message": "This offer is no longer valid. Contact your organization administrators for more information." + "message": "Ta oferta nie jest już ważna. Aby uzyskać więcej informacji, skontaktuj się z administratorami organizacji." }, "openIdOptionalCustomizations": { "message": "Opcjonalne dostosowania" @@ -6516,23 +6791,14 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "Co chcesz wygenerować?" - }, - "passwordType": { - "message": "Rodzaj hasła" - }, - "regenerateUsername": { - "message": "Wygeneruj ponownie nazwę użytkownika" - }, "generateUsername": { "message": "Wygeneruj nazwę użytkownika" }, "generateEmail": { - "message": "Generate email" + "message": "Wygeneruj e-mail" }, "spinboxBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$.", + "message": "Wartość musi być pomiędzy $MIN$ a $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6546,7 +6812,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "message": " Użyj $RECOMMENDED$ znaków lub więcej, aby wygenerować silne hasło.", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -6556,7 +6822,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "message": " Użyj $RECOMMENDED$ słów lub więcej, aby wygenerować silne hasło.", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Rodzaj nazwy użytkownika" - }, "plusAddressedEmail": { "message": "Adres e-mail z plusem", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Użyj skonfigurowanej skrzynki catch-all w swojej domenie." }, + "useThisEmail": { + "message": "Użyj tego adresu e-mail" + }, "random": { "message": "Losowa", "description": "Generates domain-based username using random letters" @@ -6671,11 +6937,11 @@ "message": "Wygeneruj alias adresu e-mail z zewnętrznej usługi przekierowania." }, "forwarderDomainName": { - "message": "Email domain", + "message": "Domena e-mail", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Choose a domain that is supported by the selected service", + "message": "Wybierz domenę, która jest obsługiwana przez wybraną usługę", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ odrzucił Twoje żądanie. Skontaktuj się z dostawcą usług w celu uzyskania pomocy.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ odrzucił Twoje żądanie: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Nie można uzyskać ID maskowanego konta e-mail dla $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Nazwa hosta", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "Token dostępu API" - }, "deviceVerification": { "message": "Weryfikacja urządzenia" }, @@ -6820,7 +7107,7 @@ "description": "the text, 'SCIM', is an acronym and should not be translated." }, "scimIntegrationDescription": { - "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "message": "Automatycznie dostarczaj użytkownikom i grupom preferowanego dostawcy tożsamości za pomocą usługi SCIM. Znajdź obsługiwane integracje", "description": "the text, 'SCIM', is an acronym and should not be translated." }, "scimEnabledCheckboxDesc": { @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Dwustopniowe logowanie DUO jest wymagane dla Twojego konta." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Logowanie dwustopniowe Duo jest wymagane dla twojego konta. Wykonaj poniższe kroki, by dokończyć logowanie" + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Wykonaj poniższe kroki, by dokończyć logowanie" + }, "launchDuo": { "message": "Uruchom DUO" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Liczba użytkowników" }, - "loggingInAs": { - "message": "Logowanie jako" - }, - "notYou": { - "message": "To nie Ty?" - }, "pickAnAvatarColor": { "message": "Wybierz kolor awatara" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "Brak kolekcji" }, - "canView": { - "message": "Może wyświetlać" - }, - "canViewExceptPass": { - "message": "Może wyświetlać z wyjątkiem haseł" - }, - "canEdit": { - "message": "Można edytować" - }, - "canEditExceptPass": { - "message": "Może edytować z wyjątkiem haseł" - }, "noCollectionsAdded": { "message": "Nie dodano kolekcji" }, @@ -7832,7 +8107,7 @@ "message": "Przesyłanie ręczne" }, "manualBillingTokenUploadDesc": { - "message": "If you do not want to opt into billing sync, manually upload your license here. This will not automatically unlock Families sponsorships." + "message": "Jeśli nie chcesz zrezygnować z synchronizacji rozliczeniowej, ręcznie prześlij swoją licencję tutaj. Nie odblokuje to automatycznie sponsorowania dla rodziny." }, "syncLicense": { "message": "Synchronizuj licencję" @@ -8101,16 +8376,16 @@ "message": "Logowanie rozpoczęte" }, "rememberThisDeviceToMakeFutureLoginsSeamless": { - "message": "Remember this device to make future logins seamless" + "message": "Zapamiętaj to urządzenie, aby przyszłe logowania były bezproblemowe" }, "deviceApprovalRequired": { "message": "Wymagane zatwierdzenie urządzenia. Wybierz opcję zatwierdzenia poniżej:" }, "deviceApprovalRequiredV2": { - "message": "Device approval required" + "message": "Wymagane zatwierdzenie urządzenia" }, "selectAnApprovalOptionBelow": { - "message": "Select an approval option below" + "message": "Wybierz opcję zatwierdzenia poniżej" }, "rememberThisDevice": { "message": "Zapamiętaj to urządzenie" @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Poproś administratora o zatwierdzenie" }, - "approveWithMasterPassword": { - "message": "Zatwierdź przy użyciu hasła głównego" - }, "trustedDeviceEncryption": { "message": "Szyfrowanie zaufanego urządzenia" }, "trustedDevices": { "message": "Zaufane urządzenia" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Po uwierzytelnieniu członkowie odszyfrowają dane sejfu przy użyciu klucza zapisanego na ich urządzeniu.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Członkowie nie będą potrzebowali hasła głównego podczas logowania się za pomocą SSO. Hasło główne jest zastąpione kluczem szyfrowania przechowywanym na urządzeniu, co sprawia, że urządzenie jest zaufane. Pierwsze urządzenie, do którego użytkownik tworzy swoje konto i logi, będą zaufane. Nowe urządzenia będą musiały zostać zatwierdzone przez istniejące zaufane urządzenie lub przez administratora.", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "polityka pojedynczej organizacji", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "pojedyncza organizacja", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": ",", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "polityka,", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "polityka wymaganego SSO", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "Wymagane SSO", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "i", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "polityka i ", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "polityka administracji odzyskiwaniem", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "Administracja odzyskiwaniem konta", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "z automatycznym zapisem włączy się, gdy ta opcja jest używana.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "polityka włączy się, gdy ta opcja zostanie wykorzystana.", + "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": "Uprawnienia w Twojej organizacji zostały zaktualizowane, musisz teraz ustawić hasło główne.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Zatwierdź prośbę" }, + "deviceApproved": { + "message": "Urządzenie zostało zatwierdzone" + }, + "deviceRemoved": { + "message": "Urządzenie zostało usunięte" + }, + "removeDevice": { + "message": "Usuń urządzenie" + }, + "removeDeviceConfirmation": { + "message": "Czy na pewno chcesz usunąć to urządzenie?" + }, "noDeviceRequests": { "message": "Brak urządzeń do zatwierdzenia" }, @@ -8291,7 +8575,7 @@ "message": "Poproszono o zatwierdzenie urządzenia." }, "tdeOffboardingPasswordSet": { - "message": "User set a master password during TDE offboarding." + "message": "Użytkownik ustawił hasło główne podczas wyłączania TDE." }, "startYour7DayFreeTrialOfBitwardenFor": { "message": "Rozpocznij 7-dniowy darmowy okres próbny Bitwarden dla $ORG$", @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Twoja prośba została wysłana do Twojego administratora." }, - "youWillBeNotifiedOnceApproved": { - "message": "Zostaniesz powiadomiony po zatwierdzeniu." - }, "troubleLoggingIn": { "message": "Problem z zalogowaniem?" }, @@ -8342,7 +8623,7 @@ "message": "Brak adresu e-mail użytkownika" }, "activeUserEmailNotFoundLoggingYouOut": { - "message": "Active user email not found. Logging you out." + "message": "Nie znaleziono aktywnego adresu e-mail. Trwa wylogowanie." }, "deviceTrusted": { "message": "Zaufano urządzeniu" @@ -8440,10 +8721,13 @@ "message": "Zarządzaj zachowaniami kolekcji w organizacji" }, "limitCollectionCreationDesc": { - "message": "Limit collection creation to owners and admins" + "message": "Ogranicz tworzenie kolekcji do właścicieli i administratorów" }, "limitCollectionDeletionDesc": { - "message": "Limit collection deletion to owners and admins" + "message": "Ogranicz usuwanie kolekcji do właścicieli i administratorów" + }, + "limitItemDeletionDesc": { + "message": "Ogranicz usuwanie elementów do członków z uprawnieniami do zarządzania" }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Właściciele i administratorzy mogą zarządzać wszystkimi zbiorami i elementami" @@ -8491,12 +8775,9 @@ "message": "Adres URL serwera" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "URL samodzielnie hostowanego serwera", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Domena aliasu" - }, "alreadyHaveAccount": { "message": "Masz już konto?" }, @@ -8558,10 +8839,10 @@ "readOnlyCollectionAccess": { "message": "Nie masz dostępu do zarządzania tą kolekcją." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Brak uprawnienia Zarządzaj Uprawnieniami" + "grantManageCollectionWarningTitle": { + "message": "Brak uprawnień do zarządzania kolekcją" }, - "grantAddAccessCollectionWarning": { + "grantManageCollectionWarning": { "message": "Przyznaj uprawnienie Zarządzaj Uprawnieniami w celu umożliwienia pełnego zarządzania kolekcją, w tym usuwania kolekcji." }, "grantCollectionAccess": { @@ -9018,47 +9299,62 @@ "message": "Użyj SDK Menedżera Sekretów Bitwarden w następujących językach programowania, aby zbudować własne aplikacje." }, "ssoDescStart": { - "message": "Configure", + "message": "Konfiguruj", "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, "ssoDescEnd": { - "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "message": "dla Bitwarden przy użyciu instrukcji implementacyjnych dla Twojego dostawcy tożsamości.", "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, "userProvisioning": { - "message": "User provisioning" + "message": "Aprowizacja użytkowników" }, "scimIntegration": { "message": "SCIM" }, "scimIntegrationDescStart": { - "message": "Configure ", + "message": "Konfiguruj", "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, "scimIntegrationDescEnd": { - "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "message": "(System do zarządzania tożsamością międzydomeną) w celu automatycznej aprowizacji użytkowników i grup do Bitwarden przy użyciu instrukcji implementacji dla Twojego dostawcy tożsamości.", "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, "bwdc": { "message": "Bitwarden Directory Connector" }, "bwdcDesc": { - "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." + "message": "Skonfiguruj Bitwarden Directory Connector do automatycznej aprowizacji użytkowników i grup za pomocą przewodnika implementacyjnego dla dostawcy tożsamości." }, "eventManagement": { - "message": "Event management" + "message": "Zarządzanie zdarzeniami" }, "eventManagementDesc": { - "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." + "message": "Zintegruj dzienniki zdarzeń Bitwarden ze swoim systemem SIEM (Security Information and Event Management), korzystając z poradnika implementacyjnego dla Twojej platformy." }, "deviceManagement": { - "message": "Device management" + "message": "Zarządzanie urządzeniem" }, "deviceManagementDesc": { - "message": "Configure device management for Bitwarden using the implementation guide for your platform." + "message": "Skonfiguruj zarządzanie urządzeniem Bitwarden za pomocą instrukcji implementacyjnych dla Twojej platformy." + }, + "deviceIdMissing": { + "message": "Brakuje identyfikatora urządzenia" + }, + "deviceTypeMissing": { + "message": "Brak typu urządzenia" + }, + "deviceCreationDateMissing": { + "message": "Brak daty utworzenia urządzenia" + }, + "desktopRequired": { + "message": "Wymagany komputer" + }, + "reopenLinkOnDesktop": { + "message": "Otwórz ponownie ten link z adresu e-mail na komputerze." }, "integrationCardTooltip": { - "message": "Launch $INTEGRATION$ implementation guide.", + "message": "Uruchom przewodnik implementacyjny $INTEGRATION$.", "placeholders": { "integration": { "content": "$1", @@ -9067,7 +9363,7 @@ } }, "smIntegrationTooltip": { - "message": "Set up $INTEGRATION$.", + "message": "Skonfiguruj $INTEGRATION$.", "placeholders": { "integration": { "content": "$1", @@ -9076,7 +9372,7 @@ } }, "smSdkTooltip": { - "message": "View $SDK$ repository", + "message": "Zobacz repozytorium $SDK$", "placeholders": { "sdk": { "content": "$1", @@ -9085,7 +9381,7 @@ } }, "integrationCardAriaLabel": { - "message": "open $INTEGRATION$ implementation guide in a new tab.", + "message": "otwórz przewodnik implementacji $INTEGRATION$ w nowej karcie.", "placeholders": { "integration": { "content": "$1", @@ -9094,7 +9390,7 @@ } }, "smSdkAriaLabel": { - "message": "view $SDK$ repository in a new tab.", + "message": "wyświetl repozytorium $SDK$ w nowej karcie.", "placeholders": { "sdk": { "content": "$1", @@ -9103,7 +9399,7 @@ } }, "smIntegrationCardAriaLabel": { - "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "message": "ustaw przewodnik implementacji $INTEGRATION$ w nowej karcie.", "placeholders": { "integration": { "content": "$1", @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "miesięcznie za członka" }, + "monthPerMemberBilledAnnually": { + "message": "miesięcznie na członka, rozliczanie rocznie" + }, "seats": { "message": "Miejsca" }, @@ -9160,13 +9459,13 @@ "message": "Kontynuuj konfigurację darmowego okresu próbnego Menedżera Sekretnych Bitwarden" }, "enterTeamsOrgInfo": { - "message": "Enter your Teams organization information" + "message": "Wprowadź informacje o organizacji Teams" }, "enterFamiliesOrgInfo": { "message": "Wprowadź informacje o swojej organizacji rodzinnej" }, "enterEnterpriseOrgInfo": { - "message": "Enter your Enterprise organization information" + "message": "Wprowadź informacje o organizacji Enterprise" }, "viewItemsIn": { "message": "Zobacz elementy w $NAME$", @@ -9221,10 +9520,10 @@ "message": "Dostawca usług zarządzanych" }, "managedServiceProvider": { - "message": "Managed service provider" + "message": "Zarządzany dostawca usług" }, "multiOrganizationEnterprise": { - "message": "Multi-organization enterprise" + "message": "Przedsiębiorstwo wielu organizacji" }, "orgSeats": { "message": "Miejsca w organizacji" @@ -9272,16 +9571,16 @@ "message": "Zaktualizowane informacje podatkowe" }, "billingInvalidTaxIdError": { - "message": "Invalid tax ID, if you believe this is an error please contact support." + "message": "Nieprawidłowy numer identyfikacji podatkowej, jeśli uważasz, że jest to błąd, skontaktuj się z pomocą techniczną." }, "billingTaxIdTypeInferenceError": { - "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + "message": "Nie mogliśmy zweryfikować Twojego identyfikatora podatkowego, jeśli uważasz, że jest to błąd, skontaktuj się z pomocą techniczną." }, "billingPreviewInvalidTaxIdError": { - "message": "Invalid tax ID, if you believe this is an error please contact support." + "message": "Nieprawidłowy numer identyfikacji podatkowej, jeśli uważasz, że jest to błąd, skontaktuj się z pomocą techniczną." }, "billingPreviewInvoiceError": { - "message": "An error occurred while previewing the invoice. Please try again later." + "message": "Wystąpił błąd podczas podglądu faktury. Spróbuj ponownie później." }, "unverified": { "message": "Niezweryfikowane" @@ -9547,14 +9846,20 @@ "learnMoreAboutApi": { "message": "Dowiedz się więcej o API Bitwarden" }, + "fileSend": { + "message": "Wysyłka pliku" + }, "fileSends": { - "message": "File Sends" + "message": "Wysyłki plików" + }, + "textSend": { + "message": "Wysyłka tekstu" }, "textSends": { - "message": "Text Sends" + "message": "Wysyłki tekstów" }, "includesXMembers": { - "message": "for $COUNT$ member", + "message": "dla $COUNT$ członków", "placeholders": { "count": { "content": "$1", @@ -9641,28 +9946,37 @@ "message": "GB dodatkowej przestrzeni" }, "sshKeyAlgorithm": { - "message": "Key algorithm" + "message": "Algorytm klucza" + }, + "sshPrivateKey": { + "message": "Klucz prywatny" + }, + "sshPublicKey": { + "message": "Klucz publiczny" + }, + "sshFingerprint": { + "message": "Odcisk palca" }, "sshKeyFingerprint": { - "message": "Fingerprint" + "message": "Odcisk palca" }, "sshKeyPrivateKey": { - "message": "Private key" + "message": "Klucz prywatny" }, "sshKeyPublicKey": { - "message": "Public key" + "message": "Klucz publiczny" }, "sshKeyAlgorithmED25519": { "message": "ED25519" }, "sshKeyAlgorithmRSA2048": { - "message": "RSA 2048-Bit" + "message": "RSA 2048-bitowy" }, "sshKeyAlgorithmRSA3072": { - "message": "RSA 3072-Bit" + "message": "RSA 3072-bitowy" }, "sshKeyAlgorithmRSA4096": { - "message": "RSA 4096-Bit" + "message": "RSA 4096-bitowy" }, "premiumAccounts": { "message": "6 kont premium" @@ -9680,7 +9994,7 @@ "message": "Monitorowanie zdarzeń" }, "directoryIntegration": { - "message": "Directory integration" + "message": "Integracja katalogu" }, "passwordLessSso": { "message": "SSO bez hasła" @@ -9732,22 +10046,22 @@ "message": "Edytuj dostęp" }, "textHelpText": { - "message": "Use text fields for data like security questions" + "message": "Użyj pól tekstowych dla danych takich jak pytania bezpieczeństwa" }, "hiddenHelpText": { - "message": "Use hidden fields for sensitive data like a password" + "message": "Użyj ukrytych pól dla danych poufnych takich jak hasło" }, "checkBoxHelpText": { - "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + "message": "Użyj pól wyboru, jeśli chcesz automatycznie wypełnić pole wyboru formularza, np. zapamiętaj e-mail" }, "linkedHelpText": { - "message": "Use a linked field when you are experiencing autofill issues for a specific website." + "message": "Użyj powiązanego pola, gdy masz problemy z autouzupełnianiem na konkretnej stronie internetowej." }, "linkedLabelHelpText": { - "message": "Enter the the field's html id, name, aria-label, or placeholder." + "message": "Wprowadź atrybut z HTML'a: id, name, aria-label lub placeholder." }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "Uwzględnij wielkie litery", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -9774,10 +10088,6 @@ "message": "Dołącz znaki specjalne", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Dodaj załącznik" }, @@ -9788,23 +10098,23 @@ "message": "Czy na pewno chcesz trwale usunąć ten załącznik?" }, "manageSubscriptionFromThe": { - "message": "Manage subscription from the", + "message": "Zarządzaj subskrypcją z", "description": "This represents the beginning of a sentence. The full sentence will be 'Manage subscription from the Provider Portal', but 'Provider Portal' will be a link and thus cannot be included in the translation file." }, "toHostBitwardenOnYourOwnServer": { - "message": "To host Bitwarden on your own server, you will need to upload your license file. To support Free Families plans and advanced billing capabilities for your self-hosted organization, you will need to set up automatic sync in your self-hosted organization." + "message": "Aby hostować Bitwarden na własnym serwerze, musisz przesłać plik licencyjny. Aby wesprzeć darmowe plany rodzinne i zaawansowane możliwości rozliczeniowe dla własnej organizacji, musisz skonfigurować automatyczną synchronizację." }, "selfHostingTitleProper": { - "message": "Self-Hosting" + "message": "Samodzielne hostowanie" }, "claim-domain-single-org-warning": { - "message": "Claiming a domain will turn on the single organization policy." + "message": "Zgłoszenie domeny włączy zasadę pojedynczej organizacji." }, "single-org-revoked-user-warning": { - "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." + "message": "Niezgodni członkowie zostaną odwołani. Administratorzy mogą przywrócić członków po opuszczeniu wszystkich innych organizacji." }, "deleteOrganizationUser": { - "message": "Delete $NAME$", + "message": "Usuń $NAME$", "placeholders": { "name": { "content": "$1", @@ -9814,7 +10124,7 @@ } }, "deleteOrganizationUserWarningDesc": { - "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "message": "Spowoduje to trwałe usunięcie wszystkich elementów należących do $NAME$. Elementy w kolekcjach nie są objęte tą operacją.", "description": "Warning description for the delete organization user dialog", "placeholders": { "name": { @@ -9824,11 +10134,11 @@ } }, "deleteManyOrganizationUsersWarningDesc": { - "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "message": "Spowoduje to trwałe usunięcie wszystkich elementów należących do wybranych członków. Elementy w kolekcjach nie są objęte tą operacją.", "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { - "message": "Deleted $NAME$", + "message": "Usunięto $NAME$", "placeholders": { "name": { "content": "$1", @@ -9837,10 +10147,10 @@ } }, "organizationUserDeletedDesc": { - "message": "The user was removed from the organization and all associated user data has been deleted." + "message": "Użytkownik został usunięty z organizacji i wszystkie powiązane dane użytkownika zostały usunięte." }, "deletedUserId": { - "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "message": "Usunięto użytkownika $ID$ - właściciel / administrator usunął konto użytkownika", "placeholders": { "id": { "content": "$1", @@ -9849,7 +10159,7 @@ } }, "userLeftOrganization": { - "message": "User $ID$ left organization", + "message": "Użytkownik $ID$ opuścił organizację", "placeholders": { "id": { "content": "$1", @@ -9858,7 +10168,7 @@ } }, "suspendedOrganizationTitle": { - "message": "The $ORGANIZATION$ is suspended", + "message": "$ORGANIZATION$ jest zawieszona", "placeholders": { "organization": { "content": "$1", @@ -9867,52 +10177,61 @@ } }, "suspendedUserOrgMessage": { - "message": "Contact your organization owner for assistance." + "message": "Skontaktuj się z właścicielem organizacji, aby uzyskać pomoc." }, "suspendedOwnerOrgMessage": { - "message": "To regain access to your organization, add a payment method." + "message": "Aby odzyskać dostęp do swojej organizacji, dodaj metodę płatności." }, "deleteMembers": { - "message": "Delete members" + "message": "Usuń członków" }, "noSelectedMembersApplicable": { - "message": "This action is not applicable to any of the selected members." + "message": "Ta akcja nie dotyczy żadnych zaznaczonych członków." }, "deletedSuccessfully": { - "message": "Deleted successfully" + "message": "Usunięto pomyślnie" }, "freeFamiliesSponsorship": { - "message": "Remove Free Bitwarden Families sponsorship" + "message": "Usuń bezpłatny sponsoring planu rodzinnego" }, "freeFamiliesSponsorshipPolicyDesc": { - "message": "Do not allow members to redeem a Families plan through this organization." + "message": "Nie zezwalaj członkom na realizację planu rodzinnego za pośrednictwem tej organizacji." }, "verifyBankAccountWithStatementDescriptorWarning": { - "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + "message": "Płatność za pomocą konta bankowego jest dostępna tylko dla klientów w Stanach Zjednoczonych. Będziesz musiał/musiała zweryfikować swoje konto bankowe. W ciągu najbliższych 1-2 dni roboczych dokonamy mikrowpłaty. Wprowadź kod deskryptora z tej wpłaty na stronie rozliczeń organizacji, aby zweryfikować konto bankowe. Niezweryfikowanie konta bankowego spowoduje brak płatności i zawieszenie Twojej subskrypcji." }, "verifyBankAccountWithStatementDescriptorInstructions": { - "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + "message": "Dokonaliśmy mikrowpłaty na Twoje konto bankowe (może to potrwać 1-2 dni robocze). Wprowadź sześciocyfrowy kod zaczynający się od „SM”, który znajdziesz w opisie wpłaty. Niezweryfikowanie konta bankowego spowoduje brak płatności i zawieszenie Twojej subskrypcji." }, "descriptorCode": { - "message": "Descriptor code" + "message": "Kod deskryptora" + }, + "cannotRemoveViewOnlyCollections": { + "message": "Nie można usunąć kolekcji z uprawnieniami tylko do przeglądania: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } }, "importantNotice": { - "message": "Important notice" + "message": "Ważna informacja" }, "setupTwoStepLogin": { - "message": "Set up two-step login" + "message": "Skonfiguruj dwustopniowe logowanie" }, "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + "message": "Bitwarden wyśle kod na Twój adres e-mail w celu zweryfikowania logowania z nowych urządzeń, począwszy od lutego 2025 r." }, "newDeviceVerificationNoticeContentPage2": { - "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + "message": "Możesz skonfigurować dwustopniowe logowanie jako alternatywny sposób ochrony konta lub zmienić swój adres e-mail, do którego masz dostęp." }, "remindMeLater": { - "message": "Remind me later" + "message": "Przypomnij mi później" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", + "message": "Czy masz pewny dostęp do swojego adresu e-mail, $EMAIL$?", "placeholders": { "email": { "content": "$1", @@ -9921,40 +10240,49 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "Nie, nie mam" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" + "message": "Tak, mam pewny dostęp do mojego adresu e-mail" }, "turnOnTwoStepLogin": { - "message": "Turn on two-step login" + "message": "Włącz dwustopniowe logowanie" }, "changeAcctEmail": { - "message": "Change account email" + "message": "Zmień adres e-mail konta" }, "removeMembers": { - "message": "Remove members" + "message": "Usuń członków" + }, + "devices": { + "message": "Urządzenia" + }, + "deviceListDescription": { + "message": "Twoje konto zostało zalogowane na każdym z poniższych urządzeń. Jeśli nie rozpoznajesz urządzenia, usuń je teraz." + }, + "deviceListDescriptionTemp": { + "message": "Twoje konto zostało zalogowane na każdym z poniższych urządzeń." }, "claimedDomains": { - "message": "Claimed domains" + "message": "Zgłoszone domeny" }, "claimDomain": { - "message": "Claim domain" + "message": "Zgłoś domenę" }, "reclaimDomain": { - "message": "Reclaim domain" + "message": "Odzyskaj domenę" }, "claimDomainNameInputHint": { - "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + "message": "Przykład: mojadomena.com. Subdomeny wymagają osobnego zgłoszenia." }, "automaticClaimedDomains": { - "message": "Automatic Claimed Domains" + "message": "Automatycznie zgłoszone domeny" }, "automaticDomainClaimProcess": { - "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + "message": "Bitwarden spróbuje zgłosić domenę 3 razy w ciągu pierwszych 72 godzin. Jeśli nie można zgłosić domeny, sprawdź rekord DNS w swoim serwerze i sprawdź go ręcznie. Domena zostanie usunięta z Twojej organizacji w ciągu 7 dni, jeśli nie zostanie zgłoszona." }, "domainNotClaimed": { - "message": "$DOMAIN$ not claimed. Check your DNS records.", + "message": "$DOMAIN$ nie została zgłoszona. Sprawdź swój rekord DNS.", "placeholders": { "DOMAIN": { "content": "$1", @@ -9963,19 +10291,19 @@ } }, "domainStatusClaimed": { - "message": "Claimed" + "message": "Zgłoszono" }, "domainStatusUnderVerification": { - "message": "Under verification" + "message": "W trakcie weryfikacji" }, "claimedDomainsDesc": { - "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + "message": "Zgłoś domenę, aby przejąć własność wszystkich kont członków, których adres e-mail pasuje do domeny. Członkowie będą mogli pominąć identyfikator SSO podczas logowania. Administratorzy będą również mogli usuwać konta członków." }, "invalidDomainNameClaimMessage": { - "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + "message": "Nieprawidłowy format. Format: mojadomena.com. Subdomeny wymagają osobnego zgłoszenia." }, "domainClaimedEvent": { - "message": "$DOMAIN$ claimed", + "message": "$DOMAIN$ została zgłoszona", "placeholders": { "DOMAIN": { "content": "$1", @@ -9984,7 +10312,7 @@ } }, "domainNotClaimedEvent": { - "message": "$DOMAIN$ not claimed", + "message": "$DOMAIN$ nie została zgłoszona", "placeholders": { "DOMAIN": { "content": "$1", @@ -9993,7 +10321,7 @@ } }, "updatedRevokeSponsorshipConfirmationForSentSponsorship": { - "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "message": "Jeśli usuniesz $EMAIL$, sponsoring dla tego planu rodzinnego nie będzie mógł zostać zrealizowany. Czy na pewno chcesz kontynuować?", "placeholders": { "email": { "content": "$1", @@ -10002,7 +10330,7 @@ } }, "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { - "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "message": "Jeśli usuniesz $EMAIL$, sponsoring dla tego planu rodzinnego zakończy się, a zapisana metoda płatności zostanie obciążona kwotą 40 USD + odpowiedni podatek w dniu $DATE$. Nie będziesz mógł/mogła zrealizować nowego sponsoringu do dnia $DATE$. Czy na pewno chcesz kontynuować?", "placeholders": { "email": { "content": "$1", @@ -10015,13 +10343,75 @@ } }, "domainClaimed": { - "message": "Domain claimed" + "message": "Domena zgłoszona" }, "organizationNameMaxLength": { - "message": "Organization name cannot exceed 50 characters." + "message": "Nazwa organizacji nie może przekraczać 50 znaków." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "Wprowadzone hasło jest nieprawidłowe." + }, + "importSshKey": { + "message": "Importuj" + }, + "confirmSshKeyPassword": { + "message": "Potwierdź hasło" + }, + "enterSshKeyPasswordDesc": { + "message": "Wprowadź hasło dla klucza SSH." + }, + "enterSshKeyPassword": { + "message": "Wprowadź hasło" + }, + "invalidSshKey": { + "message": "Klucz SSH jest nieprawidłowy" + }, + "sshKeyTypeUnsupported": { + "message": "Typ klucza SSH nie jest obsługiwany" + }, + "importSshKeyFromClipboard": { + "message": "Importuj klucz ze schowka" + }, + "sshKeyImported": { + "message": "Klucz SSH zaimportowano pomyślnie" + }, + "copySSHPrivateKey": { + "message": "Skopiuj klucz prywatny" + }, + "openingExtension": { + "message": "Otwieranie rozszerzenia przeglądarki Bitwarden" + }, + "somethingWentWrong": { + "message": "Coś poszło nie tak..." + }, + "openingExtensionError": { + "message": "Wystąpił problem z otwarciem rozszerzenia przeglądarki. Kliknij przycisk, aby go teraz otworzyć." + }, + "openExtension": { + "message": "Otwórz rozszerzenie" + }, + "doNotHaveExtension": { + "message": "Nie masz rozszerzenia Bitwarden do przeglądarki?" + }, + "installExtension": { + "message": "Zainstaluj rozszerzenie" + }, + "openedExtension": { + "message": "Otwarto rozszerzenie przeglądarki" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Pomyślnie otwarto rozszerzenie do przeglądarki. Teraz możesz przejrzeć swoje hasła zagrożone." + }, + "openExtensionManuallyPart1": { + "message": "Wystąpił problem z otwarciem rozszerzenia przeglądarki. Otwórz ikonę Bitwarden", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "z paska narzędzi.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Twoja subskrypcja wkrótce zostanie odnowiona. Aby zapewnić ciągłość usług, skontaktuj się z $RESELLER$, aby potwierdzić odnowienie przed $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "Faktura za Twoją subskrypcję została wystawiona $ISSUED_DATE$. Aby zapewnić ciągłość usług, skontaktuj się z $RESELLER$, aby potwierdzić odnowienie przed $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "Faktura za Twoją subskrypcję nie została opłacona. Aby zapewnić ciągłość usług, skontaktuj się z $RESELLER$, aby potwierdzić odnowienie przed $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Subskrypcja organizacji wznowiona" + }, + "restartSubscription": { + "message": "Wznów swoją subskrypcję" + }, + "suspendedManagedOrgMessage": { + "message": "Skontaktuj się z $PROVIDER$, aby uzyskać pomoc.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administratorzy mają teraz możliwość usuwania kont członków należących do zgłoszonej domeny." + }, + "deleteManagedUserWarningDesc": { + "message": "Ta akcja spowoduje usunięcie konta członka, w tym wszystkich elementów w ich sejfie. Zastępuje to poprzednią akcję Usuń." + }, + "deleteManagedUserWarning": { + "message": "Usunięcie to nowa akcja!" + }, + "seatsRemaining": { + "message": "Pozostało Ci $REMAINING$ miejsc z $TOTAL$ przydzielonych do tej organizacji. Skontaktuj się z dostawcą, aby zarządzać swoją subskrypcją.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Istniejąca organizacja" + }, + "selectOrganizationProviderPortal": { + "message": "Wybierz organizację, którą chcesz dodać do swojego portalu dostawcy." + }, + "noOrganizations": { + "message": "Brak organizacji do wyświetlenia" + }, + "yourProviderSubscriptionCredit": { + "message": "Twoja subskrypcja dostawcy otrzyma zwrot środków za pozostały czas subskrypcji organizacji." + }, + "doYouWantToAddThisOrg": { + "message": "Czy chcesz dodać tę organizację do $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Dodano istniejącą organizację" + }, + "assignedExceedsAvailable": { + "message": "Przydzielone miejsca przekraczają dostępne miejsca." + }, + "changeAtRiskPassword": { + "message": "Zmień hasło zagrożone" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Usuń odblokowanie kodem PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Nie zezwalaj członkom na odblokowanie ich konta za pomocą kodu PIN." + }, + "limitedEventLogs": { + "message": "Plany $PRODUCT_TYPE$ nie mają dostępu do dzienników rzeczywistych wydarzeń", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Uzyskaj pełny dostęp do dzienników zdarzeń organizacji poprzez uaktualnienie do planu Teams lub Enterprise." + }, + "upgradeEventLogTitle": { + "message": "Uaktualnij dla rzeczywistych danych dziennika zdarzeń" + }, + "upgradeEventLogMessage": { + "message": "Te wydarzenia są tylko przykładami i nie odzwierciedlają rzeczywistych wydarzeń w Twojej organizacji Bitwarden." + }, + "cannotCreateCollection": { + "message": "Darmowe organizacje mogą posiadać maksymalnie 2 kolekcje. Aby dodać więcej kolekcji, przejdź na plan płatny." } } diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index 463b7f5e060..ea43fd4eae6 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Aplicações críticas" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Acessar a Inteligência" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "Membros de risco" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total de membros" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Todos os aplicativos" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "Que tipo de item é este?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Notas" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Nota" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Arrastar para ordenar" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Texto" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Editar Pasta" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "Domínio de base", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Nome do item" }, - "cannotRemoveViewOnlyCollections": { - "message": "Você não pode remover coleções com permissões de Somente leitura: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "ex.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filtro" }, - "moveSelectedToOrg": { - "message": "Mover Selecionados para a Organização" - }, "deleteSelected": { "message": "Excluir Selecionados" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Itens selecionados movidos para $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Itens movidos para $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "Não" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "Inicie a sessão ou crie uma nova conta para acessar seu cofre seguro." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Inicie a sessão no Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Tempo de autenticação esgotado" }, "authenticationSessionTimedOut": { "message": "A sessão de autenticação expirou. Por favor, reinicie o processo de “login”." }, - "verifyIdentity": { - "message": "Verifique sua identidade" + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." }, "logInInitiated": { "message": "Login iniciado" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Enviar" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Digite o endereço de e-mail da sua conta e sua dica da senha será enviada para você" }, - "passwordHint": { - "message": "Dica da senha" - }, - "enterEmailToGetHint": { - "message": "Insira o seu endereço de e-mail para receber a dica da sua senha mestra." - }, "getMasterPasswordHint": { "message": "Obter dica da senha mestra" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "Uma notificação foi enviada para seu dispositivo." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "Uma notificação foi enviada para seu dispositivo." }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Certifique-se que sua conta esteja desbloqueado e que a frase de impressão digital corresponda à do outro dispositivo." - }, "versionNumber": { "message": "Versão $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Lembrar de mim" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Enviar código de verificação para o e-mail novamente" }, "useAnotherTwoStepMethod": { "message": "Utilizar outro método de verificação em duas etapas" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insira a sua YubiKey na porta USB do seu computador, e depois toque no botão da mesma." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Opções de login em duas etapas" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Perdeu o acesso a todos os seus provedores de duas etapas? Utilize o seu código de recuperação para desativar todos os provedores de duas etapas da sua conta." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Migrado de FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "E-mail" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Escolha uma organização para a qual deseja mover este item. Mudar para uma organização transfere a propriedade do item para essa organização. Você não será mais o proprietário direto deste item depois que ele for movido." }, - "moveManyToOrgDesc": { - "message": "Escolha uma organização para a qual deseja mover esses itens. Mudar para uma organização transfere a propriedade dos itens para essa organização. Você não será mais o proprietário direto desses itens depois que eles forem movidos." - }, "collectionsDesc": { "message": "Edite as coleções com as quais este item está sendo compartilhado. Somente usuários da organização com acesso a estas coleções poderão ver esse item." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "Você selecionou $COUNT$ item(ns). $MOVEABLE_COUNT$ item(ns) podem ser movidos para uma organização, mas, $NONMOVEABLE_COUNT$ não pode.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Código de verificação (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Evitar caracteres ambíguos", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Gerar Nova Senha" - }, "length": { "message": "Comprimento" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Por favor, reinicie a sessão." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Por favor, reinicie a sessão. Se estiver usando outros aplicativos do Bitwarden, encerre a sessão e reinicie também." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Zona de perigo" }, - "dangerZoneDesc": { - "message": "Cuidado, essas ações não são reversíveis!" - }, - "dangerZoneDescSingular": { - "message": "Cuidado, esta ação não é reversível!" - }, "deauthorizeSessions": { "message": "Desautorizar sessões" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "O processo também desconectará você da sua sessão atual, exigindo que você inicie a sessão novamente. Você também será solicitado a efetuar login em duas etapas novamente, se estiver ativado. Sessões ativas em outros dispositivos podem continuar ativas por até uma hora." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "Todas as Sessões Desautorizadas" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "A configuração de login em duas etapas pode bloqueá-lo permanentemente da sua conta no Bitwarden. Um código de recuperação permite que você acesse sua conta no caso de não poder mais usar seu provedor de login em duas etapas normalmente (exemplo: você perde seu dispositivo). O suporte do Bitwarden não será capaz de ajudá-lo se você perder o acesso à sua conta. Recomendamos que você anote ou imprima o código de recuperação e o mantenha em um lugar seguro." }, + "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." + }, "viewRecoveryCode": { "message": "Ver código de recuperação" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Gerenciar" }, - "canManage": { - "message": "Pode gerenciar" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Desabilitar" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Revogar acesso" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "Este provedor de login em duas etapas está ativado em sua conta." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "Houve um problema ao ler a chave de segurança. Tente novamente." }, - "twoFactorWebAuthnWarning": { - "message": "Devido às limitações da plataforma, o WebAuthn não pode ser usado em todos os aplicativos do Bitwarden. Você deve habilitar outro provedor de login em duas etapas, para que você possa acessar a sua conta quando o WebAuthn não puder ser usado. Plataformas suportadas:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Cofre web e extensões de navegador em um desktop/laptop com um navegador habilitado para WebAuthn (Chrome, Opera, Vivaldi ou Firefox com o FIDO U2F ativado)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Seu código de recuperação de login em duas etapas do Bitwarden" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "Este usuário está usando o login em duas etapas para proteger a sua conta." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "SSO desvinculado para o usuário $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Dispositivo" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Criando conta em" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "Você está usando um navegador da Web não suportado. O cofre web pode não funcionar corretamente." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "Se você não puder acessar sua conta por meio de seus métodos normais de login em duas etapas, poderá usar seu código de recuperação de login em duas etapas para desativar a funcionalidade de duas etapas da sua conta." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Recuperar login em duas etapas da conta" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "A atualização da chave de criptografia não pode continuar" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "Ao atualizar sua chave de criptografia, suas pastas não puderam ser descriptografadas. Para continuar com a atualização, suas pastas devem ser excluídas. Nenhum item de cofre será excluído se você prosseguir." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Defina os requisitos mínimos para a força da senha mestra." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Exigir login em duas etapas" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Defina os requisitos mínimos para configuração do gerador de senhas." }, - "passwordGeneratorPolicyInEffect": { - "message": "Uma ou mais políticas da organização estão afetando as suas configurações do gerador." - }, "masterPasswordPolicyInEffect": { "message": "Uma ou mais políticas da organização exigem que a sua senha mestra cumpra aos seguintes requisitos:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Os Proprietários e Administradores da Organização estão isentos da aplicação desta política." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "Arquivo" }, "sendTypeText": { "message": "Texto" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Criar Novo Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Excluir Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Você tem certeza que deseja excluir este Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "Que tipo de Send é este?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Data de Exclusão" }, - "deletionDateDesc": { - "message": "O Send será eliminado permanentemente na data e hora especificadas.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Contagem Máxima de Acessos" }, - "maxAccessCountDesc": { - "message": "Se atribuído, usuários não poderão mais acessar este Send assim que o número máximo de acessos for atingido.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Contagem Atual de Acessos" - }, - "sendPasswordDesc": { - "message": "Opcionalmente exigir uma senha para os usuários acessarem este Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Notas privadas sobre esse Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Desativado" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Você tem certeza que deseja remover a senha?" }, - "hideEmail": { - "message": "Ocultar meu endereço de e-mail dos destinatários." - }, - "disableThisSend": { - "message": "Desabilite este Send para que ninguém possa acessá-lo.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Todos os Sends" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Exclusão pendente" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Expirado" }, @@ -5161,13 +5441,6 @@ "message": "Não permitir que os usuários ocultem seus endereços de e-mail dos 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." }, - "sendOptionsPolicyInEffect": { - "message": "As seguintes políticas da organização estão em vigor atualmente:" - }, - "sendDisableHideEmailInEffect": { - "message": "Os usuários não têm permissão para ocultar seus endereços de e-mail dos 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." - }, "modifiedPolicyId": { "message": "Política modificada $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Desativar propriedade pessoal para usuários da organização" }, - "textHiddenByDefault": { - "message": "Ao acessar o Send, ocultar o texto por padrão", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Um nome amigável para descrever este Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "O texto que você deseja enviar." - }, - "sendFileDesc": { - "message": "O arquivo que você deseja enviar." - }, - "copySendLinkOnSave": { - "message": "Copie o link para compartilhar este Send para minha área de transferência depois de salvar." - }, - "sendLinkLabel": { - "message": "Link do Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "Ocorreu um erro ao salvar as suas datas de exclusão e validade." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "Para verificar seu 2FA, por favor, clique no botão abaixo." }, "webAuthnAuthenticate": { "message": "Autenticar WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "O WebAuthn não é suportado neste navegador." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Erro" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Gerenciar usuários também devem ser concedidos com a permissão de gerenciar a recuperação de contas" }, @@ -6516,15 +6791,6 @@ "message": "Gerador", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "O que você gostaria de gerar?" - }, - "passwordType": { - "message": "Tipo de Senha" - }, - "regenerateUsername": { - "message": "Recriar Usuário" - }, "generateUsername": { "message": "Gerar Usuário" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Tipo de Usuário" - }, "plusAddressedEmail": { "message": "E-mail alternativo (com um +)", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Use o catch-all configurado no seu domínio." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Aleatório", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Não foi possível obter a máscara do ID da conta de email $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Hostname", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "Token de acesso API" - }, "deviceVerification": { "message": "Verificação do dispositivo" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "A autenticação em duas etapas do Duo é necessária para sua conta." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Abrir o Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Número de usuários" }, - "loggingInAs": { - "message": "Entrando como" - }, - "notYou": { - "message": "Não é você?" - }, "pickAnAvatarColor": { "message": "Escolha uma cor de avatar" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "Sem coleções" }, - "canView": { - "message": "Pode ver" - }, - "canViewExceptPass": { - "message": "Pode ver, exceto senhas" - }, - "canEdit": { - "message": "Pode editar" - }, - "canEditExceptPass": { - "message": "Pode editar, exceto senhas" - }, "noCollectionsAdded": { "message": "Nenhuma coleção adicionada" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Solicitar aprovação do administrador" }, - "approveWithMasterPassword": { - "message": "Aprovar com a senha mestre" - }, "trustedDeviceEncryption": { "message": "Criptografia de dispositivo confiável" }, "trustedDevices": { "message": "Dispositivos confiáveis" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Uma vez autenticados, os membros descriptografarão os dados do cofre usando uma chave armazenada no seu dispositivo", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "organização única", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "single organization", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": "política,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "policy,", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "Necessário SSO", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO required", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "política e", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "policy, and", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "gerenciar recuperação de conta", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "account recovery administration", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "política com inscrição automática será ativada quando esta opção for utilizada.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Aprovar solicitação" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Nenhum pedido de dispositivo" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Seu pedido foi enviado para seu administrador." }, - "youWillBeNotifiedOnceApproved": { - "message": "Será notificado assim que for aprovado." - }, "troubleLoggingIn": { "message": "Problemas em efetuar login?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limitar exclusão de coleção a proprietários e administradores" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Proprietários e administradores podem gerenciar todas as coleções e itens" }, @@ -8494,9 +8778,6 @@ "message": "URL do servidor auto-host", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias do domínio" - }, "alreadyHaveAccount": { "message": "Já tem uma conta?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "Você não tem acesso para gerenciar esta coleção." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Falta conceder o Poder de Gerenciar Permissões" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Conceda o poder de gerenciar permissões para habilitar o gerenciamento completo da coleção, incluindo a exclusão da coleção." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Conceder acesso de grupos ou membros a esta coleção." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure o gerenciamento de dispositivos para o Bitwarden usando o guia de implementação da sua plataforma." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Inicie o guia de implementação $INTEGRATION$.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "mês por membro" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Lugares" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Saiba mais sobre a API do Bitwarden" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "Arquivos enviados" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Texto enviado" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Algoritmo da chave" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Impressão digital" }, @@ -9774,10 +10088,6 @@ "message": "Incluir caracteres especiais", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Adicionar anexo" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Código do descritor" }, + "cannotRemoveViewOnlyCollections": { + "message": "Você não pode remover coleções com permissões de Somente leitura: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remover membro?" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index 57a2eee7cf5..9bbda05c93f 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Aplicações críticas" }, + "noCriticalAppsAtRisk": { + "message": "Não há aplicações críticas em risco" + }, "accessIntelligence": { "message": "Aceder à informação" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "Membros em risco" }, + "atRiskMembersWithCount": { + "message": "Membros em risco ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Aplicações em risco ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "Estes membros estão a iniciar sessão em aplicações com palavras-passe fracas, expostas ou reutilizadas." + }, + "atRiskApplicationsDescription": { + "message": "Estas aplicações têm palavras-passe fracas, expostas ou reutilizadas." + }, + "atRiskMembersDescriptionWithApp": { + "message": "Estes membros estão a iniciar sessão no $APPNAME$ com palavras-passe fracas, expostas ou reutilizadas.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total de membros" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Todas de aplicações" }, + "unmarkAsCriticalApp": { + "message": "Desmarcar como aplicação crítica" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Aplicação crítica desmarcada com sucesso" + }, "whatTypeOfItem": { "message": "Que tipo de item é este?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Notas" }, + "privateNote": { + "message": "Nota privada" + }, "note": { "message": "Nota" }, @@ -172,7 +217,7 @@ "message": "Credenciais de início de sessão" }, "personalDetails": { - "message": "Detalhes pessoais" + "message": "Dados pessoais" }, "identification": { "message": "Identificação" @@ -383,6 +428,9 @@ "dragToSort": { "message": "Arraste para ordenar" }, + "dragToReorder": { + "message": "Arraste para reordenar" + }, "cfTypeText": { "message": "Texto" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Editar pasta" }, + "editWithName": { + "message": "Editar $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "Nova pasta" + }, + "folderName": { + "message": "Nome da pasta" + }, + "folderHintText": { + "message": "Crie uma subpasta adicionando o nome da pasta principal seguido de um \"/\". Exemplo: Redes Sociais/Fóruns" + }, + "deleteFolderPermanently": { + "message": "Tem a certeza de que pretende eliminar permanentemente esta pasta?" + }, "baseDomain": { "message": "Domínio de base", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Nome do item" }, - "cannotRemoveViewOnlyCollections": { - "message": "Não é possível remover coleções com permissões de Apenas visualização: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "ex.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filtrar" }, - "moveSelectedToOrg": { - "message": "Mover selecionados para a organização" - }, "deleteSelected": { "message": "Eliminar selecionados" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Itens selecionados movidos para $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Itens movidos para $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "Não" }, + "location": { + "message": "Localização" + }, "loginOrCreateNewAccount": { "message": "Inicie sessão ou crie uma nova conta para aceder ao seu cofre seguro." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Iniciar sessão no Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Introduza o código enviado para o seu e-mail" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Introduza o código da sua app de autenticação" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Prima a sua YubiKey para se autenticar" + }, "authenticationTimeout": { "message": "Tempo limite de autenticação" }, "authenticationSessionTimedOut": { "message": "A sessão de autenticação expirou. Por favor, reinicie o processo de início de sessão." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verifique a sua identidade" }, + "weDontRecognizeThisDevice": { + "message": "Não reconhecemos este dispositivo. Introduza o código enviado para o seu e-mail para verificar a sua identidade." + }, + "continueLoggingIn": { + "message": "Continuar a iniciar sessão" + }, + "whatIsADevice": { + "message": "O que é um dispositivo?" + }, + "aDeviceIs": { + "message": "Um dispositivo é uma instalação única da app Bitwarden onde o utilizador iniciou sessão. Reinstalar, limpar os dados da aplicação ou limpar os seus cookies pode fazer com que um dispositivo apareça várias vezes." + }, "logInInitiated": { "message": "A preparar o início de sessão" }, + "logInRequestSent": { + "message": "Pedido enviado" + }, "submit": { "message": "Submeter" }, @@ -1168,7 +1247,7 @@ "message": "Dica da palavra-passe mestra" }, "masterPassHintText": { - "message": "Se se esquecer da sua palavra-passe, a dica da palavra-passe pode ser enviada para o seu e-mail. Máximo de $CURRENT$/$MAXIMUM$ caracteres.", + "message": "Se se esquecer da sua palavra-passe, a dica da palavra-passe pode ser enviada para o seu e-mail. Máximo de $CURRENT$/$MAXIMUM$ carateres.", "placeholders": { "current": { "content": "$1", @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Introduza o endereço de e-mail da sua conta e ser-lhe-á enviada a sua dica da palavra-passe" }, - "passwordHint": { - "message": "Dica da palavra-passe" - }, - "enterEmailToGetHint": { - "message": "Introduza o endereço de e-mail da sua conta para receber a dica da sua palavra-passe mestra." - }, "getMasterPasswordHint": { "message": "Obter a dica da palavra-passe mestra" }, @@ -1217,7 +1290,7 @@ "message": "É necessário reescrever a palavra-passe mestra." }, "masterPasswordMinlength": { - "message": "A palavra-passe mestra deve ter pelo menos $VALUE$ caracteres.", + "message": "A palavra-passe mestra deve ter pelo menos $VALUE$ carateres.", "description": "The Master Password must be at least a specific number of characters long.", "placeholders": { "value": { @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "Foi enviada uma notificação para o seu dispositivo." }, + "notificationSentDevicePart1": { + "message": "Desbloqueie o Bitwarden no seu dispositivo ou no " + }, + "areYouTryingToAccessYourAccount": { + "message": "Está a tentar aceder à sua conta?" + }, + "accessAttemptBy": { + "message": "Tentativa de acesso por $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirmar acesso" + }, + "denyAccess": { + "message": "Recusar acesso" + }, + "notificationSentDeviceAnchor": { + "message": "aplicação web" + }, + "notificationSentDevicePart2": { + "message": "Certifique-se de que a frase da impressão digital corresponde à frase abaixo indicada antes de a aprovar." + }, + "notificationSentDeviceComplete": { + "message": "Desbloqueie o Bitwarden no seu dispositivo. Certifique-se de que a frase da impressão digital corresponde à frase abaixo antes de a aprovar." + }, "aNotificationWasSentToYourDevice": { "message": "Foi enviada uma notificação para o seu dispositivo" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Certifique-se de que a sua conta está desbloqueada e que a frase de impressão digital corresponde à do outro dispositivo" - }, "versionNumber": { "message": "Versão $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Memorizar" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Não voltar a perguntar neste dispositivo durante 30 dias" + }, "sendVerificationCodeEmailAgain": { "message": "Enviar e-mail com o código de verificação novamente" }, "useAnotherTwoStepMethod": { "message": "Utilizar outro método de verificação de dois passos" }, + "selectAnotherMethod": { + "message": "Selecionar outro método", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Utilize o seu código de recuperação" + }, "insertYubiKey": { "message": "Introduza a sua YubiKey na porta USB do seu computador, depois toque no botão da mesma." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Opções de verificação de dois passos" }, + "selectTwoStepLoginMethod": { + "message": "Selecionar método de verificação de dois passos" + }, "recoveryCodeDesc": { "message": "Perdeu o acesso a todos os seus fornecedores de verificação de dois passos? Utilize o seu código de recuperação para desativar todos os fornecedores de verificação de dois passos da sua conta." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Migrado do FIDO)" }, + "openInNewTab": { + "message": "Abrir num novo separador" + }, "emailTitle": { "message": "E-mail" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Escolha uma organização para a qual pretende mover este item. Mover para uma organização transfere a propriedade do item para essa organização. Deixará de ser o proprietário direto deste item depois de este ter sido movido." }, - "moveManyToOrgDesc": { - "message": "Escolha uma organização para a qual pretende mover estes itens. Mover para uma organização transfere a propriedade dos itens para essa organização. Deixará de ser o proprietário direto destes itens depois de terem sido movidos." - }, "collectionsDesc": { "message": "Edite as coleções com as quais este item está a ser partilhado. Apenas os utilizadores da organização com acesso a estas coleções poderão ver este item." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "Selecionou $COUNT$ item(ns). $MOVEABLE_COUNT$ item(ns) podem ser movidos para uma organização, $NONMOVEABLE_COUNT$ não podem.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Código de verificação (TOTP)" }, @@ -1602,20 +1698,17 @@ "message": "Mínimo de números" }, "minSpecial": { - "message": "Mínimo de caracteres especiais", + "message": "Mínimo de carateres especiais", "description": "Minimum special characters" }, "ambiguous": { - "message": "Evitar caracteres ambíguos", + "message": "Evitar carateres ambíguos", "description": "deprecated. Use avoidAmbiguous instead." }, "avoidAmbiguous": { - "message": "Evitar caracteres ambíguos", + "message": "Evitar carateres ambíguos", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Regenerar palavra-passe" - }, "length": { "message": "Comprimento" }, @@ -1635,7 +1728,7 @@ "description": "deprecated. Use numbersLabel instead." }, "specialCharacters": { - "message": "Caracteres especiais (!@#$%^&*)" + "message": "Carateres especiais (!@#$%^&*)" }, "numWords": { "message": "Número de palavras" @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Por favor, inicie sessão novamente." }, + "currentSession": { + "message": "Sessão atual" + }, + "requestPending": { + "message": "Pedido pendente" + }, "logBackInOthersToo": { "message": "Por favor, inicie sessão novamente. Se estiver a utilizar outras aplicações Bitwarden, termine a sessão e volte a iniciar sessão nessas também." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Zona de risco" }, - "dangerZoneDesc": { - "message": "Cuidado, estas ações são irreversíveis!" - }, - "dangerZoneDescSingular": { - "message": "Cuidado, esta ação não é reversível!" - }, "deauthorizeSessions": { "message": "Desautorizar sessões" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Ao prosseguir, também terminará a sua sessão atual e terá de iniciar sessão novamente. Ser-lhe-á também pedido que volte efetuar a verificação de dois passos, se estiver configurada. As sessões ativas noutros dispositivos podem continuar ativas até uma hora." }, + "newDeviceLoginProtection": { + "message": "Início de sessão de um novo dispositivo" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Desativar a proteção de início de sessão de novos dispositivos" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Ativar a proteção de início de sessão de novos dispositivos" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceda da seguinte forma para desativar os e-mails de verificação que o Bitwarden envia quando inicia sessão a partir de um novo dispositivo." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceda da seguinte forma para que o Bitwarden lhe envie e-mails de verificação quando iniciar sessão a partir de um novo dispositivo." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "Com a proteção de início de sessão de novos dispositivos desativada, qualquer pessoa com a sua palavra-passe mestra pode aceder à sua conta a partir de qualquer dispositivo. Para proteger a sua conta sem e-mails de verificação, configure a verificação de dois passos." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "Alterações na proteção do início de sessão do novo dispositivo guardadas" + }, "sessionsDeauthorized": { "message": "Todas as sessões desautorizadas" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "A configuração da verificação de dois passos pode bloquear permanentemente a sua conta Bitwarden. Um código de recuperação permite-lhe aceder à sua conta no caso de já não poder utilizar o seu fornecedor normal de verificação de dois passos (por exemplo, se perder o seu dispositivo). O suporte Bitwarden não poderá ajudá-lo se perder o acesso à sua conta. Recomendamos que anote ou imprima o código de recuperação e o guarde num local seguro." }, + "yourSingleUseRecoveryCode": { + "message": "O seu código de recuperação de utilização única pode ser utilizado para desativar a verificação de dois passos no caso de perder o acesso ao seu fornecedor de verificação de dois passos. O Bitwarden recomenda que anote o código de recuperação e o guarde num local seguro." + }, "viewRecoveryCode": { "message": "Ver código de recuperação" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Gerir" }, - "canManage": { - "message": "Pode gerir" + "manageCollection": { + "message": "Gerir coleção" + }, + "viewItems": { + "message": "Ver itens" + }, + "viewItemsHidePass": { + "message": "Ver itens, palavras-passe ocultas" + }, + "editItems": { + "message": "Editar itens" + }, + "editItemsHidePass": { + "message": "Editar itens, palavras-passe ocultas" }, "disable": { "message": "Desativar" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Revogar o acesso" }, + "revoke": { + "message": "Revogar" + }, "twoStepLoginProviderEnabled": { "message": "Este fornecedor de verificação de dois passos está ativado na sua conta." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "Ocorreu um problema ao ler a chave de segurança. Tente novamente." }, - "twoFactorWebAuthnWarning": { - "message": "Devido a limitações da plataforma, o WebAuthn não pode ser utilizado em todas as aplicações Bitwarden. Deve configurar outro fornecedor de verificação de dois passos para que possa aceder à sua conta quando o WebAuthn não puder ser utilizado. Plataformas suportadas:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Cofre web e extensões do navegador num computador de secretária/portátil com um navegador compatível com WebAuthn (Chrome, Opera, Vivaldi ou Firefox com FIDO U2F ativado)." + "twoFactorWebAuthnWarning1": { + "message": "Devido a limitações da plataforma, o WebAuthn não pode ser utilizado em todas as aplicações Bitwarden. Deve configurar outro fornecedor de verificação de dois passos para poder aceder à sua conta quando o WebAuthn não puder ser utilizado." }, "twoFactorRecoveryYourCode": { "message": "O seu código Bitwarden de recuperação da verificação de dois passos" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Ainda tem 1 convite." + }, + "inviteZeroEmailDesc": { + "message": "Restam-lhe 0 convites." + }, "userUsingTwoStep": { "message": "Este utilizador está a utilizar a verificação de dois passos para proteger a sua conta." }, @@ -3717,8 +3852,11 @@ } } }, + "unlinkedSso": { + "message": "SSO desvinculado." + }, "unlinkedSsoUser": { - "message": "SSO do utilizador $ID$ desligado.", + "message": "SSO desvinculado para o utilizador $ID$.", "placeholders": { "id": { "content": "$1", @@ -3765,6 +3903,76 @@ "device": { "message": "Dispositivo" }, + "loginStatus": { + "message": "Estado do início de sessão" + }, + "firstLogin": { + "message": "Primeiro início de sessão" + }, + "trusted": { + "message": "Confiável" + }, + "needsApproval": { + "message": "Precisa de aprovação" + }, + "areYouTryingtoLogin": { + "message": "Está a tentar iniciar sessão?" + }, + "logInAttemptBy": { + "message": "Tentativa de início de sessão por $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Tipo de dispositivo" + }, + "ipAddress": { + "message": "Endereço IP" + }, + "confirmLogIn": { + "message": "Confirmar início de sessão" + }, + "denyLogIn": { + "message": "Recusar início de sessão" + }, + "thisRequestIsNoLongerValid": { + "message": "Este pedido já não é válido." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Início de sessão confirmado para $EMAIL$ no $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "Recusou uma tentativa de início de sessão de outro dispositivo. Se foi realmente o caso, tente iniciar sessão com o dispositivo novamente." + }, + "loginRequestHasAlreadyExpired": { + "message": "O pedido de início de sessão já expirou." + }, + "justNow": { + "message": "Agora mesmo" + }, + "requestedXMinutesAgo": { + "message": "Pedido há $MINUTES$ minutos", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "A criar conta em" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "Está a utilizar um navegador web não suportado. O cofre web pode não funcionar corretamente." }, + "youHaveAPendingLoginRequest": { + "message": "Tem um pedido de início de sessão pendente doutro dispositivo." + }, + "reviewLoginRequest": { + "message": "Rever pedido de início de sessão" + }, "freeTrialEndPromptCount": { "message": "O seu período experimental gratuito termina dentro de $COUNT$ dias.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "Se não conseguir aceder à sua conta através dos seus métodos normais de verificação de dois passos, pode utilizar o seu código de recuperação de verificação de dois passos para desativar todos os fornecedores deste método na sua conta." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Inicie sessão abaixo utilizando o seu código de recuperação de utilização única. Esta ação desativará todos os fornecedores de verificação de dois passos da sua conta." + }, "recoverAccountTwoStep": { "message": "Recuperar a verificação de dois passos da conta" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "A atualização da chave de encriptação não pode prosseguir" }, + "editFieldLabel": { + "message": "Editar $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reordenar $LABEL$. Utilize a tecla de seta para mover o item para cima ou para baixo.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ movido para cima, posição $INDEX$ de $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ movido para baixo, posição $INDEX$ de $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "Ao atualizar a sua chave de encriptação, as suas pastas não puderam ser desencriptadas. Para continuar com a atualização, as suas pastas têm de ser eliminadas. Nenhum item do cofre será eliminado se prosseguir." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Definir requisitos para a força da palavra-passe mestra." }, + "passwordStrengthScore": { + "message": "Pontuação da força da palavra-passe: $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Exigir verificação de dois passos" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Definir requisitos para o gerador de palavras-passe." }, - "passwordGeneratorPolicyInEffect": { - "message": "Uma ou mais políticas da organização estão a afetar as suas definições do gerador." - }, "masterPasswordPolicyInEffect": { "message": "Uma ou mais políticas da organização exigem que a sua palavra-passe mestra cumpra os seguintes requisitos:" }, @@ -4584,16 +4859,16 @@ } }, "policyInEffectUppercase": { - "message": "Contém um ou mais caracteres em maiúsculas" + "message": "Contém um ou mais carateres em maiúsculas" }, "policyInEffectLowercase": { - "message": "Contém um ou mais caracteres em minúsculas" + "message": "Contém um ou mais carateres em minúsculas" }, "policyInEffectNumbers": { "message": "Contém um ou mais números" }, "policyInEffectSpecial": { - "message": "Contém um ou mais dos seguintes caracteres especiais $CHARS$", + "message": "Contém um ou mais dos seguintes carateres especiais $CHARS$", "placeholders": { "chars": { "content": "$1", @@ -4783,13 +5058,13 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" }, "unlinkSso": { - "message": "Desativar SSO" + "message": "Desvincular SSO" }, "unlinkSsoConfirmation": { - "message": "Tem a certeza de que pretende desligar o SSO desta organização?" + "message": "Tem a certeza de que pretende desvincular o SSO desta organização?" }, "linkSso": { - "message": "Ativar SSO" + "message": "Vincular SSO" }, "singleOrg": { "message": "Organização única" @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Os proprietários e administradores da organização estão isentos da aplicação desta política." }, + "limitSendViews": { + "message": "Limitar visualizações" + }, + "limitSendViewsHint": { + "message": "Ninguém poderá ver este Send depois de o limite ser atingido.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ visualizações restantes", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Detalhes do Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Texto a partilhar" + }, "sendTypeFile": { "message": "Ficheiro" }, "sendTypeText": { "message": "Texto" }, + "sendPasswordDescV3": { + "message": "Adicione uma palavra-passe opcional para os destinatários acederem a este Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Novo Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Eliminar Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Tem a certeza de que pretende eliminar este Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "Que tipo de Send é este?", + "deleteSendPermanentConfirmation": { + "message": "Tem a certeza de que pretende eliminar permanentemente este Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Data de eliminação" }, - "deletionDateDesc": { - "message": "O Send será permanentemente eliminado na data e hora especificadas.", + "deletionDateDescV2": { + "message": "O Send será permanentemente eliminado nesta data.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Número máximo de acessos" }, - "maxAccessCountDesc": { - "message": "Se definido, os utilizadores deixarão de poder aceder a este Send quando a contagem máxima de acessos for atingida.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Número de acessos atual" - }, - "sendPasswordDesc": { - "message": "Opcionalmente, exigir uma palavra-passe para os utilizadores acederem a este Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Notas privadas sobre este Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Desativado" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Tem a certeza de que pretende remover a palavra-passe?" }, - "hideEmail": { - "message": "Ocultar o meu endereço de e-mail dos destinatários." - }, - "disableThisSend": { - "message": "Desative este Send para que ninguém possa aceder ao mesmo.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Todos os Sends" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Eliminação pendente" }, + "hideTextByDefault": { + "message": "Ocultar texto por predefinição" + }, "expired": { "message": "Expirado" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "As seguintes políticas da organização estão atualmente em vigor:" - }, - "sendDisableHideEmailInEffect": { - "message": "Os utilizadores não estão autorizados a ocultar o seu endereço de e-mail dos destinatários quando criam ou editam um Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "modifiedPolicyId": { "message": "Política $ID$ modificada.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Remover a propriedade individual dos utilizadores da organização" }, - "textHiddenByDefault": { - "message": "Ao aceder ao Send, ocultar o texto por defeito", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Um nome simpático para descrever este Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "O texto que deseja enviar." - }, - "sendFileDesc": { - "message": "O ficheiro que deseja enviar." - }, - "copySendLinkOnSave": { - "message": "Copiar o link para partilhar este Send para a minha área de transferência ao guardar." - }, - "sendLinkLabel": { - "message": "Link do Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "Ocorreu um erro ao guardar as suas datas de eliminação e validade." }, + "hideYourEmail": { + "message": "Oculte o seu endereço de e-mail dos visualizadores." + }, "webAuthnFallbackMsg": { "message": "Para verificar a sua 2FA, por favor, clique no botão abaixo." }, "webAuthnAuthenticate": { "message": "Autenticar o WebAuthn" }, + "readSecurityKey": { + "message": "Ler chave de segurança" + }, + "awaitingSecurityKeyInteraction": { + "message": "A aguardar interação da chave de segurança..." + }, "webAuthnNotSupported": { "message": "O WebAuthn não é suportado por este navegador." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Erro" }, + "decryptionError": { + "message": "Erro de desencriptação" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "O Bitwarden não conseguiu desencriptar o(s) item(ns) do cofre listado(s) abaixo." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contacte o serviço de apoio ao cliente", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "para evitar perdas adicionais de dados.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "A permissão para gerir utilizadores deve também ser concedida com a permissão para gerir a recuperação da conta" }, @@ -6412,7 +6687,7 @@ "message": "obrigatório" }, "charactersCurrentAndMaximum": { - "message": "$CURRENT$/$MAX$ máximo de caracteres", + "message": "$CURRENT$/$MAX$ máximo de carateres", "placeholders": { "current": { "content": "$1", @@ -6425,7 +6700,7 @@ } }, "characterMaximum": { - "message": "Máximo de $MAX$ caracteres", + "message": "Máximo de $MAX$ carateres", "placeholders": { "max": { "content": "$1", @@ -6516,15 +6791,6 @@ "message": "Gerador", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "O que é que gostaria de gerar?" - }, - "passwordType": { - "message": "Tipo de palavra-passe" - }, - "regenerateUsername": { - "message": "Regenerar nome de utilizador" - }, "generateUsername": { "message": "Gerar nome de utilizador" }, @@ -6546,7 +6812,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " Utilize $RECOMMENDED$ caracteres ou mais para gerar uma palavra-passe forte.", + "message": " Utilize $RECOMMENDED$ carateres ou mais para gerar uma palavra-passe forte.", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Tipo de nome de utilizador" - }, "plusAddressedEmail": { "message": "E-mail com subendereço", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Utilize a caixa de entrada de captura geral configurada para o seu domínio." }, + "useThisEmail": { + "message": "Utilizar este e-mail" + }, "random": { "message": "Aleatório", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ recusou o seu pedido. Por favor, contacte o seu fornecedor de serviços para obter assistência.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ recusou o seu pedido: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Não foi possível obter o ID da conta de e-mail mascarada de $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Nome de domínio", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "Token de acesso à API" - }, "deviceVerification": { "message": "Verificação do dispositivo" }, @@ -6876,7 +7163,7 @@ "message": "O campo não é um endereço de e-mail." }, "inputMinLength": { - "message": "O campo deve ter pelo menos $COUNT$ caracteres.", + "message": "O campo deve ter pelo menos $COUNT$ carateres.", "placeholders": { "count": { "content": "$1", @@ -6885,7 +7172,7 @@ } }, "inputMaxLength": { - "message": "O campo não pode exceder os $COUNT$ caracteres de comprimento.", + "message": "O campo não pode exceder os $COUNT$ carateres de comprimento.", "placeholders": { "count": { "content": "$1", @@ -6894,7 +7181,7 @@ } }, "inputForbiddenCharacters": { - "message": "Não são permitidos os seguintes caracteres: $CHARACTERS$", + "message": "Não são permitidos os seguintes carateres: $CHARACTERS$", "placeholders": { "characters": { "content": "$1", @@ -6903,7 +7190,7 @@ } }, "inputMinValue": { - "message": "O valor do campo tem de ser, pelo menos, $MIN$ caracteres.", + "message": "O valor do campo tem de ser, pelo menos, $MIN$ carateres.", "placeholders": { "min": { "content": "$1", @@ -6912,7 +7199,7 @@ } }, "inputMaxValue": { - "message": "O valor do campo não pode exceder os $MAX$ caracteres.", + "message": "O valor do campo não pode exceder os $MAX$ carateres.", "placeholders": { "max": { "content": "$1", @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "A verificação de dois passos Duo é necessária para a sua conta." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "A verificação de dois passos do Duo é necessária para a sua conta. Siga os passos abaixo para concluir o início de sessão." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Siga os passos abaixo para concluir o início de sessão." + }, "launchDuo": { "message": "Iniciar o DUO" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Número de utilizadores" }, - "loggingInAs": { - "message": "A iniciar sessão como" - }, - "notYou": { - "message": "Utilizador incorreto?" - }, "pickAnAvatarColor": { "message": "Escolha uma cor para o avatar" }, @@ -7044,11 +7331,11 @@ "message": "Limpar tudo" }, "toggleCharacterCount": { - "message": "Mostrar/ocultar contagem de caracteres", + "message": "Mostrar/ocultar contagem de carateres", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, "passwordCharacterCount": { - "message": "Contagem de caracteres da palavra-passe", + "message": "Contagem de carateres da palavra-passe", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, "hide": { @@ -7498,18 +7785,6 @@ "noCollection": { "message": "Sem coleções" }, - "canView": { - "message": "Pode ver" - }, - "canViewExceptPass": { - "message": "Pode ver, excepto palavras-passe" - }, - "canEdit": { - "message": "Pode editar" - }, - "canEditExceptPass": { - "message": "Pode editar, excepto palavras-passe" - }, "noCollectionsAdded": { "message": "Não foram adicionadas coleções" }, @@ -8052,7 +8327,7 @@ "message": "Palavra-passe fraca identificada e encontrada numa violação de dados. Utilize uma palavra-passe forte e única para proteger a sua conta. Tem a certeza de que pretende utilizar esta palavra-passe?" }, "characterMinimum": { - "message": "$LENGTH$ caracteres no mínimo", + "message": "$LENGTH$ carateres no mínimo", "placeholders": { "length": { "content": "$1", @@ -8061,7 +8336,7 @@ } }, "masterPasswordMinimumlength": { - "message": "A palavra-passe mestra deve ter pelo menos $LENGTH$ caracteres.", + "message": "A palavra-passe mestra deve ter pelo menos $LENGTH$ carateres.", "placeholders": { "length": { "content": "$1", @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Pedir aprovação do administrador" }, - "approveWithMasterPassword": { - "message": "Aprovar com a palavra-passe mestra" - }, "trustedDeviceEncryption": { "message": "Encriptação de dispositivo de confiança" }, "trustedDevices": { "message": "Dispositivos de confiança" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Uma vez autenticados, os membros desencriptam os dados do cofre utilizando uma chave armazenada no seu dispositivo. A", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Os membros não precisarão de uma palavra-passe mestra quando iniciarem sessão com o SSO. A palavra-passe mestra é substituída por uma chave de encriptação armazenada no dispositivo, tornando esse dispositivo fiável. O primeiro dispositivo em que um membro cria a sua conta e inicia sessão será de confiança. Os novos dispositivos terão de ser aprovados por um dispositivo de confiança existente ou por um administrador. A", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "política de organização", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "única,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "política de SSO", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "a política de SSO", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "obrigatória, e a", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "obrigatório e a", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "gestão da recuperação de contas", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "política de administração de recuperaçã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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "com inscrição automática será ativada quando esta opção for utilizada.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "de conta serão ativadas quando esta opção for utilizada.", + "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 a definição de uma palavra-passe mestra.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Aprovar pedido" }, + "deviceApproved": { + "message": "Dispositivo aprovado" + }, + "deviceRemoved": { + "message": "Dispositivo removido" + }, + "removeDevice": { + "message": "Remover dispositivo" + }, + "removeDeviceConfirmation": { + "message": "Tem a certeza de que pretende remover este dispositivo?" + }, "noDeviceRequests": { "message": "Sem pedidos de dispositivos" }, @@ -8312,7 +8596,7 @@ } }, "next": { - "message": "Avançar" + "message": "Seguinte" }, "ssoLoginIsRequired": { "message": "É necessário um início de sessão SSO" @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "O seu pedido foi enviado ao seu administrador." }, - "youWillBeNotifiedOnceApproved": { - "message": "Será notificado quando for aprovado." - }, "troubleLoggingIn": { "message": "Problemas a iniciar sessão?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limitar a eliminação de coleções aos proprietários e administradores" }, + "limitItemDeletionDesc": { + "message": "Limitar a eliminação de itens a membros com a permissão Pode gerir" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Os proprietários e administradores podem gerir todas as coleções e itens" }, @@ -8494,9 +8778,6 @@ "message": "URL do servidor auto-hospedado", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias de domínio" - }, "alreadyHaveAccount": { "message": "Já tem uma conta?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "Não tem acesso para gerir esta coleção." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Faltam permissões Pode gerir" + "grantManageCollectionWarningTitle": { + "message": "Permissões de gestão de coleções em falta" }, - "grantAddAccessCollectionWarning": { - "message": "Conceda permissões Pode gerir para permitir a gestão completa da coleção, incluindo a eliminação da coleção." + "grantManageCollectionWarning": { + "message": "Conceda permissões Gerir coleção para permitir a gestão completa da coleção, incluindo a eliminação da mesma." }, "grantCollectionAccess": { "message": "Conceder a grupos ou membros acesso a esta coleção." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure a gestão de dispositivos do Bitwarden utilizando o guia de implementação da sua plataforma." }, + "deviceIdMissing": { + "message": "ID do dispositivo em falta" + }, + "deviceTypeMissing": { + "message": "Tipo de dispositivo em falta" + }, + "deviceCreationDateMissing": { + "message": "Data de criação do dispositivo em falta" + }, + "desktopRequired": { + "message": "É necessário um computador" + }, + "reopenLinkOnDesktop": { + "message": "Reabra este link a partir do seu e-mail num computador." + }, "integrationCardTooltip": { "message": "Lançar o guia de implementação da $INTEGRATION$.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "mês por membro" }, + "monthPerMemberBilledAnnually": { + "message": "mês por membro faturado anualmente" + }, "seats": { "message": "Lugares" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Saiba mais sobre a API do Bitwarden" }, + "fileSend": { + "message": "Send de ficheiro" + }, "fileSends": { "message": "Sends de ficheiros" }, + "textSend": { + "message": "Send de texto" + }, "textSends": { "message": "Sends de texto" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Algoritmo de chaves" }, + "sshPrivateKey": { + "message": "Chave privada" + }, + "sshPublicKey": { + "message": "Chave pública" + }, + "sshFingerprint": { + "message": "Impressão digital" + }, "sshKeyFingerprint": { "message": "Impressão digital" }, @@ -9723,10 +10037,10 @@ "description": "The text, 'API', is an acronym and should not be translated." }, "showCharacterCount": { - "message": "Mostrar contagem de caracteres" + "message": "Mostrar contagem de carateres" }, "hideCharacterCount": { - "message": "Ocultar contagem de caracteres" + "message": "Ocultar contagem de carateres" }, "editAccess": { "message": "Editar acesso" @@ -9747,7 +10061,7 @@ "message": "Introduza o ID do HTML, o nome, a aria-label ou o placeholder do campo." }, "uppercaseDescription": { - "message": "Incluir caracteres em maiúsculas", + "message": "Incluir carateres em maiúsculas", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -9755,7 +10069,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Incluir caracteres em minúsculas", + "message": "Incluir carateres em minúsculas", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -9771,13 +10085,9 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Incluir caracteres especiais", + "message": "Incluir carateres especiais", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Adicionar anexo" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Código descritor" }, + "cannotRemoveViewOnlyCollections": { + "message": "Não é possível remover coleções com permissões de Apenas visualização: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Aviso importante" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remover membros" }, + "devices": { + "message": "Dispositivos" + }, + "deviceListDescription": { + "message": "A sua conta foi iniciada em cada um dos dispositivos abaixo. Se não reconhecer um dispositivo, remova-o agora." + }, + "deviceListDescriptionTemp": { + "message": "Tem sessão iniciada em cada um dos dispositivos abaixo." + }, "claimedDomains": { "message": "Domínios reivindicados" }, @@ -10018,9 +10346,71 @@ "message": "Domínio reivindicado" }, "organizationNameMaxLength": { - "message": "O nome da organização não pode exceder 50 caracteres." + "message": "O nome da organização não pode exceder 50 carateres." }, - "resellerRenewalWarning": { + "sshKeyWrongPassword": { + "message": "A palavra-passe que introduziu está incorreta." + }, + "importSshKey": { + "message": "Importar" + }, + "confirmSshKeyPassword": { + "message": "Confirmar palavra-passe" + }, + "enterSshKeyPasswordDesc": { + "message": "Introduza a palavra-passe para a chave SSH." + }, + "enterSshKeyPassword": { + "message": "Introduzir palavra-passe" + }, + "invalidSshKey": { + "message": "A chave SSH é inválida" + }, + "sshKeyTypeUnsupported": { + "message": "O tipo de chave SSH não é suportado" + }, + "importSshKeyFromClipboard": { + "message": "Importar chave da área de transferência" + }, + "sshKeyImported": { + "message": "Chave SSH importada com sucesso" + }, + "copySSHPrivateKey": { + "message": "Copiar chave privada" + }, + "openingExtension": { + "message": "A abrir a extensão de navegador Bitwarden" + }, + "somethingWentWrong": { + "message": "Ocorreu um erro..." + }, + "openingExtensionError": { + "message": "Tivemos problemas ao abrir a extensão de navegador Bitwarden. Clique no botão para a abrir agora." + }, + "openExtension": { + "message": "Abrir extensão" + }, + "doNotHaveExtension": { + "message": "Não tem a extensão de navegador Bitwarden?" + }, + "installExtension": { + "message": "Instalar extensão" + }, + "openedExtension": { + "message": "Extensão de navegador aberta" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Abriu com sucesso a extensão de navegador Bitwarden. Pode agora rever as suas palavras-passe em risco." + }, + "openExtensionManuallyPart1": { + "message": "Tivemos problemas ao abrir a extensão de navegador Bitwarden. Abra o ícone do Bitwarden", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "da barra de ferramentas.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { "message": "A sua subscrição será renovada em breve. Para garantir um serviço ininterrupto, contacte a $RESELLER$ para confirmar a sua renovação antes de $RENEWAL_DATE$.", "placeholders": { "reseller": { @@ -10033,7 +10423,7 @@ } } }, - "resellerOpenInvoiceWarning": { + "resellerOpenInvoiceWarningMgs": { "message": "A fatura da sua subscrição foi emitida a $ISSUED_DATE$. Para garantir um serviço ininterrupto, contacte a $RESELLER$ para confirmar a sua renovação antes de $DUE_DATE$.", "placeholders": { "reseller": { @@ -10050,7 +10440,7 @@ } } }, - "resellerPastDueWarning": { + "resellerPastDueWarningMsg": { "message": "A fatura da sua subscrição não foi paga. Para garantir um serviço ininterrupto, contacte a $RESELLER$ para confirmar a sua renovação antes de $GRACE_PERIOD_END$.", "placeholders": { "reseller": { @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Subscrição da organização reiniciada" + }, + "restartSubscription": { + "message": "Reinicie a sua subscrição" + }, + "suspendedManagedOrgMessage": { + "message": "Contacte a $PROVIDER$ para obter assistência.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Os administradores têm agora a capacidade de eliminar contas de membros que pertençam a um domínio reivindicado." + }, + "deleteManagedUserWarningDesc": { + "message": "Esta ação elimina a conta do membro, incluindo todos os itens no seu cofre. Esta ação substitui a anterior Remover." + }, + "deleteManagedUserWarning": { + "message": "Eliminar é uma nova ação!" + }, + "seatsRemaining": { + "message": "Tem $REMAINING$ lugares restantes dos $TOTAL$ lugares atribuídos a esta organização. Contacte o seu fornecedor para gerir a sua subscrição.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Organização existente" + }, + "selectOrganizationProviderPortal": { + "message": "Selecione uma organização para adicionar ao seu Portal do fornecedor." + }, + "noOrganizations": { + "message": "Não existem organizações para listar" + }, + "yourProviderSubscriptionCredit": { + "message": "A subscrição do fornecedor receberá um crédito por qualquer tempo restante na subscrição da organização." + }, + "doYouWantToAddThisOrg": { + "message": "Pretende adicionar esta organização a $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Organização existente adicionada" + }, + "assignedExceedsAvailable": { + "message": "Os lugares atribuídos excedem os lugares disponíveis." + }, + "changeAtRiskPassword": { + "message": "Alterar palavra-passe em risco" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remover o desbloqueio com PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Não permitir que os membros desbloqueiem a sua conta com um PIN." + }, + "limitedEventLogs": { + "message": "Os planos $PRODUCT_TYPE$ não têm acesso a registos de eventos reais", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Obtenha acesso total aos registos de eventos da organização ao atualizar para um plano Equipas ou Empresarial." + }, + "upgradeEventLogTitle": { + "message": "Atualizar para dados de registo de eventos reais" + }, + "upgradeEventLogMessage": { + "message": "Estes eventos são apenas exemplos e não refletem eventos reais na sua organização Bitwarden." + }, + "cannotCreateCollection": { + "message": "As organizações gratuitas podem ter até 2 coleções. Atualize para um plano pago para adicionar mais coleções." } } diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 18ff0b2158f..c57ad26d251 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Aplicațiile critice" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "Ce fel de articol este acesta?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Note" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Note" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Tragere pentru sortare" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Text" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Editare dosar" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "Domeniu de bază", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "ex.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filter" }, - "moveSelectedToOrg": { - "message": "Mutați cele selectate în organizație" - }, "deleteSelected": { "message": "Ștergere selecție" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Articolele selectate au fost mutate în $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "Nu" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "Autentificați-vă sau creați un cont nou pentru a accesa seiful dvs. securizat." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Autentificare inițiată" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Trimitere" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Indiciu parolă" - }, - "enterEmailToGetHint": { - "message": "Adresa de e-mail a contului pentru primirea indiciului parolei principale." - }, "getMasterPasswordHint": { "message": "Obținere indiciu parolă principală" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "O notificare a fost trimisă pe dispozitivul dvs." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "Versiunea $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Memorare autentificare" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Retrimitere e-mail cu codul de verificare" }, "useAnotherTwoStepMethod": { "message": "Utilizare de metodă diferită de autentificare în două etape" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Introduceți YubiKey în portul USB al calculatorului apoi apăsați butonul acestuia." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Opțiuni de autentificare în două etape" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Ați pierdut accesul la toți furnizorii de autentificare în două etape? Folosiți codul de recuperare pentru a dezactiva toți acești furnizori din contul dvs." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Migrate din FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "E-mail" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Alegeți o organizație la care doriți să mutați acest articol. Mutarea într-o organizație, transferă proprietatea asupra articolului către organizația respectivă. Nu veți mai fi proprietarul direct al acestui articol odată ce a fost mutat." }, - "moveManyToOrgDesc": { - "message": "Alegeți o organizație la care doriți să mutați aceste articole. Mutarea într-o organizație, transferă proprietatea asupra articolelor către organizația respectivă. Nu veți mai fi proprietarul direct al acestor articole odată ce au fost mutate." - }, "collectionsDesc": { "message": "Editați colecțiile cu care este partajat acest articol. Numai utilizatorii organizației cu acces la aceste colecții vor putea vedea acest articol." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "Ați selectat $COUNT$ articol(e). $MOVEABLE_COUNT$ articol(e) poate/pot fi mutat(e) într-o organizație, $NONMOVEABLE_COUNT$ nu.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Cod de verificare (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Regenerare parolă" - }, "length": { "message": "Lungime" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Vă rugăm să vă conectați din nou." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Vă rugăm să vă reconectați. Dacă utilizați și alte aplicații Bitwarden, reconectați-vă la ele de asemenea." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Zona de pericol" }, - "dangerZoneDesc": { - "message": "Atenție, aceste acțiuni nu sunt reversibile!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Revocare sesiuni" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Procedând astfel, veți fi deconectat din sesiunea curentă, fiind necesar să vă reconectați. De asemenea, vi se va solicita din nou autentificarea în două etape, dacă este configurată. Sesiunile active de pe alte dispozitive pot rămâne active timp de până la o oră." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "Toate sesiunile au fost dezautorizate" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Configurarea unei autentificări în două etape vă poate bloca permanent din contul Bitwarden. Un cod de recuperare vă permite să vă accesați contul în cazul în care nu mai puteți utiliza furnizorul normal de autentificare în două etape (exemplu: vă pierdeți dispozitivul). Serviciul de asistență Bitwarden nu vă va putea ajuta dacă pierdeți accesul la cont. Vă recomandăm să notați sau să imprimați codul de recuperare și să-l păstrați într-un loc sigur." }, + "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." + }, "viewRecoveryCode": { "message": "Afișare cod de recuperare" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Gestionare" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Dezactivare" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Revocare acces" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "Acest furnizor de autentificare în două etape este activ în contul dvs." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "A apărut o problemă la citirea cheii de securitate. Încercați din nou." }, - "twoFactorWebAuthnWarning": { - "message": "Din cauza limitărilor de platformă, WebAuthn nu poate fi utilizat pe toate aplicațiile Bitwarden. Ar trebui să configurați un alt furnizor de autentificare în două etape, astfel încât să vă puteți accesa contul atunci când WebAuthn nu se poate utiliza. Platformele acceptate:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Seif web și extensii de browser pe un desktop/laptop cu un browser compatibil cu WebAuthn (Chrome, Opera, Vivaldi sau Firefox cu FIDO U2F activat)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Codul dvs. Bitwarden de recuperare a autentificării în două etape" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "Acest utilizator folosește conectarea în două etape pentru a-și proteja contul." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "SSO deconectat pentru utilizatorul $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Dispozitiv" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "Utilizați un browser nesuportat. Seiful web ar putea să nu funcționeze corect." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "Dacă nu puteți să vă accesați contul prin metodele obișnuite de autentificare în două etape, puteți utiliza codul de recuperare a autentificării în două etape pentru a dezactiva toți furnizorii de servicii în două etape din cont." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Recuperare autentificare în două etape a contului" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Setați cerințele pentru puterea parolei principale." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Necesită autentificare în doi pași" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Setați cerințele pentru generatorul de parole." }, - "passwordGeneratorPolicyInEffect": { - "message": "Una sau mai multe politici organizaționale vă afectează setările generatorului." - }, "masterPasswordPolicyInEffect": { "message": "Una sau mai multe politici organizaționale necesită ca parola principală să îndeplinească următoarele cerințe:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Proprietarii și administratorii de organizații sunt exceptați de la aplicarea acestei politici." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "Fișier" }, "sendTypeText": { "message": "Text" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Nou Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Ștergere Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Sigur doriți să ștergeți acest Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "Ce fel de Send este acesta?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Data ștergerii" }, - "deletionDateDesc": { - "message": "Send-ul va fi șters definitiv la data și ora specificate.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Numărul maxim de accesări" }, - "maxAccessCountDesc": { - "message": "Dacă este configurat, utilizatorii nu vor mai putea accesa acest Send când a fost atins numărul maxim de accesări.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Numărul actual de accesări" - }, - "sendPasswordDesc": { - "message": "Opțional, este necesară o parolă pentru ca utilizatorii să acceseze acest Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Note private despre acest Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Dezactivat" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Sigur doriți să eliminați parola?" }, - "hideEmail": { - "message": "Ascundeți adresa mea de e-mail de la destinatari." - }, - "disableThisSend": { - "message": "Dezactivare Send pentru ca nimeni să nu-l poată accesa.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Toate Send-urile" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Ștergere în așteptare" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Expirat" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "În prezent, sunt în vigoare următoarele politici de organizare:" - }, - "sendDisableHideEmailInEffect": { - "message": "Utilizatorii nu au voie să-și ascundă adresa de e-mail de la destinatari atunci când creează sau editează un Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "modifiedPolicyId": { "message": "Politica $ID$ a fost editată.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Eliminați proprietatea individuală pentru utilizatorii organizației" }, - "textHiddenByDefault": { - "message": "Când Send-ul este accesat, ascundeți textul în mod implicit", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Un nume prietenos pentru a descrie acest Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Textul pe care doriți să-l trimiteți." - }, - "sendFileDesc": { - "message": "Fișierul pe care doriți să-l trimiteți." - }, - "copySendLinkOnSave": { - "message": "Copiați linkul pentru a partaja acest Send în clipboard-ul meu la salvare." - }, - "sendLinkLabel": { - "message": "Link Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "A survenit o eroare la salvarea datelor de ștergere și de expirare." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "Pentru a verifica 2FA, vă rugăm să faceți clic pe butonul de mai jos." }, "webAuthnAuthenticate": { "message": "Autentificare WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn nu este acceptat în acest browser." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Eroare" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "Ce doriți să generați?" - }, - "passwordType": { - "message": "Tip de parolă" - }, - "regenerateUsername": { - "message": "Regenerare nume de utilizator" - }, "generateUsername": { "message": "Generare nume de utilizator" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Tip de nume de utilizator" - }, "plusAddressedEmail": { "message": "E-mail Plus adresat", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Utilizați inbox-ul catch-all configurat pentru domeniul dvs." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Aleatoriu", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Nume gazdă", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "Token de acces API" - }, "deviceVerification": { "message": "Verificarea dispozitivului" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Număr de utilizatori" }, - "loggingInAs": { - "message": "Autentificare ca" - }, - "notYou": { - "message": "Nu sunteți dvs.?" - }, "pickAnAvatarColor": { "message": "Pick an avatar color" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "No collection" }, - "canView": { - "message": "Can view" - }, - "canViewExceptPass": { - "message": "Can view, except passwords" - }, - "canEdit": { - "message": "Can edit" - }, - "canEditExceptPass": { - "message": "Can edit, except passwords" - }, "noCollectionsAdded": { "message": "No collections added" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domain" - }, "alreadyHaveAccount": { "message": "Already have an account?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index 89209f2fa52..a31d13d227e 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Критичные приложения" }, + "noCriticalAppsAtRisk": { + "message": "Никакие критически важные приложения не подвергаются риску" + }, "accessIntelligence": { "message": "Управление доступом" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "Участники группы риска" }, + "atRiskMembersWithCount": { + "message": "Пользователи повышенного риска ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Приложения с повышенным риском ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "Эти пользователи входят в приложения со слабыми, скомпрометированными или повторно используемыми паролями." + }, + "atRiskApplicationsDescription": { + "message": "Эти приложения имеют слабые, скомпрометированные или повторно используемые пароли." + }, + "atRiskMembersDescriptionWithApp": { + "message": "Эти пользователи входят в $APPNAME$ со слабыми, скомпрометированными или повторно используемыми паролями.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Всего участников" }, @@ -122,11 +158,17 @@ "totalApplications": { "message": "Всего приложений" }, + "unmarkAsCriticalApp": { + "message": "Снять отметку критического приложения" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Отметка критического приложения успешно снята" + }, "whatTypeOfItem": { "message": "Выберите тип элемента" }, "name": { - "message": "Название" + "message": "Имя" }, "uri": { "message": "URI" @@ -159,6 +201,9 @@ "notes": { "message": "Заметки" }, + "privateNote": { + "message": "Приватная заметка" + }, "note": { "message": "Заметка" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Перетащите для сортировки" }, + "dragToReorder": { + "message": "Перетащите для изменения порядка" + }, "cfTypeText": { "message": "Текстовое" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Изменить папку" }, + "editWithName": { + "message": "Изменить $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "Новая папка" + }, + "folderName": { + "message": "Название папки" + }, + "folderHintText": { + "message": "Создайте вложенную папку, добавив название родительской папки и символ \"/\". Пример: Сообщества/Форумы" + }, + "deleteFolderPermanently": { + "message": "Вы действительно хотите безвозвратно удалить эту папку?" + }, "baseDomain": { "message": "Основной домен", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Название элемента" }, - "cannotRemoveViewOnlyCollections": { - "message": "Вы не можете удалить коллекции с правами только на просмотр: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "напр.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Фильтр" }, - "moveSelectedToOrg": { - "message": "Переместить выбранное в организацию" - }, "deleteSelected": { "message": "Удалить выбранное" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Выбранные элементы перемещены в $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Элементы перемещены в $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "Нет" }, + "location": { + "message": "Местоположение" + }, "loginOrCreateNewAccount": { "message": "Войдите или создайте новый аккаунт для доступа к вашему защищенному хранилищу." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Войти в Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Введите код, отправленный на ваш email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Введите код из приложения-аутентификатора" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Нажмите на YubiKey для аутентификации" + }, "authenticationTimeout": { "message": "Таймаут аутентификации" }, "authenticationSessionTimedOut": { "message": "Сеанс аутентификации завершился по времени. Пожалуйста, попробуйте войти еще раз." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Подтвердите вашу личность" }, + "weDontRecognizeThisDevice": { + "message": "Мы не распознали это устройство. Введите код, отправленный на ваш email, чтобы подтвердить вашу личность." + }, + "continueLoggingIn": { + "message": "Продолжить вход" + }, + "whatIsADevice": { + "message": "Что такое устройство?" + }, + "aDeviceIs": { + "message": "Устройство - это уникальная установка приложения Bitwarden, в которой вы авторизовались. Переустановка, очистка данных приложения или очистка файлов cookie может привести к тому, что устройство будет отображаться несколько раз." + }, "logInInitiated": { "message": "Вход инициирован" }, + "logInRequestSent": { + "message": "Запрос отправлен" + }, "submit": { "message": "Подтвердить" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Введите email вашего аккаунта, и вам будет отправлена подсказка для пароля" }, - "passwordHint": { - "message": "Подсказка к паролю" - }, - "enterEmailToGetHint": { - "message": "Введите email учетной записи для получения подсказки к мастер-паролю." - }, "getMasterPasswordHint": { "message": "Получить подсказку к мастер-паролю" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "На ваше устройство отправлено уведомление." }, + "notificationSentDevicePart1": { + "message": "Разблокируйте Bitwarden на своем устройстве или " + }, + "areYouTryingToAccessYourAccount": { + "message": "Вы пытаетесь получить доступ к своему аккаунту?" + }, + "accessAttemptBy": { + "message": "Попытка доступа $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Подтвердить доступ" + }, + "denyAccess": { + "message": "Отказать в доступе" + }, + "notificationSentDeviceAnchor": { + "message": "веб-приложении" + }, + "notificationSentDevicePart2": { + "message": "Перед одобрением убедитесь, что фраза отпечатка совпадает с приведенной ниже." + }, + "notificationSentDeviceComplete": { + "message": "Разблокируйте Bitwarden на своем устройстве. Перед одобрением убедитесь, что фраза отпечатка совпадает с приведенной ниже." + }, "aNotificationWasSentToYourDevice": { "message": "На ваше устройство было отправлено уведомление" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Убедитесь, что ваш аккаунт разблокирован и фраза отпечатка совпадает с фразой на другом устройстве" - }, "versionNumber": { "message": "Версия $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Запомнить меня" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Не спрашивать на этом устройстве в течение 30 дней" + }, "sendVerificationCodeEmailAgain": { "message": "Отправить код подтверждения еще раз" }, "useAnotherTwoStepMethod": { "message": "Использовать другой метод двухэтапной аутентификации" }, + "selectAnotherMethod": { + "message": "Выбрать другой способ", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Использовать код восстановления" + }, "insertYubiKey": { "message": "Вставьте свой YubiKey в USB-порт компьютера и нажмите его кнопку." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Настройки двухэтапной аутентификации" }, + "selectTwoStepLoginMethod": { + "message": "Выбрать другой метод двухэтапной аутентификации" + }, "recoveryCodeDesc": { "message": "Потеряли доступ ко всем вариантам двухэтапной аутентификации? Используйте код восстановления, чтобы отключить ее для вашего аккаунта." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Перенесено из FIDO)" }, + "openInNewTab": { + "message": "Открыть в новой вкладке" + }, "emailTitle": { "message": "Email" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Выберите организацию, в которую вы хотите переместить этот элемент. При перемещении в организацию право собственности на элемент переходит к этой организации. Вы больше не будете прямым владельцем этого элемента после его перемещения." }, - "moveManyToOrgDesc": { - "message": "Выберите организацию, в которую вы хотите переместить эти элементы. При перемещении в организацию право собственности на элементы переходит к этой организации. Вы больше не будете прямым владельцем этих элементов после их перемещения." - }, "collectionsDesc": { "message": "Отредактируйте коллекции, с которыми совместно используется этот элемент. Данный элемент смогут видеть только пользователи организации, имеющие доступ к этим коллекциям." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "Вы выбрали $COUNT$ элемента(-ов). $MOVEABLE_COUNT$ элемента(-ов) могут быть перемещены в организацию, $NONMOVEABLE_COUNT$ не могут.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Код подтверждения (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Избегать неоднозначных символов", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Создать новый пароль" - }, "length": { "message": "Длина" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Пожалуйста, войдите снова." }, + "currentSession": { + "message": "Текущая сессия" + }, + "requestPending": { + "message": "Запрос в ожидании" + }, "logBackInOthersToo": { "message": "Пожалуйста, войдите снова. Если вы используете другие приложения Bitwarden, выполните на них выход и повторный вход." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Зона риска" }, - "dangerZoneDesc": { - "message": "Осторожно, эти действия необратимы!" - }, - "dangerZoneDescSingular": { - "message": "Будьте внимательны - это действие не обратимо!" - }, "deauthorizeSessions": { "message": "Деавторизовать сессии" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "В случае продолжения, ваша сессия будет завершена и вам будет предложено авторизоваться повторно. Вам также будет предложено выполнить двухэтапную аутентификацию, если она настроена. Сессии на других устройствах могут оставаться активными в течение одного часа." }, + "newDeviceLoginProtection": { + "message": "Авторизация с нового устройства" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Отключить защиту авторизации с нового устройства" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Включить защиту авторизации с нового устройства" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Продолжите, чтобы отключить письма от bitwarden отправляемые при авторизации с нового устройства." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Продолжите, чтобы включить письма от bitwarden отправляемые при авторизации с нового устройства." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "Если защита авторизации с нового устройства отключена, любой с вашим мастер-паролем может получить доступ к вашему аккаунту с любого устройства. Чтобы обезопасить аккаунт при отключении писем от bitwarden настройте двухэтапную аутентификацию." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "Изменения защиты авторизации сохранены" + }, "sessionsDeauthorized": { "message": "Все сессии деавторизованы" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "При включении двухэтапной аутентификации вы можете навсегда потерять доступ к вашей учетной записи Bitwarden. Код восстановления позволяет получить доступ к вашему аккаунту в случае, если вы больше не можете использовать свой обычный метод двухэтапной аутентификации (например, при потере устройства). Служба поддержки Bitwarden не сможет вам помочь, если вы потеряете доступ к своему аккаунту. Мы рекомендуем вам записать или распечатать код восстановления и хранить его в надежном месте." }, + "yourSingleUseRecoveryCode": { + "message": "Одноразовый код восстановления можно использовать для отключения двухэтапной аутентификации в случае потери доступа к провайдеру двухэтапной аутентификации. Bitwarden рекомендует записать код восстановления и хранить его в надежном месте." + }, "viewRecoveryCode": { "message": "Просмотреть код восстановления" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Управление" }, - "canManage": { - "message": "Может управлять" + "manageCollection": { + "message": "Управление коллекцией" + }, + "viewItems": { + "message": "Просмотр элементов" + }, + "viewItemsHidePass": { + "message": "Просмотр элементов, пароли скрыты" + }, + "editItems": { + "message": "Изменение элементов" + }, + "editItemsHidePass": { + "message": "Изменение элементов, пароли скрыты" }, "disable": { "message": "Отключить" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Отозвать доступ" }, + "revoke": { + "message": "Отозвать" + }, "twoStepLoginProviderEnabled": { "message": "Этот провайдер двухэтапной аутентификации включен для вашего аккаунта." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "Не удалось прочитать ключ безопасности. Попробуйте снова." }, - "twoFactorWebAuthnWarning": { - "message": "Из-за ограничений платформы WebAuthn нельзя использовать во всех приложениях Bitwarden. Вы должны настроить другого провайдера двухэтапной аутентификации, чтобы иметь возможность получить доступ к своей учетной записи, когда WebAuthn не может быть использован. Поддерживаемые платформы:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Веб-хранилище и расширения браузера на компьютере/ноутбуке с браузером с поддержкой WebAuthn (Chrome, Opera, Vivaldi или Firefox с включенным FIDO U2F)." + "twoFactorWebAuthnWarning1": { + "message": "Из-за ограничений платформы WebAuthn нельзя использовать во всех приложениях Bitwarden. Вы должны настроить другого провайдера двухэтапной аутентификации, чтобы иметь возможность получить доступ к своей учетной записи, когда WebAuthn не может быть использован." }, "twoFactorRecoveryYourCode": { "message": "Ваш код восстановления двухэтапной аутентификации Bitwarden" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "У вас осталось 1 приглашение." + }, + "inviteZeroEmailDesc": { + "message": "У вас осталось 0 приглашений." + }, "userUsingTwoStep": { "message": "Этот пользователь использует двухэтапную аутентификацию для защиты своего аккаунта." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Несвязанный SSO." + }, "unlinkedSsoUser": { "message": "Несвязанный SSO для пользователя $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Устройство" }, + "loginStatus": { + "message": "Статус авторизации" + }, + "firstLogin": { + "message": "Первый вход" + }, + "trusted": { + "message": "Доверенный" + }, + "needsApproval": { + "message": "Требуется одобрение" + }, + "areYouTryingtoLogin": { + "message": "Вы пытаетесь войти?" + }, + "logInAttemptBy": { + "message": "Попытка авторизации через $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Тип устройства" + }, + "ipAddress": { + "message": "IP-адрес" + }, + "confirmLogIn": { + "message": "Подтвердить вход" + }, + "denyLogIn": { + "message": "Запретить вход" + }, + "thisRequestIsNoLongerValid": { + "message": "Этот запрос больше не действителен." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Вход подтвержден для $EMAIL$ на $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "Вы отклонили попытку авторизации с другого устройства. Если это действительно были вы, попробуйте авторизоваться с этого устройства еще раз." + }, + "loginRequestHasAlreadyExpired": { + "message": "Запрос на вход истек." + }, + "justNow": { + "message": "Только что" + }, + "requestedXMinutesAgo": { + "message": "Запрошено $MINUTES$ мин назад", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Создание аккаунта" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "Вы используете неподдерживаемый браузер. Веб-хранилище может работать некорректно." }, + "youHaveAPendingLoginRequest": { + "message": "У вас есть незавершенный запрос на авторизацию с другого устройства." + }, + "reviewLoginRequest": { + "message": "Просмотр запроса на вход" + }, "freeTrialEndPromptCount": { "message": "Ваша бесплатная пробная версия заканчивается через $COUNT$ дней.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "Если вы не можете получить доступ к своему аккаунту с помощью двухэтапной аутентификации, для ее отключения вы можете использовать свой код восстановления." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Авторизуйтесь, используя одноразовый код восстановления. Это отключит всех провайдеров двухэтапной аутентификации вашего аккаунта." + }, "recoverAccountTwoStep": { "message": "Восстановить двухэтапную аутентификацию аккаунта" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Обновление ключа шифрования невозможно" }, + "editFieldLabel": { + "message": "Изменить $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Изменить порядок $LABEL$. Используйте клавиши курсора для перемещения элемента вверх или вниз.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ перемещено вверх, позиция $INDEX$ $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ перемещено вниз, позиция $INDEX$ $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "При обновлении ключа шифрования не удалось расшифровать папки. Чтобы продолжить обновление, папки необходимо удалить. При продолжении обновления элементы хранилища удалены не будут." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Установите требования к надежности мастер-пароля." }, + "passwordStrengthScore": { + "message": "Оценка надежности пароля $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Требуется двухэтапная аутентификация" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Установить требования к генератору паролей." }, - "passwordGeneratorPolicyInEffect": { - "message": "На настройки генератора влияют одна или несколько политик организации." - }, "masterPasswordPolicyInEffect": { "message": "Согласно одной или нескольким политикам организации необходимо, чтобы ваш мастер-пароль отвечал следующим требованиям:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Владельцы и администраторы организаций освобождены от применения этой политики." }, + "limitSendViews": { + "message": "Лимит просмотров" + }, + "limitSendViewsHint": { + "message": "Никто не сможет просмотреть эту Send после лимита просмотров.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "Осталось просмотров: $ACCESSCOUNT$", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Информация о Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Текст для отправки" + }, "sendTypeFile": { "message": "Файл" }, "sendTypeText": { "message": "Текст" }, + "sendPasswordDescV3": { + "message": "Добавьте опциональный пароль для доступа получателей к этой Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Новая Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Удалить Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Вы действительно хотите удалить эту Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "Какой это тип Send?", + "deleteSendPermanentConfirmation": { + "message": "Вы уверены, что хотите безвозвратно удалить эту Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Дата удаления" }, - "deletionDateDesc": { - "message": "Эта Send будет окончательно удалена в указанные дату и время.", + "deletionDateDescV2": { + "message": "С этой даты Send будет удалена навсегда.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Максимум обращений" }, - "maxAccessCountDesc": { - "message": "Если задано, пользователи больше не смогут получить доступ к этой Send, как только будет достигнуто максимальное количество обращений.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Текущих обращений" - }, - "sendPasswordDesc": { - "message": "По возможности запрашивать у пользователей пароль для доступа к этой Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Личные заметки об этой Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Отключено" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Вы уверены, что хотите удалить пароль?" }, - "hideEmail": { - "message": "Скрыть мой адрес email от получателей." - }, - "disableThisSend": { - "message": "Деактивировать эту Send, чтобы никто не мог получить к ней доступ.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Все Send’ы" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Ожидание удаления" }, + "hideTextByDefault": { + "message": "Скрыть текст по умолчанию" + }, "expired": { "message": "Срок истек" }, @@ -5161,13 +5441,6 @@ "message": "Всегда показывать email пользователя получателям при создании или редактировании Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendOptionsPolicyInEffect": { - "message": "На данный момент действуют следующие политики организации:" - }, - "sendDisableHideEmailInEffect": { - "message": "Пользователям не разрешается скрывать свой адрес email от получателей при создании или редактировании Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "modifiedPolicyId": { "message": "Изменена политика $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Отключить личное владение для пользователей организации" }, - "textHiddenByDefault": { - "message": "При доступе к Send скрывать текст по умолчанию", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Понятное имя для описания этой Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Текст, который отправится вместе с Send." - }, - "sendFileDesc": { - "message": "Файл, который отправится вместе с Send." - }, - "copySendLinkOnSave": { - "message": "Скопировать ссылку в буфер обмена после сохранения, чтобы поделиться этой Send." - }, - "sendLinkLabel": { - "message": "Ссылка на Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "Произошла ошибка при сохранении данных о сроках удаления и истечения." }, + "hideYourEmail": { + "message": "Скрыть ваш email от просматривающих." + }, "webAuthnFallbackMsg": { "message": "Для подтверждения 2ЭА нажмите кнопку ниже." }, "webAuthnAuthenticate": { "message": "Аутентификация WebAutn" }, + "readSecurityKey": { + "message": "Считать ключ безопасности" + }, + "awaitingSecurityKeyInteraction": { + "message": "Ожидание взаимодействия с ключом безопасности..." + }, "webAuthnNotSupported": { "message": "WebAuthn не поддерживается в этом браузере." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Ошибка" }, + "decryptionError": { + "message": "Ошибка расшифровки" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden не удалось расшифровать элемент(ы) хранилища, перечисленные ниже." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Обратитесь в службу поддержки,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "чтобы избежать дополнительной потери данных.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Для управления пользователями также необходимо предоставить разрешение на управление восстановлением аккаунта" }, @@ -6516,15 +6791,6 @@ "message": "Генератор", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "Что вы хотите сгенерировать?" - }, - "passwordType": { - "message": "Тип пароля" - }, - "regenerateUsername": { - "message": "Пересоздать имя пользователя" - }, "generateUsername": { "message": "Создать имя пользователя" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Тип имени пользователя" - }, "plusAddressedEmail": { "message": "Субадресованные email", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Использовать общий email домена." }, + "useThisEmail": { + "message": "Использовать этот email" + }, "random": { "message": "Случайно", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ отклонил(а) ваш запрос. Обратитесь за помощью к своему провайдеру.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ отклонил(а) ваш запрос: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Не удалось получить скрытый идентификатор email аккаунта $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Имя хоста", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "Токен доступа к API" - }, "deviceVerification": { "message": "Проверка устройства" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Для вашего аккаунта требуется двухэтапная аутентификация Duo." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Для вашего аккаунта требуется двухэтапная аутентификация Duo. Выполните следующие действия, чтобы завершить авторизацию." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Следуйте указаниям ниже, чтобы завершить авторизацию." + }, "launchDuo": { "message": "Запустить Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Количество пользователей" }, - "loggingInAs": { - "message": "Войти как" - }, - "notYou": { - "message": "Не вы?" - }, "pickAnAvatarColor": { "message": "Выбрать цвет аватара" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "Нет коллекций" }, - "canView": { - "message": "Может просматривать" - }, - "canViewExceptPass": { - "message": "Может просматривать, кроме паролей" - }, - "canEdit": { - "message": "Может редактировать" - }, - "canEditExceptPass": { - "message": "Может редактировать, кроме паролей" - }, "noCollectionsAdded": { "message": "Нет добавленных коллекций" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Запросить одобрение администратора" }, - "approveWithMasterPassword": { - "message": "Одобрить с мастер-паролем" - }, "trustedDeviceEncryption": { "message": "Шифрование доверенного устройства" }, "trustedDevices": { "message": "Доверенные устройства" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "После аутентификации данные хранилища пользователей станут расшифровываться с помощью ключа, хранящегося на их устройстве. При", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Пользователям не нужен мастер-пароль при авторизации с помощью SSO. Мастер-пароль будет заменен ключом шифрования, хранящимся на устройстве, что сделает это устройство доверенным. Первое устройство, на котором участник создает аккаунт и авторизуется, будет доверенным. Новые устройства должны быть одобрены существующим доверенным устройством или администратором.", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "использовании данной опции будут включены политика единой организации,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": "политика", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "SSO", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "Требуется SSO", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "и политика", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "администрирования восстановления аккаунтов", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "с автоматической регистрацией.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "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.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Права доступа организации были обновлены, требуется установить мастер-пароль.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Одобрить запрос" }, + "deviceApproved": { + "message": "Устройство одобрено" + }, + "deviceRemoved": { + "message": "Устройство удалено" + }, + "removeDevice": { + "message": "Удалить устройство" + }, + "removeDeviceConfirmation": { + "message": "Вы уверены, что хотите удалить это устройство?" + }, "noDeviceRequests": { "message": "Нет запросов устройств" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Ваш запрос был отправлен администратору." }, - "youWillBeNotifiedOnceApproved": { - "message": "Вас уведомят об одобрении." - }, "troubleLoggingIn": { "message": "Не удалось войти?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Ограничить удаление коллекций владельцам и администраторам" }, + "limitItemDeletionDesc": { + "message": "Ограничить удаление элементов для пользователей с разрешением 'Может управлять'" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Владельцы и администраторы могут управлять всеми коллекциями и элементами" }, @@ -8494,9 +8778,6 @@ "message": "URL собственного сервера", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Псевдоним домена" - }, "alreadyHaveAccount": { "message": "Уже зарегистрированы?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "У вас нет доступа к управлению этой коллекцией." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Недостаточно полномочий для управления" + "grantManageCollectionWarningTitle": { + "message": "Отсутствуют разрешения на управление коллекцией" }, - "grantAddAccessCollectionWarning": { - "message": "Предоставить возможность управлять разрешениями коллекций, включая их удаление." + "grantManageCollectionWarning": { + "message": "Представить разрешения на управление коллекцией, включая ее удаление." }, "grantCollectionAccess": { "message": "Предоставить группам или участникам доступ к этой коллекции." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Настройте управление устройствами для Bitwarden, используя руководство по внедрению для вашей платформы." }, + "deviceIdMissing": { + "message": "ID устройства отсутствует" + }, + "deviceTypeMissing": { + "message": "Тип устройства отсутствует" + }, + "deviceCreationDateMissing": { + "message": "Дата создания устройства отсутствует" + }, + "desktopRequired": { + "message": "Требуется компьютер" + }, + "reopenLinkOnDesktop": { + "message": "Откройте эту ссылку из email на вашем компьютере." + }, "integrationCardTooltip": { "message": "Запустить руководство по внедрению $INTEGRATION$.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "в месяц за пользователя" }, + "monthPerMemberBilledAnnually": { + "message": "месяц за пользователя в год" + }, "seats": { "message": "Места" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Узнайте больше об API Bitwarden" }, + "fileSend": { + "message": "Файловая Send" + }, "fileSends": { "message": "Файловая Send" }, + "textSend": { + "message": "Текстовая Send" + }, "textSends": { "message": "Текстовая Send" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Алгоритм ключа" }, + "sshPrivateKey": { + "message": "Приватный ключ" + }, + "sshPublicKey": { + "message": "Публичный ключ" + }, + "sshFingerprint": { + "message": "Отпечаток" + }, "sshKeyFingerprint": { "message": "Отпечаток" }, @@ -9774,10 +10088,6 @@ "message": "Включить специальные символы", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Добавить вложение" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Код дескриптора" }, + "cannotRemoveViewOnlyCollections": { + "message": "Вы не можете удалить коллекции с правами только на просмотр: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Важное уведомление" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Удалить участников" }, + "devices": { + "message": "Устройства" + }, + "deviceListDescription": { + "message": "Ваш аккаунт был авторизован на каждом из перечисленных ниже устройств. Если устройство вам не знакомо, удалите его сейчас." + }, + "deviceListDescriptionTemp": { + "message": "Ваш аккаунт авторизован на перечисленных ниже устройствах." + }, "claimedDomains": { "message": "Зарегистрированные домены" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Название организации не может превышать 50 символов." }, - "resellerRenewalWarning": { - "message": "Ваша подписка скоро будет продлена. Чтобы гарантировать непрерывность сервиса, свяжитесь с $RESELLER$ для подтверждения продления до $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "Введенный пароль неверен." + }, + "importSshKey": { + "message": "Импорт" + }, + "confirmSshKeyPassword": { + "message": "Подтвердите пароль" + }, + "enterSshKeyPasswordDesc": { + "message": "Введите пароль для ключа SSH." + }, + "enterSshKeyPassword": { + "message": "Введите пароль" + }, + "invalidSshKey": { + "message": "Ключ SSH недействителен" + }, + "sshKeyTypeUnsupported": { + "message": "Тип ключа SSH не поддерживается" + }, + "importSshKeyFromClipboard": { + "message": "Импорт ключа из буфера обмена" + }, + "sshKeyImported": { + "message": "Ключ SSH успешно импортирован" + }, + "copySSHPrivateKey": { + "message": "Скопировать приватный ключ" + }, + "openingExtension": { + "message": "Открытие расширения для браузера Bitwarden" + }, + "somethingWentWrong": { + "message": "Что-то пошло не так..." + }, + "openingExtensionError": { + "message": "У нас возникли проблемы с открытием расширения для браузера Bitwarden. Нажмите на кнопку, чтобы открыть его сейчас." + }, + "openExtension": { + "message": "Открыть расширение" + }, + "doNotHaveExtension": { + "message": "У вас нет расширения браузера Bitwarden?" + }, + "installExtension": { + "message": "Установить расширение" + }, + "openedExtension": { + "message": "Открыто расширение браузера" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Расширение для браузера Bitwarden успешно открыто. Теперь вы можете просмотреть свои пароли, подверженные риску." + }, + "openExtensionManuallyPart1": { + "message": "У нас возникли проблемы с открытием расширения для браузера Bitwarden. Нажмите на значок Bitwarden", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "на панели инструментов.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Ваша подписка скоро будет продлена. Чтобы обеспечить непрерывность сервиса, свяжитесь с $RESELLER$ для подтверждения продления до $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "Счет за вашу подписку был выставлен $ISSUED_DATE$. Чтобы гарантировать непрерывность сервиса, свяжитесь с $RESELLER$, чтобы подтвердить продление подписки до $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "Счет за вашу подписку был выставлен $ISSUED_DATE$. Чтобы обеспечить непрерывность сервиса, свяжитесь с $RESELLER$, чтобы подтвердить продление подписки до $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "Счет за вашу подписку не был оплачен. Чтобы гарантировать непрерывность сервиса, свяжитесь с $RESELLER$ для подтверждения продления до $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "Счет за вашу подписку не был оплачен. Чтобы обеспечить непрерывность сервиса, свяжитесь с $RESELLER$ для подтверждения продления до $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Подписка организации перезапущена" + }, + "restartSubscription": { + "message": "Перезапустить подписку" + }, + "suspendedManagedOrgMessage": { + "message": "Обратитесь за помощью к $PROVIDER$.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Администраторы теперь могут удалять аккаунты пользователей, принадлежащие зарегистрированному домену." + }, + "deleteManagedUserWarningDesc": { + "message": "Это действие удалит аккаунт пользователя, включая все элементы в его хранилище. Это действие заменяет предыдущее действие Удалить." + }, + "deleteManagedUserWarning": { + "message": "Удалить - это новое действие!" + }, + "seatsRemaining": { + "message": "У вас осталось $REMAINING$ мест из $TOTAL$, закрепленных за этой организацией. Обратитесь к своему провайдеру для управления подпиской.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Существующая организация" + }, + "selectOrganizationProviderPortal": { + "message": "Выберите организацию для добавления на ваш портал провайдеров." + }, + "noOrganizations": { + "message": "Нет организаций для отображения" + }, + "yourProviderSubscriptionCredit": { + "message": "Ваш провайдер получит кредит на любое время, оставшееся в подписке организации." + }, + "doYouWantToAddThisOrg": { + "message": "Вы хотите добавить эту организацию в $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Добавлена ​​существующая организация" + }, + "assignedExceedsAvailable": { + "message": "Количество назначенных мест превышает количество доступных." + }, + "changeAtRiskPassword": { + "message": "Изменить пароль, подверженный риску" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Отключить разблокировку PIN-кодом" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Не разрешать пользователям разблокировать свои аккаунты с помощью PIN-кода." + }, + "limitedEventLogs": { + "message": "Планы $PRODUCT_TYPE$ не имеют доступа к журналам текущих событий", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Получите полный доступ к журналам событий организации, перейдя на план Teams или Enterprise." + }, + "upgradeEventLogTitle": { + "message": "Переудите на более высокий тариф для просмотра журнала реальных событий" + }, + "upgradeEventLogMessage": { + "message": "Эти события являются лишь примерами и не отражают текущих событий вашей организации Bitwarden." + }, + "cannotCreateCollection": { + "message": "В бесплатных организациях может быть до 2 коллекций. Перейдите на платный план, чтобы добавить больше коллекций." } } diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index 0649f83f519..fc79da1352b 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Critical applications" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "What type of item is this?" }, @@ -159,6 +201,9 @@ "notes": { "message": "සටහන්" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Note" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Drag to sort" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Text" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "බහාලුම සංස්කරණය" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "Base domain", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "උදා.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filter" }, - "moveSelectedToOrg": { - "message": "Move selected to organization" - }, "deleteSelected": { "message": "Delete selected" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "නැහැ" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "Log in or create a new account to access your secure vault." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Submit" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Lost access to all of your two-step login providers? Use your recovery code to turn off all two-step login providers from your account." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Migrated from FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "වි-තැපෑල" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." }, - "moveManyToOrgDesc": { - "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved." - }, "collectionsDesc": { "message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Verification code (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Regenerate password" - }, "length": { "message": "Length" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Danger zone" }, - "dangerZoneDesc": { - "message": "Careful, these actions are not reversible!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Deauthorize sessions" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Proceeding will also log you out of your current session, requiring you to log back in. You will also be prompted for two-step login again, if set up. Active sessions on other devices may continue to remain active for up to one hour." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "All sessions deauthorized" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Setting up two-step login can permanently lock you out of your Bitwarden account. A recovery code allows you to access your account in the event that you can no longer use your normal two-step login provider (example: you lose your device). Bitwarden support will not be able to assist you if you lose access to your account. We recommend you write down or print the recovery code and keep it in a safe place." }, + "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." + }, "viewRecoveryCode": { "message": "View recovery code" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Manage" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Turn off" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Revoke access" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "This two-step login provider is active on your account." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "There was a problem reading the security key. Try again." }, - "twoFactorWebAuthnWarning": { - "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used. Supported platforms:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Web vault and browser extensions on a desktop/laptop with a WebAuthn supported browser (Chrome, Opera, Vivaldi, or Firefox with FIDO U2F turned on)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Your Bitwarden two-step login recovery code" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "If you cannot access your account through your normal two-step login methods, you can use your two-step login recovery code to turn off all two-step providers on your account." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Recover account two-step login" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Set requirements for master password strength." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Require two-step login" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Set requirements for password generator." }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." - }, "masterPasswordPolicyInEffect": { "message": "One or more organization policies require your master password to meet the following requirements:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Organization owners and admins are exempt from this policy's enforcement." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "File" }, "sendTypeText": { "message": "Text" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Delete Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Are you sure you want to delete this Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "What type of Send is this?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maximum access count" }, - "maxAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Disabled" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Are you sure you want to remove the password?" }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, - "disableThisSend": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "All Sends" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Pending deletion" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Expired" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "The following organization policies are currently in effect:" - }, - "sendDisableHideEmailInEffect": { - "message": "Users are not allowed 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." - }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Remove individual ownership for organization users" }, - "textHiddenByDefault": { - "message": "When accessing the Send, hide the text by default", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to Send." - }, - "sendFileDesc": { - "message": "The file you want to Send." - }, - "copySendLinkOnSave": { - "message": "Copy the link to share this Send to my clipboard upon save." - }, - "sendLinkLabel": { - "message": "Send link", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "To verify your 2FA please click the button below." }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn is not supported in this browser." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, - "regenerateUsername": { - "message": "Regenerate username" - }, "generateUsername": { "message": "Generate username" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Username type" - }, "plusAddressedEmail": { "message": "Plus addressed email", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Hostname", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API access token" - }, "deviceVerification": { "message": "Device verification" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Number of users" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "pickAnAvatarColor": { "message": "Pick an avatar color" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "No collection" }, - "canView": { - "message": "Can view" - }, - "canViewExceptPass": { - "message": "Can view, except passwords" - }, - "canEdit": { - "message": "Can edit" - }, - "canEditExceptPass": { - "message": "Can edit, except passwords" - }, "noCollectionsAdded": { "message": "No collections added" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domain" - }, "alreadyHaveAccount": { "message": "Already have an account?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index dbffa25048c..fdf57456a4c 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Kritické aplikácie" }, + "noCriticalAppsAtRisk": { + "message": "Nie sú ohrozené žiadne kritické aplikácie" + }, "accessIntelligence": { "message": "Prehľad o prístupe" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "Ohrozených členov" }, + "atRiskMembersWithCount": { + "message": "Ohrození členovia ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Ohrozené aplikácie ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "Títo členovia sa prihlasujú do aplikácií so slabým, uniknutým alebo viacnásobne použitým heslom." + }, + "atRiskApplicationsDescription": { + "message": "Tieto aplikácie majú slabé, uniknuté alebo opätovne použité heslá." + }, + "atRiskMembersDescriptionWithApp": { + "message": "Títo členovia sa prihlasujú do $APPNAME$ so slabým, uniknutým alebo viacnásobne použitým heslom.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Celkový počet členov" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Všetkých aplikácii" }, + "unmarkAsCriticalApp": { + "message": "Zrušiť označenie aplikácie za kritickú" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Označenie aplikácie za kritickú úspešne zrušené" + }, "whatTypeOfItem": { "message": "Aký typ položky to je?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Poznámky" }, + "privateNote": { + "message": "Súkromná poznámka" + }, "note": { "message": "Poznámka" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Zoradiť presúvaním" }, + "dragToReorder": { + "message": "Potiahnutím zmeníte poradie" + }, "cfTypeText": { "message": "Text" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Upraviť priečinok" }, + "editWithName": { + "message": "Upraviť $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "Nový priečinok" + }, + "folderName": { + "message": "Názov priečinka" + }, + "folderHintText": { + "message": "Vnorte priečinok pridaním názvu nadradeného priečinka a znaku \"/\". Príklad: Sociálne siete/Fóra" + }, + "deleteFolderPermanently": { + "message": "Naozaj chcete natrvalo odstrániť tento priečinok?" + }, "baseDomain": { "message": "Základná doména", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Názov položky" }, - "cannotRemoveViewOnlyCollections": { - "message": "Zbierky, ktoré môžete len zobraziť nemôžete odstrániť: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "napr.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filter" }, - "moveSelectedToOrg": { - "message": "Presunúť vybraté do organizácie" - }, "deleteSelected": { "message": "Odstrániť vybrané" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Vybraté položky boli presunuté do $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Položky presunuté do $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "Nie" }, + "location": { + "message": "Poloha" + }, "loginOrCreateNewAccount": { "message": "Prihláste sa, alebo vytvorte nový účet pre prístup k vášmu bezpečnému trezoru." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Prihlásenie do Bitwardenu" }, + "enterTheCodeSentToYourEmail": { + "message": "Zadajte kód zaslaný na váš e-mail" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Zadajte kód z overovacej aplikácie" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Stlačte YubiKey na overenie" + }, "authenticationTimeout": { "message": "Časový limit overenia" }, "authenticationSessionTimedOut": { "message": "Relácia overovania skončila. Znovu spustite proces prihlásenia." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Overte svoju totožnosť" }, + "weDontRecognizeThisDevice": { + "message": "Nespoznávame toto zariadenie. Pre overenie vašej identity zadajte kód ktorý bol zaslaný na váš email." + }, + "continueLoggingIn": { + "message": "Pokračovať v prihlasovaní" + }, + "whatIsADevice": { + "message": "Čo je zariadenie?" + }, + "aDeviceIs": { + "message": "Zariadenie je jednotlivá inštalácia aplikácie Bitwarden, do ktorej ste sa prihlásili. Preinštalovanie, vymazanie údajov aplikácie alebo vymazanie súborov cookie môže mať za následok, že sa zariadenie objaví viackrát." + }, "logInInitiated": { "message": "Iniciované prihlásenie" }, + "logInRequestSent": { + "message": "Požiadavka bola odoslaná" + }, "submit": { "message": "Potvrdiť" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Zadajte e-mailovú adresu účtu a zašleme vám nápoveď k heslu" }, - "passwordHint": { - "message": "Nápoveď k heslu" - }, - "enterEmailToGetHint": { - "message": "Zadajte emailovú adresu na zaslanie nápovede pre vaše hlavné heslo." - }, "getMasterPasswordHint": { "message": "Získať nápoveď k hlavnému heslu" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "Do vášho zariadenia bolo odoslané upozornenie." }, + "notificationSentDevicePart1": { + "message": "Odomknúť Bitwarden vo svojom zariadení alebo vo " + }, + "areYouTryingToAccessYourAccount": { + "message": "Snažíte sa získať prístup k svojmu účtu?" + }, + "accessAttemptBy": { + "message": "Pokus o prístup z $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Potvrďte prístup" + }, + "denyAccess": { + "message": "Zamietnuť prístup" + }, + "notificationSentDeviceAnchor": { + "message": "webovej aplikácii" + }, + "notificationSentDevicePart2": { + "message": "Pred schválením sa uistite, že sa odtlačok prístupovej frázy zhoduje s tou uvedenou nižšie." + }, + "notificationSentDeviceComplete": { + "message": "Odomknite Bitwarden na vašom zariadení. Pred schválením sa uistite, že sa odtlačok prístupovej frázy zhoduje s tým uvedeným nižšie." + }, "aNotificationWasSentToYourDevice": { "message": "Do vášho zariadenia bolo odoslané upozornenie" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Uistite sa, že je váš účet odomknutý a fráza odtlačku prsta sa zhoduje s frázou na druhom zariadení" - }, "versionNumber": { "message": "Verzia $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Zapamätaj si ma" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Nepýtať sa znova na tomto zariadení 30 dní" + }, "sendVerificationCodeEmailAgain": { "message": "Znovu zaslať overovací kód emailom" }, "useAnotherTwoStepMethod": { "message": "Použiť inú dvojstupňovú metódu prihlásenia" }, + "selectAnotherMethod": { + "message": "Vyberte iný spôsob", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Použiť obnovovací kód" + }, "insertYubiKey": { "message": "Vložte váš YubiKey do USB portu počítača a stlačte jeho tlačidlo." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Možnosti dvojstupňového prihlásenia" }, + "selectTwoStepLoginMethod": { + "message": "Vyberte metódu dvojstupňového prihlásenia" + }, "recoveryCodeDesc": { "message": "Stratili ste prístup ku všetkým vašim dvojstupňovým poskytovateľom? Použite váš záchranný kód pre vypnutie všetkých poskytovateľov vo vašom účte." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Migrované z FIDO)" }, + "openInNewTab": { + "message": "Otvoriť v novej karte" + }, "emailTitle": { "message": "Email" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Vyberte organizáciu, do ktorej chcete presunúť túto položku. Presunom do organizácie sa vlastníctvo položky prenáša na túto organizáciu. Po presunutí už nebudete priamym vlastníkom danej položky." }, - "moveManyToOrgDesc": { - "message": "Vyberte organizáciu, do ktorej chcete presunúť tieto položky. Presunom do organizácie sa vlastníctvo položky prenáša na túto organizáciu. Po presunutí už nebudete priamym vlastníkom daných položiek." - }, "collectionsDesc": { "message": "Upravte zbierky s ktorými bude táto položka zdieľaná. Iba členovia organizácie s prístupom k vybraným zbierkam budú vidieť túto položku." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "Vybrali ste $COUNT$ položku(iek). $MOVEABLE_COUNT$ položka(iek) môže(u) byť presunuté do organizácie, $NONMOVEABLE_COUNT$ nemôže(u).", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Overovací kód (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Vyhnúť sa zameniteľným znakom", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Vygenerovať nové heslo" - }, "length": { "message": "Dĺžka" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Prosím, opäť sa prihláste." }, + "currentSession": { + "message": "Aktuálna relácia" + }, + "requestPending": { + "message": "Žiadosť čaká na spracovanie" + }, "logBackInOthersToo": { "message": "Prosím odhláste sa. Ak používate iné Bitwarden aplikácie, odhláste sa a opäť sa prihláste aj v nich." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Riziková zóna" }, - "dangerZoneDesc": { - "message": "Opatrne, tieto zmeny nemožno vrátiť späť!" - }, - "dangerZoneDescSingular": { - "message": "Opatrne, túto zmenu nemožno vrátiť späť!" - }, "deauthorizeSessions": { "message": "Odhlásiť sedenia" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Ak budete pokračovať, budete tiež odhlásený z vášho súčasného sedenia a budete sa musieť opäť prihlásiť. Tiež budete opäť požiadaný o dvojstupňové prihlásenie ak ho máte zapnuté. Aktívne sedenia na iných zariadeniach môžu ostať aktívne až po dobu jednej hodiny." }, + "newDeviceLoginProtection": { + "message": "Prihlásenie z nového zariadenia" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Vypnúť ochranu pri prihlásení z nového zariadenia" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Zapnúť ochranu pri prihlásení z nového zariadenia" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Ak chcete vypnúť overovacie emaily, ktoré Bitwarden posiela keď sa prihlasujete z nového zariadenia, pokračujte nižšie." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Ak chcete aby Bitwarden posielal overovacie emaily keď sa prihlasujete z nového zariadenia, pokračujte nižšie." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "S vypnutou ochranou pri prihlásení z nového zariadenia môže ktokoľvek, kto pozná vaše hlavne heslo, pristupovať k vášmu účtu z ľubovoľného zariadenia. Ak chcete ochrániť váš účet bez overovacích emailov, nastavte si dvojstupňové overovanie pri prihlasovaní." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "Zmeny v nastavení ochrany prihlasovania z nového zariadenia boli uložené" + }, "sessionsDeauthorized": { "message": "Všetky sedenia odhlásené" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Zapnutie dvojstupňového prihlásenia vás môže natrvalo vymknúť z vášho Bitwarden účtu. Záchranný kód umožňuje prístup k vášmu kontu v prípade že už nemôžete použiť svoj normálny dvojstupňový spôsob overenia. (napríklad ak stratíte zariadenie) Zákaznícka podpora nebude schopná pomôcť vám ak stratíte prístup k účtu. Preto vám odporúčame zapísať si, alebo si vytlačiť záchranný kód a uložiť ho na bezpečnom mieste." }, + "yourSingleUseRecoveryCode": { + "message": "Váš jednorázový záchranný kód sa dá použiť na vypnutie dvojstupňového prihlasovania ak ste stratili pristúp k jeho poskytovateľovi. Bitwarden odporúča, aby ste si záchranný kód zapísali a odložili na bezpečné miesto." + }, "viewRecoveryCode": { "message": "Zobraziť záchranný kód" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Spravovať" }, - "canManage": { - "message": "Môže spravovať" + "manageCollection": { + "message": "Spravovať zbierku" + }, + "viewItems": { + "message": "Zobraziť položky" + }, + "viewItemsHidePass": { + "message": "Zobraziť položky, skryté heslá" + }, + "editItems": { + "message": "Upraviť položky" + }, + "editItemsHidePass": { + "message": "Upraviť položky, skryté heslá" }, "disable": { "message": "Vypnúť" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Zrušiť prístup" }, + "revoke": { + "message": "Odvolať" + }, "twoStepLoginProviderEnabled": { "message": "Tento poskytovateľ overenia je povolený pre váš účet." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "Problém pri čítaní bezpečnostného kľúča. Skúste to znova." }, - "twoFactorWebAuthnWarning": { - "message": "Vzhľadom na obmedzenia platform, WebAuthn nemôže byť použitý vo všetkých Bitwarden aplikáciách. Mali by ste povoliť inú formu dvojitého overenia, aby ste sa mohli prihlásiť k svojmu účtu ak nie je možné použiť WebAuthn. Podporované platformy:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Webový trezor a rozšírenia prehliadača na pracovnej stanici s prehliadačom podporujúcim WebAuthn (Chrome, Opera, Vivaldi, alebo Firefox so zapnutou podporou FIDO U2F)." + "twoFactorWebAuthnWarning1": { + "message": "Vzhľadom na obmedzenia platforiem, WebAuthn nemôže byť použitý vo všetkých Bitwarden aplikáciách. Mali by ste nastaviť inú formu dvojitého overenia, aby ste sa mohli prihlásiť k svojmu účtu ak nie je možné použiť WebAuthn." }, "twoFactorRecoveryYourCode": { "message": "Váš Bitwarden záchranný kód pre dvojstupňové overovanie" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Ostáva vám 1 pozvánka." + }, + "inviteZeroEmailDesc": { + "message": "Ostáva vám 0 pozvánok." + }, "userUsingTwoStep": { "message": "Tento používateľ používa dvojstupňové overovanie aby si zabezpečil konto." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Odpojené SSO." + }, "unlinkedSsoUser": { "message": "SSO odpojené pre používateľa $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Zariadenie" }, + "loginStatus": { + "message": "Stav prihlásenia" + }, + "firstLogin": { + "message": "Prvé prihlásenie" + }, + "trusted": { + "message": "Dôveryhodné" + }, + "needsApproval": { + "message": "Potrebuje súhlas" + }, + "areYouTryingtoLogin": { + "message": "Snažíte sa prihlásiť?" + }, + "logInAttemptBy": { + "message": "Pokus o prihlásenie pomocou $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Typ zariadenia" + }, + "ipAddress": { + "message": "IP adresa" + }, + "confirmLogIn": { + "message": "Potvrdiť prihlásenie" + }, + "denyLogIn": { + "message": "Odmietnuť prihlásenie" + }, + "thisRequestIsNoLongerValid": { + "message": "Táto žiadosť už nie je platná." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Potvrdené prihlásenie pomocou $EMAIL$ na $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "Odmietli ste pokus o prihlásenie z iného zariadenia. Ak ste to boli naozaj vy, skúste sa prihlásiť pomocou zariadenia znova." + }, + "loginRequestHasAlreadyExpired": { + "message": "Platnosť žiadosti o prihlásenie už vypršala." + }, + "justNow": { + "message": "Práve teraz" + }, + "requestedXMinutesAgo": { + "message": "Vyžiadané pred $MINUTES$ minútami", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "Používate nepodporovaný prehliadač. Webový trezor nemusí úplne fungovať." }, + "youHaveAPendingLoginRequest": { + "message": "Máte čakajúcu požiadavku na prihlásenie z iného zariadenia." + }, + "reviewLoginRequest": { + "message": "Skontrolovať požiadavku o prihlásenie" + }, "freeTrialEndPromptCount": { "message": "Vaše bezplatné skúšobné obdobie vyprší o $COUNT$ dní.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "Ak sa nemôžete dostať k svojmu účtu prostredníctvom normálneho dvojstupňového prihlásenia, môžete použiť záchranný kód a vypnúť dvojstupňové prihlasovanie do vášho účtu." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Nižšie sa prihláste svojím jednorazovým záchranným kódom. Vypnete tým všetky metódy dvojstupňového prihlasovania vášho účtu." + }, "recoverAccountTwoStep": { "message": "Získať prístup k účtu s dvojstupňovým prihlásením" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Aktualizácia šifrovacieho kľúča nemôže pokračovať" }, + "editFieldLabel": { + "message": "Upraviť $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Zmeniť poradie $LABEL$. Na presun položky hore alebo dole použite klávesy so šípkami.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ presunuté vyššie, pozícia $INDEX$ z $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ presunuté nižšie, pozícia $INDEX$ z $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "Pri aktualizácii šifrovacieho kľúča nebolo možné dešifrovať vaše priečinky. Ak chcete pokračovať v aktualizácii, vaše priečinky sa musia odstrániť. Ak budete pokračovať, nebudú odstránené žiadne položky trezora." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Nastavte minimálne požiadavky pre silu hlavného hesla." }, + "passwordStrengthScore": { + "message": "Sila hesla $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Vyžadovať dvojstupňové prihlásenie" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Zvoľte minimálne požiadavky pre nastavenie generátora hesiel." }, - "passwordGeneratorPolicyInEffect": { - "message": "Jedno alebo viac nastavení organizácie ovplyvňujú vaše nastavenia generátora." - }, "masterPasswordPolicyInEffect": { "message": "Jedno alebo viac pravidiel organizácie požadujú aby vaše hlavné heslo spĺňalo nasledujúce požiadavky:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Vlastníci a administrátori organizácie sú vyňatí z uplatnenia tohto pravidla." }, + "limitSendViews": { + "message": "Obmedziť zobrazenia" + }, + "limitSendViewsHint": { + "message": "Po dosiahnutí limitu si tento Send nemôže nikto zobraziť.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "Zostáva $ACCESSCOUNT$ zobrazení", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Podrobnosti o Sende", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text, ktorý chcete zdieľať" + }, "sendTypeFile": { "message": "Súbor" }, "sendTypeText": { "message": "Text" }, + "sendPasswordDescV3": { + "message": "Pridajte voliteľné heslo pre príjemcov na prístup k tomuto Sendu.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Vytvoriť nový Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Zmazať Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Naozaj chcete odstrániť tento Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "Aký typ Send to je?", + "deleteSendPermanentConfirmation": { + "message": "Naozaj chcete natrvalo odstrániť tento Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Dátum vymazania" }, - "deletionDateDesc": { - "message": "Odoslanie bude natrvalo odstránené v zadaný dátum a čas.", + "deletionDateDescV2": { + "message": "Send bude natrvalo odstránený v tento deň.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maximálny počet prístupov" }, - "maxAccessCountDesc": { - "message": "Ak je nastavené, používatelia už nebudú mať prístup k tomuto Sendu po dosiahnutí maximálneho počtu prístupov.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Súčasný počet prístupov" - }, - "sendPasswordDesc": { - "message": "Voliteľne môžete vyžadovať heslo pre používateľov na prístup k tomuto odoslaniu.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Zabezpečená poznámka o tomto Odoslaní.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Vypnuté" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Naozaj chcete odstrániť heslo?" }, - "hideEmail": { - "message": "Skryť moju emailovú adresu pred príjemcami." - }, - "disableThisSend": { - "message": "Vypnúť tento Send, aby k nemu nikto nemal prístup.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Všetky Sendy" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Čakajúce odstránenie" }, + "hideTextByDefault": { + "message": "V predvolenom nastavení skryť text" + }, "expired": { "message": "Expirované" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "V súčasnosti platia nasledujúce pravidlá organizácie:" - }, - "sendDisableHideEmailInEffect": { - "message": "Používatelia nemajú povolené 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." - }, "modifiedPolicyId": { "message": "Upravená politika $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Zakázať osobné vlastníctvo pre používateľov organizácie" }, - "textHiddenByDefault": { - "message": "Pri prístupe k Odoslaniu, predvolene skryť text", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Priateľský názov pre popísanie tohto Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Text, ktorý chcete odoslať." - }, - "sendFileDesc": { - "message": "Súbor, ktorý chcete odoslať." - }, - "copySendLinkOnSave": { - "message": "Kopírovať odkaz na zdieľanie tohto Send do schránky počas ukladania." - }, - "sendLinkLabel": { - "message": "Odkaz na Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "Pri ukladaní dátumov odstránenia a vypršania platnosti sa vyskytla chyba." }, + "hideYourEmail": { + "message": "Skryť moju e-mailovú adresu pri zobrazení." + }, "webAuthnFallbackMsg": { "message": "Na overenie 2FA, prosím, kliknite na tlačidlo nižšie." }, "webAuthnAuthenticate": { "message": "Overiť cez WebAuthn" }, + "readSecurityKey": { + "message": "Prečítať bezpečnostný kľúč" + }, + "awaitingSecurityKeyInteraction": { + "message": "Čaká sa na interakciu s bezpečnostným kľúčom..." + }, "webAuthnNotSupported": { "message": "Tento prehliadač nepodporuje WebAuthn." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Chyba" }, + "decryptionError": { + "message": "Chyba dešifrovania" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nedokázal dešifrovať nižšie uvedené položky trezoru." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktujte zákaznícku podporu,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "aby ste predišli ďalším stratám údajov.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Generátor", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "Čo by ste chceli vygenerovať?" - }, - "passwordType": { - "message": "Typ hesla" - }, - "regenerateUsername": { - "message": "Vygenerovať nové používateľské meno" - }, "generateUsername": { "message": "Vygenerovať používateľské meno" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Typ používateľského mena" - }, "plusAddressedEmail": { "message": "E-mail s plusovým aliasom", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Použiť doručenú poštu typu catch-all nastavenú na doméne." }, + "useThisEmail": { + "message": "Použiť tento e-mail" + }, "random": { "message": "Náhodné", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "Služba $SERVICENAME$ odmietla vašu žiadosť. Obráťte sa na svojho poskytovateľa služieb a požiadajte o pomoc.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "Služba $SERVICENAME$ odmietla vašu žiadosť: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Názov hostiteľa", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "Prístupový token API" - }, "deviceVerification": { "message": "Overenie zariadenia" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Pre váš účet je potrebné dvojstupňové prihlásenie DUO." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Pre váš účet sa vyžaduje dvojstupňové prihlásenie Duo. Na dokončenie prihlásenia postupujte podľa pokynov." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Na dokončenie prihlásenia postupujte podľa pokynov." + }, "launchDuo": { "message": "Spustiť DUO" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Počet používateľov" }, - "loggingInAs": { - "message": "Prihlasujete sa ako" - }, - "notYou": { - "message": "Nie ste to vy?" - }, "pickAnAvatarColor": { "message": "Výber farby avatara" }, @@ -7105,7 +7392,7 @@ "description": "Label for the search bar used to search projects." }, "project": { - "message": "Project", + "message": "Projekt", "description": "Similar to collections, projects can be used to group secrets." }, "editProject": { @@ -7498,18 +7785,6 @@ "noCollection": { "message": "Žiadna zbierka" }, - "canView": { - "message": "Môže zobraziť" - }, - "canViewExceptPass": { - "message": "Môže zobraziť okrem hesiel" - }, - "canEdit": { - "message": "Môže upraviť" - }, - "canEditExceptPass": { - "message": "Môže upravovať okrem hesiel" - }, "noCollectionsAdded": { "message": "Nebolí pridané žiadne zbierky" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Žiadosť o schválenie správcom" }, - "approveWithMasterPassword": { - "message": "Schváliť pomocou hlavného hesla" - }, "trustedDeviceEncryption": { "message": "Šifrovanie dôveryhodného zariadenia" }, "trustedDevices": { "message": "Dôveryhodné zariadenia" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Po autentifikácii, členovia budú dešifrovať dáta v trezore za pomoci kľúča uloženého na ich zariadení.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "jedna organizácia", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "single organization", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "správa obnovy konta", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "account recovery administration", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Povolenia vašej organizácie boli aktualizované, čo si vyžaduje nastavenie hlavného hesla.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Schváliť žiadosť" }, + "deviceApproved": { + "message": "Zariadenie schválené" + }, + "deviceRemoved": { + "message": "Zariadenie odstránené" + }, + "removeDevice": { + "message": "Odstrániť zariadenie" + }, + "removeDeviceConfirmation": { + "message": "Ste si istí, že chcete odstrániť toto zariadenie?" + }, "noDeviceRequests": { "message": "Žiadne žiadosti pre zariadenia" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Vaša žiadosť bola odoslaná správcovi." }, - "youWillBeNotifiedOnceApproved": { - "message": "Po schválení budete informovaný." - }, "troubleLoggingIn": { "message": "Máte problémy s prihlásením?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Obmedziť vymazávanie zbierky len pre vlastníkov a administrátorov" }, + "limitItemDeletionDesc": { + "message": "Obmedziť vymazávanie položiek na členov s oprávnením \"Môže spravovať\"" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Vlastníci a správcovia môžu spravovať všetky zbierky a položky" }, @@ -8494,9 +8778,6 @@ "message": "Adresa URL vlastného hostingu", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias doména" - }, "alreadyHaveAccount": { "message": "Už máte účet?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "Nemáte prístup k spravovaniu tejto zbierky." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Chýbajúce oprávnenia pre správu zbierky" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Pre povolenie kompletnej správy zbierky vrátane jej vymazania, udeľte oprávnenia na správu zbierky." }, "grantCollectionAccess": { "message": "Povoľte skupinám, alebo jednotlivcom prístup k tejto zbierke." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Chýba ID zariadenia" + }, + "deviceTypeMissing": { + "message": "Chýba typ zariadenia" + }, + "deviceCreationDateMissing": { + "message": "Chýba dátum vytvorenia zaradenia" + }, + "desktopRequired": { + "message": "Vyžaduje sa desktop" + }, + "reopenLinkOnDesktop": { + "message": "Otvorte tento odkaz z emailu na desktope." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "mesačne za člena účtovaného ročne" + }, "seats": { "message": "Sedenia" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Dozvedieť sa viac o Bitwarden API" }, + "fileSend": { + "message": "Send so súborom" + }, "fileSends": { "message": "Sendy so súborom" }, + "textSend": { + "message": "Textový Send" + }, "textSends": { "message": "Textové Sendy" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Algoritmus kľúča" }, + "sshPrivateKey": { + "message": "Súkromný kľúč" + }, + "sshPublicKey": { + "message": "Verejný kľúč" + }, + "sshFingerprint": { + "message": "Odtlačok" + }, "sshKeyFingerprint": { "message": "Odtlačok" }, @@ -9774,10 +10088,6 @@ "message": "Zahrnúť špeciálne znaky", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Priložiť prílohu" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Kód výpisu" }, + "cannotRemoveViewOnlyCollections": { + "message": "Zbierky, ktoré môžete len zobraziť nemôžete odstrániť: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Dôležité upozornenie" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Odstrániť členov" }, + "devices": { + "message": "Zariadenia" + }, + "deviceListDescription": { + "message": "Vaše konto bolo prihlásené do každého z nižšie uvedených zariadení. Ak niektoré zariadenie nepoznáte, teraz ho odstráňte." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Privlastnené domény" }, @@ -10020,7 +10348,69 @@ "organizationNameMaxLength": { "message": "Meno organizácie nemôže mať viac ako 50 znakov." }, - "resellerRenewalWarning": { + "sshKeyWrongPassword": { + "message": "Zadané heslo je nesprávne." + }, + "importSshKey": { + "message": "Importovať" + }, + "confirmSshKeyPassword": { + "message": "Potvrdiť heslo" + }, + "enterSshKeyPasswordDesc": { + "message": "Zadajte heslo pre kľúč SSH." + }, + "enterSshKeyPassword": { + "message": "Zadať heslo" + }, + "invalidSshKey": { + "message": "Kľúč SSH je neplatný" + }, + "sshKeyTypeUnsupported": { + "message": "Tento typ kľúča SSH nie je podporovaný" + }, + "importSshKeyFromClipboard": { + "message": "Importovať kľúč zo schránky" + }, + "sshKeyImported": { + "message": "Kľúč SSH bol úspešne importovaný" + }, + "copySSHPrivateKey": { + "message": "Kopírovať súkromný kľúč" + }, + "openingExtension": { + "message": "Otvára sa rozšírenie Bitwarden pre prehliadač" + }, + "somethingWentWrong": { + "message": "Niečo sa pokazilo..." + }, + "openingExtensionError": { + "message": "Mali sme problém otvoriť Bitwarden rozšírenie pre prehliadač. Otvoríte ho kliknutím na tlačidlo." + }, + "openExtension": { + "message": "Otvoriť rozšírenie" + }, + "doNotHaveExtension": { + "message": "Nemáte Bitwarden rozšírenie pre prehliadač?" + }, + "installExtension": { + "message": "Inštalovať rozšírenie" + }, + "openedExtension": { + "message": "Rozšírenie pre prehliadač otvorené" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Rozšírenie pre prehliadač Bitwarden úspešne otvorené. Teraz môžete skontrolovať vaše ohrozene heslá." + }, + "openExtensionManuallyPart1": { + "message": "Mali sme problém otvoriť Bitwarden rozšírenie pre prehliadač. Otvorte ikonu Bitwarden", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "na paneli nástrojov.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { "message": "Vaše predplatné sa čoskoro obnoví. Aby ste si zabezpečili nepretržitú prevádzku, kontaktujte $RESELLER$ a potvrďte obnovenie pred $RENEWAL_DATE$.", "placeholders": { "reseller": { @@ -10033,7 +10423,7 @@ } } }, - "resellerOpenInvoiceWarning": { + "resellerOpenInvoiceWarningMgs": { "message": "Faktúra za vaše predplatné bola vystavená dňa $ISSUED_DATE$. Aby ste si zabezpečili nepretržitú prevádzku, kontaktujte $RESELLER$ a potvrďte obnovenie predplatného pred $DUE_DATE$.", "placeholders": { "reseller": { @@ -10050,7 +10440,7 @@ } } }, - "resellerPastDueWarning": { + "resellerPastDueWarningMsg": { "message": "Faktúra za vaše predplatné nebola uhradená. Aby ste si zabezpečili nepretržitú prevádzku, kontaktujte $RESELLER$ a potvrďte obnovenie pred $GRACE_PERIOD_END$.", "placeholders": { "reseller": { @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Odteraz majú správcovia právomoc vymazať konta členov, ktorí prináležia do privlastnenej domény." + }, + "deleteManagedUserWarningDesc": { + "message": "Táto akcia vymaže člena vrátane všetkých ich položiek v trezore. Tato akcia nahrádza predchádzajúcu akciu Odstrániť." + }, + "deleteManagedUserWarning": { + "message": "\"Vymazať\" je nová akcia!" + }, + "seatsRemaining": { + "message": "Z celkovo $TOTAL$ sedení pridelených tejto organizácii vám zostava $REMAINING$ sedení. Pre správu predplatného kontaktujte vášho poskytovateľa.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existujúca organizácia" + }, + "selectOrganizationProviderPortal": { + "message": "Vyberte organizáciu ktorú chcete pridať do portálu poskytovateľa." + }, + "noOrganizations": { + "message": "Neexistujú žiadne organizácie na zobrazenie" + }, + "yourProviderSubscriptionCredit": { + "message": "Váš poskytovateľ predplatného obdrží kredit za zostávajúci čas predplatného vašej organizácie." + }, + "doYouWantToAddThisOrg": { + "message": "Chcete pridať tuto organizáciu k $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Existujúca organizácia pridaná" + }, + "assignedExceedsAvailable": { + "message": "Počet pridelených sedení presahuje počet dostupných sedení." + }, + "changeAtRiskPassword": { + "message": "Zmeniť ohrozené heslo" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Odstrániť odomknutie PIN kódom" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Neumožnite vaším členom odomykať si konto PIN kódom." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Prechodom na plán Teams alebo Enterprise získate úplný prístup k denníku udalostí organizácie." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Bezplatné organizácie môžu mat maximálne dve zbierky. Ak chcete pridať viac zbierok povýšte na platené predplatné." } } diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index 9a9535aebaa..1652841b650 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -1,18 +1,21 @@ { "allApplications": { - "message": "All applications" + "message": "Vse aplikacije" }, "criticalApplications": { - "message": "Critical applications" + "message": "Kritične aplikacije" + }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" }, "accessIntelligence": { - "message": "Access Intelligence" + "message": "Analiza dostopa" }, "riskInsights": { - "message": "Risk Insights" + "message": "Vpogled v tveganja" }, "passwordRisk": { - "message": "Password Risk" + "message": "Varnostno tveganje gesla" }, "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." @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "Katere vrste element je to?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Zapisek" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Note" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Povleci za sortiranje" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Besedilo" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Uredi mapo" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "Bazna domena", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "npr.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filter" }, - "moveSelectedToOrg": { - "message": "Premakni označeno v organizacijo" - }, "deleteSelected": { "message": "Izbriši izbrano" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Izbrani elementi premaknjeni v $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "Ne" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "Prijavite se ali ustvarite nov račun za dostop do svojega zavarovanega trezorja." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Prijava se je začela" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Potrdi" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Namig za geslo" - }, - "enterEmailToGetHint": { - "message": "Vnesite e-poštni naslov svojega računa in poslali vam bomo namig za vaše glavno geslo." - }, "getMasterPasswordHint": { "message": "Pokaži namig za glavno geslo" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "Na vašo napravo smo poslali obvestilo." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "Različica $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Zapomni si me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Ponovno posreduj potrditveno kodo na e-poštni naslov" }, "useAnotherTwoStepMethod": { "message": "Uporabi drug dvostopenjski način vpisa" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Svoj YubiKey ključek vstavite v USB režo in pritisnite na njegovo tipko." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Možnosti dvostopenjske prijave" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Ste izgubili dostop do vseh vaših ponudnikov dvostopenjse prijave? Uporabite svoje kode za obnovitev in tako onemogočite dvostopenjsko prijavo v svoj račun." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Preseljeno iz FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "E-pošta" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Izberite organizacijo, v katero želite premakniti ta element. To bo preneslo lasništvo elementa na to organizacijo. Potem ne boste več neposredni lastnik tega elementa." }, - "moveManyToOrgDesc": { - "message": "Izberite organizacijo, v katero želite premakniti te elemente. To bo preneslo lasništvo elementov na to organizacijo. Potem ne boste več neposredni lastnik teh elementov." - }, "collectionsDesc": { "message": "Uredite zbirke s katerimi želite deliti ta predmet. Predmet bodo lahko videli le uporabniki orgnanizacije, ki bodo imajo dostop do teh zbirk." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "Izbrali ste toliko elementov: $COUNT$. Toliko jih je mogoče premakniti v organizacijo: $MOVEABLE_COUNT$, toliko pa ne: $NONMOVEABLE_COUNT$.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Verifikacijska koda (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Ponovno generiraj geslo" - }, "length": { "message": "Dolžina" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Prosimo, ponovno se prijavite." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Prosimo, ponovno se prijavite. Če uporabljate druge Bitwarden aplikacije, se odjavite in ponovno prijavite tudi tam." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Območje nevarnosti" }, - "dangerZoneDesc": { - "message": "Previdno, ta dejanja so nepovratna!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Prekini seje" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Proceeding will also log you out of your current session, requiring you to log back in. You will also be prompted for two-step login again, if set up. Active sessions on other devices may continue to remain active for up to one hour." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "Vse seje prekinjene" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Setting up two-step login can permanently lock you out of your Bitwarden account. A recovery code allows you to access your account in the event that you can no longer use your normal two-step login provider (example: you lose your device). Bitwarden support will not be able to assist you if you lose access to your account. We recommend you write down or print the recovery code and keep it in a safe place." }, + "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." + }, "viewRecoveryCode": { "message": "Prikaži kodo za obnovitev" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Upravljaj" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Onemogočeno" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Odvzemi dostop" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "This two-step login provider is active on your account." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "There was a problem reading the security key. Try again." }, - "twoFactorWebAuthnWarning": { - "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used. Supported platforms:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Web vault and browser extensions on a desktop/laptop with a WebAuthn supported browser (Chrome, Opera, Vivaldi, or Firefox with FIDO U2F turned on)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Your Bitwarden two-step login recovery code" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "If you cannot access your account through your normal two-step login methods, you can use your two-step login recovery code to turn off all two-step providers on your account." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Recover account two-step login" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Nastavite zahteve za moč glavnega gesla." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Require two-step login" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Set requirements for password generator." }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." - }, "masterPasswordPolicyInEffect": { "message": "One or more organization policies require your master password to meet the following requirements:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Organization owners and admins are exempt from this policy's enforcement." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "Datoteka" }, "sendTypeText": { "message": "Besedilo" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Nova pošiljka", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Izbriši pošiljko", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Ste prepričani, da želite izbrisati to pošiljko?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "Katere vrste pošiljka je to?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maximum access count" }, - "maxAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Disabled" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Are you sure you want to remove the password?" }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, - "disableThisSend": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Vse pošiljke" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Pending deletion" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Expired" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "The following organization policies are currently in effect:" - }, - "sendDisableHideEmailInEffect": { - "message": "Users are not allowed 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." - }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Remove individual ownership for organization users" }, - "textHiddenByDefault": { - "message": "When accessing the Send, hide the text by default", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to Send." - }, - "sendFileDesc": { - "message": "Datoteka, ki jo želite poslati kot pošiljko." - }, - "copySendLinkOnSave": { - "message": "Ko shranim, skopiraj povezavo za deljenje te pošiljke v odložišče." - }, - "sendLinkLabel": { - "message": "Povezava pošiljke", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Pošiljka", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "To verify your 2FA please click the button below." }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn is not supported in this browser." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "Kaj želite generirati?" - }, - "passwordType": { - "message": "Vrsta gesla" - }, - "regenerateUsername": { - "message": "Ponovno generiraj uporabniško ime" - }, "generateUsername": { "message": "Generate username" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Vrsta uporabniškega imena" - }, "plusAddressedEmail": { "message": "E-naslov s plusom", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Uporabite naslov za vse (\"catch-all\"), ki ste ga nastavili za svojo domeno." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Hostname", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "Dostopni žeto (\"access token\") za API" - }, "deviceVerification": { "message": "Device verification" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Number of users" }, - "loggingInAs": { - "message": "Prijavljate se kot" - }, - "notYou": { - "message": "To niste vi?" - }, "pickAnAvatarColor": { "message": "Izberite barvo za avatar" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "No collection" }, - "canView": { - "message": "Can view" - }, - "canViewExceptPass": { - "message": "Can view, except passwords" - }, - "canEdit": { - "message": "Can edit" - }, - "canEditExceptPass": { - "message": "Can edit, except passwords" - }, "noCollectionsAdded": { "message": "No collections added" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domain" - }, "alreadyHaveAccount": { "message": "Already have an account?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/sr/messages.json b/apps/web/src/locales/sr/messages.json index e15d6d66653..005fa7602b5 100644 --- a/apps/web/src/locales/sr/messages.json +++ b/apps/web/src/locales/sr/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Критичне апликације" }, + "noCriticalAppsAtRisk": { + "message": "Нема критичних апликација у ризику" + }, "accessIntelligence": { "message": "Приступи интелигенцији" }, @@ -15,7 +18,7 @@ "message": "Ризик од лозинке" }, "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." + "message": "Прегледај ризичне лозинке (слабе, изложене или поново коришћене) у апликацијама. Изабери своје најкритичније апликације да би дао приоритет безбедносним радњама како би твоји корисници адресирали ризичне лозинке." }, "dataLastUpdated": { "message": "Подаци су последњи пут ажурирани: $DATE$", @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "Чланови под ризиком" }, + "atRiskMembersWithCount": { + "message": "Ризични чланови ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Ризичне апликације ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "Ови чланови се пријављују у апликације са слабим, откривеним или поново коришћеним лозинкама." + }, + "atRiskApplicationsDescription": { + "message": "Ове апликације имају слабу, проваљену или често коришћену лозинку." + }, + "atRiskMembersDescriptionWithApp": { + "message": "Ови чланови се пријављују у $APPNAME$ са слабим, откривеним или поново коришћеним лозинкама.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Укупно чланова" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Укупно апликација" }, + "unmarkAsCriticalApp": { + "message": "Уклони ознаку критичне апликације" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Успешно уклоњена ознака са критичне апликације" + }, "whatTypeOfItem": { "message": "Који је ово тип елемента?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Напомене" }, + "privateNote": { + "message": "Приватна белешка" + }, "note": { "message": "Белешка" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Превуците за сортирање" }, + "dragToReorder": { + "message": "Превуците да бисте организовали" + }, "cfTypeText": { "message": "Текст" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Уреди фасциклу" }, + "editWithName": { + "message": "Уредити $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "Нова фасцикла" + }, + "folderName": { + "message": "Име фасцикле" + }, + "folderHintText": { + "message": "Угнездите фасциклу додавањем имена надређене фасцкле праћеног знаком „/“. Пример: Друштвени/Форуми" + }, + "deleteFolderPermanently": { + "message": "Да ли сте сигурни да желите да трајно избришете ову фасциклу?" + }, "baseDomain": { "message": "Главни домен", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Име ставке" }, - "cannotRemoveViewOnlyCollections": { - "message": "Не можете уклонити колекције са дозволама само за приказ: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "нпр.", "description": "Short abbreviation for 'example'." @@ -777,7 +841,7 @@ "message": "Копирати телефон" }, "copyEmail": { - "message": "Копирати имејл" + "message": "Копирај Е-пошту" }, "copyCompany": { "message": "Копирати фирму" @@ -815,9 +879,6 @@ "filter": { "message": "Филтер" }, - "moveSelectedToOrg": { - "message": "Премести одабрано у организацију" - }, "deleteSelected": { "message": "Избриши изабрано" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Одабране ставке премештене у $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Ставке премештене у $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "Не" }, + "location": { + "message": "Локација" + }, "loginOrCreateNewAccount": { "message": "Пријавите се или креирајте нови налог за приступ Сефу." }, @@ -1119,23 +1174,47 @@ "logInToBitwarden": { "message": "Пријавите се на Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Унесите кôд послат на ваш имејл" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Унесите кôд из апликације за аутентификацију" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Стисните Ваш YubiKey за аутентификацију" + }, "authenticationTimeout": { "message": "Истекло је време аутентификације" }, "authenticationSessionTimedOut": { "message": "Истекло је време сесије за аутентификацију. Молим вас покрените процес пријаве поново." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Потврдите идентитет" }, + "weDontRecognizeThisDevice": { + "message": "Не препознајемо овај уређај. Унесите код послат на адресу ваше електронске поште да би сте потврдили ваш идентитет." + }, + "continueLoggingIn": { + "message": "Настави са пријављивањем" + }, + "whatIsADevice": { + "message": "Шта је уређај?" + }, + "aDeviceIs": { + "message": "Уређај је јединствена инсталација Bitwarden апликације на коју сте се пријавили. Поновно инсталирање, брисање података апликације или брисање колачића може довести до тога да се уређај појави више пута." + }, "logInInitiated": { "message": "Пријава је покренута" }, + "logInRequestSent": { + "message": "Захтев је послат" + }, "submit": { "message": "Пошаљи" }, "emailAddressDesc": { - "message": "Користите ваш имејл за пријављивање." + "message": "Користи твоју Е-пошту за пријављивање." }, "yourName": { "message": "Ваше име" @@ -1168,7 +1247,7 @@ "message": "Савет Главне Лозинке" }, "masterPassHintText": { - "message": "Ако заборавите лозинку, наговештај за лозинку се може послати на ваш имејл. $CURRENT$/$MAXIMUM$ карактера максимум.", + "message": "Ако заборавиш лозинку, наговештај за лозинку се може послати на твоју Е-пошту. $CURRENT$/$MAXIMUM$ карактера максимум.", "placeholders": { "current": { "content": "$1", @@ -1184,7 +1263,7 @@ "message": "Подешавања" }, "accountEmail": { - "message": "Имејл налога" + "message": "Е-пошта налога" }, "requestHint": { "message": "Захтевај савет" @@ -1193,22 +1272,16 @@ "message": "Затражити савет лозинке" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { - "message": "Унесите имејл свог налога и биће вам послат савет за лозинку" - }, - "passwordHint": { - "message": "Помоћ за лозинку" - }, - "enterEmailToGetHint": { - "message": "Унесите Ваш имејл да би добили савет за Вашу Главну Лозинку." + "message": "Унеси адресу Е-поште свог налога и биће ти послат савет за лозинку" }, "getMasterPasswordHint": { "message": "Добити савет за Главну Лозинку" }, "emailRequired": { - "message": "Имејл је неопходан." + "message": "Адреса Е-поште је неопходна." }, "invalidEmail": { - "message": "Неисправан имејл." + "message": "Нетачна адреса Е-поште." }, "masterPasswordRequired": { "message": "Главна Лозинка је неопходна." @@ -1251,7 +1324,7 @@ "message": "Изаберите датум истека који је у будућности." }, "emailAddress": { - "message": "Имејл" + "message": "Адреса Е-поште" }, "yourVaultIsLockedV2": { "message": "Ваш сеф је блокиран" @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "Обавештење је послато на ваш уређај." }, + "notificationSentDevicePart1": { + "message": "Откључај Bitwarden на твом уређају или на " + }, + "areYouTryingToAccessYourAccount": { + "message": "Да ли покушавате да приступите вашем налогу?" + }, + "accessAttemptBy": { + "message": "Покушај приступа са $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Потврди приступ" + }, + "denyAccess": { + "message": "Одбиј приступ" + }, + "notificationSentDeviceAnchor": { + "message": "веб апликација" + }, + "notificationSentDevicePart2": { + "message": "Потврдите да се фраза отиска прста поклапа са овом испод пре одобравања." + }, + "notificationSentDeviceComplete": { + "message": "Октључај Bitwarden на твом уређају. Потврдите да се фраза отиска прста поклапа са овом испод пре одобравања." + }, "aNotificationWasSentToYourDevice": { "message": "Обавештење је послато на ваш уређај" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Уверите се да је ваш налог откључан и да се фраза отиска подудара на другом уређају" - }, "versionNumber": { "message": "Верзија $VERSION_NUMBER$", "placeholders": { @@ -1348,7 +1448,7 @@ } }, "verificationCodeEmailSent": { - "message": "Провера имејла послата на $EMAIL$.", + "message": "Е-пошта за верификацију је послата на $EMAIL$.", "placeholders": { "email": { "content": "$1", @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Запамти ме" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Не питајте поново на овом уређају 30 дана" + }, "sendVerificationCodeEmailAgain": { - "message": "Поново послати верификациони код на имејл" + "message": "Поново послати верификациони код на Е-пошту" }, "useAnotherTwoStepMethod": { "message": "Користите другу методу пријављивања у два корака" }, + "selectAnotherMethod": { + "message": "Изаберите другу методу", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Употребите шифру за опоравак" + }, "insertYubiKey": { "message": "Убаците свој YubiKey у УСБ порт рачунара, а затим додирните његово дугме." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Опције дво-коракне пријаве" }, + "selectTwoStepLoginMethod": { + "message": "Одабрати методу пријављивања у два корака" + }, "recoveryCodeDesc": { "message": "Изгубили сте приступ свим својим двофакторским добављачима? Употребите код за опоравак да онемогућите све двофакторске добављаче из налога." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Мигрирао из FIDO)" }, + "openInNewTab": { + "message": "Отвори у новом језичку" + }, "emailTitle": { "message": "Е-пошта" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Изаберите организацију коју желите да преместите овај предмет. Прелазак на организацију преноси власништво над ставком у ту организацију. Више нећете бити директни власник ове ставке након што је премештена." }, - "moveManyToOrgDesc": { - "message": "Изаберите организацију коју желите да преместите ове ставке. Прелазак на организацију преноси власништво над ставкама у ту организацију. Више нећете бити директни власник ове ставки након што су премештене." - }, "collectionsDesc": { "message": "Уредите колекције са којима се ова ставка дели. Само корисници организације који имају приступ овим колекцијама моћи ће да виде ову ставку." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "Одабрали сте $COUNT$ ставку(и). $MOVEABLE_COUNT$ ставка(и) може да се преместе у организацију, $NONMOVEABLE_COUNT$ не.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Једнократни код" }, @@ -1613,9 +1709,6 @@ "message": "Избегавај двосмислене карактере", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Поново генериши лозинку" - }, "length": { "message": "Дужина" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Молимо да се поново пријавите." }, + "currentSession": { + "message": "Тренутна сесија" + }, + "requestPending": { + "message": "Захтев је на чекању" + }, "logBackInOthersToo": { "message": "Молимо вас да се поново пријавите. Ако користите друге Bitwarden апликације, одјавите се и вратите се и на њих." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Опасна зона" }, - "dangerZoneDesc": { - "message": "Пажљиво, ове акције су крајне!" - }, - "dangerZoneDescSingular": { - "message": "Пажљиво, ова акција није реверзибилна!" - }, "deauthorizeSessions": { "message": "Одузели овлашћење сесије" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Наставак ће вас такође одјавити из тренутне сесије, што захтева поновно пријављивање. Од вас ће такође бити затражено да се поново пријавите у два корака, ако је омогућено. Активне сесије на другим уређајима могу да остану активне још један сат." }, + "newDeviceLoginProtection": { + "message": "Пријава на новом уређају" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Искључи заштиту пријаве на новом уређају" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Укључи заштиту пријаве на новом уређају" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Наставите доле да бисте искључили верификационе имејлове које Bitwarden шаље када се пријавите са новог уређаја." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Наставите доле да бисте Bitwarden Ва- шаље верификационе имејлове када се пријавите са новог уређаја." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "Ако је заштита нових пријава искључена, је било ко са вашом главном лозинком може приступити вашем налогу са било којег уређаја. Да бисте заштитили свој рачун без верификационих имејлова, поставите пријаву у два корака." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "Промене нових уређаји за заштиту пријаве су сачуване" + }, "sessionsDeauthorized": { "message": "Одузето овлашћење свих сесија" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Омогућавање пријаве у два корака може вас трајно закључати са вашег Bitwarden-а налога. Код за опоравак омогућава вам приступ вашем налогу у случају да више не можете да користите свог уобичајеног добављача услуге пријављивања у два корака (нпр. ако изгубите уређај). Подршка Bitwarden-а неће вам моћи помоћи ако изгубите приступ свом налогу. Препоручујемо да запишете или одштампате код за опоравак и сачувате га на сигурном месту." }, + "yourSingleUseRecoveryCode": { + "message": "Ваш јединствени кôд за опоравак може се користити за искључивање у два корака у случају да изгубите приступ свом двоструком провајдеру пријаве. Bitwarden препоручује да запишете кôд за опоравак и држите га на сигурном месту." + }, "viewRecoveryCode": { "message": "Погледати шифру за опоравак" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Управљати" }, - "canManage": { - "message": "Може управљати" + "manageCollection": { + "message": "Управљај колекцијом" + }, + "viewItems": { + "message": "Прикажи ставке" + }, + "viewItemsHidePass": { + "message": "Прикажи ставке, сакривене лозинке" + }, + "editItems": { + "message": "Уреди ставке" + }, + "editItemsHidePass": { + "message": "Уреди ставке, сакривене лозинке" }, "disable": { "message": "Онемогући" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Опозови Приступ" }, + "revoke": { + "message": "Опозови" + }, "twoStepLoginProviderEnabled": { "message": "Овај добављач услуге пријављивања у два корака је омогућен на вашем налогу." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "Догодила се грешка приликом читања безбедносног кључа. Покушајте поново." }, - "twoFactorWebAuthnWarning": { - "message": "Због ограничења платформе, WebAuthn се не могу користити на свим Bitwarden апликацијама. Требали бисте омогућити другог добављача услуге пријављивања у два корака како бисте могли да приступите свом налогу када WebAuthn не могу да се користе. Подржане платформе:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Веб сеф и додатке прегледача на рачунару са WebAuthn омогућен прегледач (Chrome, Opera, Vivaldi, или Firefox са FIDO U2F омогућено)." + "twoFactorWebAuthnWarning1": { + "message": "Због ограничења платформе, WebAuthn се не могу користити на свим Bitwarden апликацијама. Требали бисте подесити другог добављача услуге пријављивања у два корака како бисте могли да приступите свом налогу када WebAuthn не могу да се користе." }, "twoFactorRecoveryYourCode": { "message": "Ваш Bitwarden код за опоравак пријаве у два корака" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Преостала вам је 1 позивница." + }, + "inviteZeroEmailDesc": { + "message": "Преостало вам је 0 позивница." + }, "userUsingTwoStep": { "message": "Овај корисник користи пријаву у два корака за заштиту свог налога." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Неповезан SSO." + }, "unlinkedSsoUser": { "message": "Отповезај SSO за $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Уређај" }, + "loginStatus": { + "message": "Статус пријаве" + }, + "firstLogin": { + "message": "Прва пријава" + }, + "trusted": { + "message": "Поуздан" + }, + "needsApproval": { + "message": "Потребно је одобрење" + }, + "areYouTryingtoLogin": { + "message": "Да ли покушавате да се пријавите?" + }, + "logInAttemptBy": { + "message": "Покушај пријаве од $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Тип уређаја" + }, + "ipAddress": { + "message": "ИП адреса" + }, + "confirmLogIn": { + "message": "Потврди пријављивање" + }, + "denyLogIn": { + "message": "Одбиј пријављивање" + }, + "thisRequestIsNoLongerValid": { + "message": "Овај захтев више није важећи." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Пријава потврђена за $EMAIL$ на $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "Одбили сте покушај пријаве са другог уређаја. Ако сте то заиста били ви, покушајте поново да се пријавите помоћу уређаја." + }, + "loginRequestHasAlreadyExpired": { + "message": "Захтев за пријаву је већ истекао." + }, + "justNow": { + "message": "Управо сада" + }, + "requestedXMinutesAgo": { + "message": "Затражено пре $MINUTES$ минута", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Креирај налог на" }, @@ -3883,11 +4091,17 @@ "message": "Ажурирајте Претраживач" }, "generatingRiskInsights": { - "message": "Generating your risk insights..." + "message": "Генерисање прегледа вашег ризика..." }, "updateBrowserDesc": { "message": "Користите неподржани веб прегледач. Веб сеф можда неће правилно функционисати." }, + "youHaveAPendingLoginRequest": { + "message": "Имате захтев за пријаву на чекању са другог уређаја." + }, + "reviewLoginRequest": { + "message": "Прегледајте захтев за пријаву" + }, "freeTrialEndPromptCount": { "message": "Ваша проба се завршава за $COUNT$ дана.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "Ако не можете да приступите свом налогу путем уобичајених метода пријављивања у два корака, можете користити свој код за опоравак пријаве да бисте онемогућили све добављаче услуга у два корака на свом налогу." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Пријавите се испод коришћења вашег једнократног кôда за опоравак. Ово ће искључити све провајдере у два корака на вашем налогу." + }, "recoverAccountTwoStep": { "message": "Опоравак пријаве у два корака" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Ажурирање кључа за шифровање не може да се настави" }, + "editFieldLabel": { + "message": "Уреди $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Преместити $LABEL$. Користите тастер са стрелицом да бисте померили ставку.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ премештено на горе, позиција $INDEX$ од $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ премештено на доле, позиција $INDEX$ од $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "Приликом ажурирања кључа за шифровање, ваше фасцикле нису могле да се дешифрују. Да бисте наставили са ажурирањем, ваше фасцикле морају бити избрисане. Ниједна ставка у сефу неће бити избрисана ако наставите." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Поставите минималне захтеве за чврстоћу главне лозинке." }, + "passwordStrengthScore": { + "message": "Снага лозинкe $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Потребна дво-степенска пријава" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Поставите минималне захтеве за конфигурацију генератора лозинки." }, - "passwordGeneratorPolicyInEffect": { - "message": "Једна или више смерница организације утичу на поставке вашег генератора." - }, "masterPasswordPolicyInEffect": { "message": "Једна или више смерница организације захтевају да ваша главна лозинка да би испуњавали следеће захтеве:" }, @@ -4725,7 +5000,7 @@ "message": "Пријавите се помоћу портала за јединствену пријаву ваше организације. Унесите идентификатор организације да бисте започели." }, "singleSignOnEnterOrgIdentifier": { - "message": "Enter your organization's SSO identifier to begin" + "message": "За почетак унесите SSO идентификатор ваше организације" }, "singleSignOnEnterOrgIdentifierText": { "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Власници и администратори организација изузети су ове политике." }, + "limitSendViews": { + "message": "Ограничити приказе" + }, + "limitSendViewsHint": { + "message": "Нико не може да види ово Send након што се достигне ограничење.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "Осталих прегледа: $ACCESSCOUNT$", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Детаљи Send-а", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Текст за дељење" + }, "sendTypeFile": { "message": "Датотека" }, "sendTypeText": { "message": "Текст" }, + "sendPasswordDescV3": { + "message": "Додајте опционалну лозинку за примаоце да приступе овом Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Креирај ново „Send“", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Избриши „Send“", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Сигурно избрисати овај „Send“?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "Који је ово тип „Send“-a?", + "deleteSendPermanentConfirmation": { + "message": "Да ли сте сигурни да желите да трајно избришете овај Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Брисање после" }, - "deletionDateDesc": { - "message": "„The Send“ ће бити трајно избрисан наведеног датума и времена.", + "deletionDateDescV2": { + "message": "Send ће бити трајно обрисано у наведени датум.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Максималан број приступа" }, - "maxAccessCountDesc": { - "message": "Ако је постављено, корисници више неће моћи да приступе овом „send“ када се достигне максимални број приступа.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Тренутни број приступа" - }, - "sendPasswordDesc": { - "message": "Опционално захтевајте лозинку за приступ корисницима „Send“-у.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Приватне белешке о овом „Send“.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Онемогућено" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Да ли сте сигурни да желите уклонити лозинку?" }, - "hideEmail": { - "message": "Сакриј моју е-адресу од примаоца." - }, - "disableThisSend": { - "message": "Онемогућите овај „Send“ да нико не би могао да му приступи.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Све „Send“" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Брисање на чекању" }, + "hideTextByDefault": { + "message": "Сакриј текст подразумевано" + }, "expired": { "message": "Истекло" }, @@ -5161,13 +5441,6 @@ "message": "Не дозволите корисницима да сакрију своју е-пошту од примаоца приликом креирања или уређивања „Send“-а.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendOptionsPolicyInEffect": { - "message": "Следеће организационе политике су тренутно на снази:" - }, - "sendDisableHideEmailInEffect": { - "message": "Корисници не могу да сакрију своју е-пошту од примаоца приликом креирања или уређивања „Send“-а.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "modifiedPolicyId": { "message": "Политика $ID$ промењена.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Онемогућите лично власништво за кориснике организације" }, - "textHiddenByDefault": { - "message": "На притуп „Send“-а, сакриј текст по дефаулту", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Име да се опише ово слање.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Текст који желиш да пошаљеш." - }, - "sendFileDesc": { - "message": "Датотека коју желиш да пошаљеш." - }, - "copySendLinkOnSave": { - "message": "Копирај везу да би поделио слање на бележницу након снимања." - }, - "sendLinkLabel": { - "message": "Пошаљи везу", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Пошаљи", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "Појавила се грешка при снимању датума брисања и истицања." }, + "hideYourEmail": { + "message": "Сакријте свој имејл од гледалаца." + }, "webAuthnFallbackMsg": { "message": "Да би проверили Ваш 2FA Кликните на дугме испод." }, "webAuthnAuthenticate": { "message": "WebAutn аутентификација" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn није подржано у овом прегледачу." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Грешка" }, + "decryptionError": { + "message": "Грешка при декрипцији" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden није могао да декриптује ставке из трезора наведене испод." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Обратите се корисничкој подршци", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "да бисте избегли додатни губитак података.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Корисницима за управљање такође мора бити додељена дозвола за опоравак налога за управљање" }, @@ -5986,10 +6261,10 @@ "message": "Assertion consumer service (ACS) URL" }, "spNameIdFormat": { - "message": "Name ID format" + "message": "Формат ИД назива" }, "spOutboundSigningAlgorithm": { - "message": "Outbound signing algorithm" + "message": "Алгоритам за излазно потписивање" }, "spSigningBehavior": { "message": "Понашање пријављања" @@ -6031,7 +6306,7 @@ "message": "Дозволи нежељени одговор на аутентификацију" }, "idpAllowOutboundLogoutRequests": { - "message": "Allow outbound logout requests" + "message": "Дозволи одлазне захтеве за одјављивањем" }, "idpSignAuthenticationRequests": { "message": "Sign authentication requests" @@ -6127,7 +6402,7 @@ } }, "freeFamiliesPlan": { - "message": "Free Families plan" + "message": "Бесплатан породични план" }, "redeemNow": { "message": "Откупи сада" @@ -6516,15 +6791,6 @@ "message": "Генератор", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "Шта желите да генеришете?" - }, - "passwordType": { - "message": "Тип лозинке" - }, - "regenerateUsername": { - "message": "Поново генериши име" - }, "generateUsername": { "message": "Генериши име" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Тип имена" - }, "plusAddressedEmail": { "message": "Плус имејл адресе", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Случајно", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Није могуће добити ИД налога маскираног имејла $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Име домаћина", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "Приступни АПИ токен" - }, "deviceVerification": { "message": "Провера уређаја" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "DUO пријава у два корака је потребна за ваш налог." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Покренути DUO" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Број корисника" }, - "loggingInAs": { - "message": "Пријављивање као" - }, - "notYou": { - "message": "Нисте Ви?" - }, "pickAnAvatarColor": { "message": "Изабрати боју аватара" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "Нема колекције" }, - "canView": { - "message": "Може прегледати" - }, - "canViewExceptPass": { - "message": "Може видетим осим лозинке" - }, - "canEdit": { - "message": "Може уређивати" - }, - "canEditExceptPass": { - "message": "Ноже уређивати осим лозинке" - }, "noCollectionsAdded": { "message": "Ниједна колекција додата" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Затражити одобрење администратора" }, - "approveWithMasterPassword": { - "message": "Одобрити са главном лозинком" - }, "trustedDeviceEncryption": { "message": "Шифровање поузданог уређаја" }, "trustedDevices": { "message": "Поуздани уређаји" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Када се аутентификују, чланови ће дешифровати податке из сефљ користећи кључ сачуван на њиховом уређају", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "јединствена организација", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "single organization", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": "полиса,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "policy,", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "SSO потребан", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO required", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "полиса, и", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "policy, and", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "администрација опоравка налога", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "account recovery administration", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "полисе са аутоматским уписом ће се укључити када се користи ова опција.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Дозволе за вашу организацију су ажуриране, што захтева да поставите главну лозинку.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Одобри захтев" }, + "deviceApproved": { + "message": "Уређај одобрен" + }, + "deviceRemoved": { + "message": "Уређај је уклоњен" + }, + "removeDevice": { + "message": "Уклони уређај" + }, + "removeDeviceConfirmation": { + "message": "Да ли сте сигурни да желите да уклоните овај уређај?" + }, "noDeviceRequests": { "message": "Нема захтева уређаја" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Ваш захтев је послат вашем администратору." }, - "youWillBeNotifiedOnceApproved": { - "message": "Бићете обавештени када буде одобрено." - }, "troubleLoggingIn": { "message": "Имате проблема са пријављивањем?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Ограничите брисање збирке на власнике и администраторе" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Власници и администратори могу да управљају свим колекцијама и ставкама" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Домен алијаса" - }, "alreadyHaveAccount": { "message": "Већ имате налог?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "Немате приступ за управљање овом колекцијом." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Фале дозволе „Може да управља“" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Додатје дозволе „Може да управља“ да би се омогућило потпуно управљање наплатом укључујући брисање колекције." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Одобрите групама или члановима приступ овој колекцији." @@ -9046,19 +9327,34 @@ "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." }, "eventManagement": { - "message": "Event management" + "message": "Управљање догађајима" }, "eventManagementDesc": { "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." }, "deviceManagement": { - "message": "Device management" + "message": "Управљање уређајима" }, "deviceManagementDesc": { - "message": "Configure device management for Bitwarden using the implementation guide for your platform." + "message": "Конфигуришите управљање уређајима за Bitwarden помоћу водича за имплементацију за своју платформу." + }, + "deviceIdMissing": { + "message": "Недостаје ИД уређаја" + }, + "deviceTypeMissing": { + "message": "Недостаје тип уређаја" + }, + "deviceCreationDateMissing": { + "message": "Недостаје датум креације уређаја" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." }, "integrationCardTooltip": { - "message": "Launch $INTEGRATION$ implementation guide.", + "message": "Покренути $INTEGRATION$ водич за имплементацију.", "placeholders": { "integration": { "content": "$1", @@ -9067,7 +9363,7 @@ } }, "smIntegrationTooltip": { - "message": "Set up $INTEGRATION$.", + "message": "Подесити $INTEGRATION$.", "placeholders": { "integration": { "content": "$1", @@ -9076,7 +9372,7 @@ } }, "smSdkTooltip": { - "message": "View $SDK$ repository", + "message": "Преглед $SDK$ спремишта", "placeholders": { "sdk": { "content": "$1", @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "месечно по члану" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Места" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Сазнајте више о API Bitwarden-а" }, + "fileSend": { + "message": "Датотека „Send“" + }, "fileSends": { "message": "Датотека „Send“" }, + "textSend": { + "message": "Текст „Send“" + }, "textSends": { "message": "Текст „Send“" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Алгоритам кључа" }, + "sshPrivateKey": { + "message": "Приватни кључ" + }, + "sshPublicKey": { + "message": "Јавни кључ" + }, + "sshFingerprint": { + "message": "Отисак" + }, "sshKeyFingerprint": { "message": "Отисак прста" }, @@ -9716,7 +10030,7 @@ "message": "Ваша комплементарна једногодишња претплата на Менаџер Лозинки ће надоградити на изабрани план. Неће вам бити наплаћено док се бесплатни период не заврши." }, "fileSavedToDevice": { - "message": "File saved to device. Manage from your device downloads." + "message": "Датотека је сачувана на уређају. Управљајте преузимањима са свог уређаја." }, "publicApi": { "message": "Јавни API", @@ -9774,10 +10088,6 @@ "message": "Укључити специјална слова", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Додај прилог" }, @@ -9894,7 +10204,16 @@ "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." }, "descriptorCode": { - "message": "Descriptor code" + "message": "Кôд дескриптора" + }, + "cannotRemoveViewOnlyCollections": { + "message": "Не можете уклонити колекције са дозволама само за приказ: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } }, "importantNotice": { "message": "Важно обавештење" @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Уклони чланове" }, + "devices": { + "message": "Уређаји" + }, + "deviceListDescription": { + "message": "Ваш рачун је пријављен на сваку од доле наведених уређаја. Ако не препознајете уређај, извадите га сада." + }, + "deviceListDescriptionTemp": { + "message": "Ваш рачун је пријављен на сваку од доле наведених уређаја." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10018,10 +10346,72 @@ "message": "Domain claimed" }, "organizationNameMaxLength": { - "message": "Organization name cannot exceed 50 characters." + "message": "Име организације не може прећи 50 знакова." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "Лозинка коју сте унели није тачна." + }, + "importSshKey": { + "message": "Увоз" + }, + "confirmSshKeyPassword": { + "message": "Потврда лозинке" + }, + "enterSshKeyPasswordDesc": { + "message": "Унети лозинку за SSH кључ." + }, + "enterSshKeyPassword": { + "message": "Унесите лозинку" + }, + "invalidSshKey": { + "message": "SSH кључ је неважећи" + }, + "sshKeyTypeUnsupported": { + "message": "Тип SSH кључа није подржан" + }, + "importSshKeyFromClipboard": { + "message": "Увезите кључ из оставе" + }, + "sshKeyImported": { + "message": "SSH кључ је успешно увезен" + }, + "copySSHPrivateKey": { + "message": "Копирај приватни кључ" + }, + "openingExtension": { + "message": "Отварање Bitwarden додатка прегледача" + }, + "somethingWentWrong": { + "message": "Нешто није у реду..." + }, + "openingExtensionError": { + "message": "Имали смо проблема са отварањем додатка. Кликните на дугме да бисте га сада отворили." + }, + "openExtension": { + "message": "Отвори додатак" + }, + "doNotHaveExtension": { + "message": "Немате додатак за Bitwarden?" + }, + "installExtension": { + "message": "Инсталирајте додатак" + }, + "openedExtension": { + "message": "Отворите додатак претраживача" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Успешно је отворен додатак. Сада можете да прегледате своје ризичне лозинке." + }, + "openExtensionManuallyPart1": { + "message": "Имали смо проблема са отварањем додатка. Отворите Bitwarden иконицу", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "са алатне траке.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Поново покрените претплату" + }, + "suspendedManagedOrgMessage": { + "message": "Контактирајте $PROVIDER$ за помоћ.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Избриши је нова акција!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Постојећа организација" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Променити ризичну лозинку" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ планови немају приступ стварним извештајима догађаја", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Добијте потпуни приступ извештајима о догађајима организације надоградњом на Teams или Enterprise." + }, + "upgradeEventLogTitle": { + "message": "Надоградите за извештај о реалним догађајима" + }, + "upgradeEventLogMessage": { + "message": "Ови догађаји су само примери и не одражавају стварне догађаје у вашем Bitwarden отганизацији." + }, + "cannotCreateCollection": { + "message": "Бесплатне организације могу имати до 2 колекције. Надоградите на плаћени план за додавање више колекција." } } diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index b2cd3a877d4..d141d8301fc 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -1,24 +1,27 @@ { "allApplications": { - "message": "All applications" + "message": "Sve aplikacije" }, "criticalApplications": { - "message": "Critical applications" + "message": "Kritične aplikacije" + }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" }, "accessIntelligence": { - "message": "Access Intelligence" + "message": "Pristupi inteligenciji" }, "riskInsights": { - "message": "Risk Insights" + "message": "Uvid u rizik" }, "passwordRisk": { - "message": "Password Risk" + "message": "Rizik od lozinke" }, "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." + "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." }, "dataLastUpdated": { - "message": "Data last updated: $DATE$", + "message": "Podaci su poslednji put ažurirani: $DATE$", "placeholders": { "date": { "content": "$1", @@ -27,19 +30,19 @@ } }, "notifiedMembers": { - "message": "Notified members" + "message": "Obavešteni članovi" }, "revokeMembers": { - "message": "Revoke members" + "message": "Ukloni članove" }, "restoreMembers": { - "message": "Restore members" + "message": "Vrati članove" }, "cannotRestoreAccessError": { - "message": "Cannot restore organization access" + "message": "Nije moguće povratiti pristup organizaciji" }, "allApplicationsWithCount": { - "message": "All applications ($COUNT$)", + "message": "Sve aplikacije ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -48,10 +51,10 @@ } }, "createNewLoginItem": { - "message": "Create new login item" + "message": "Kreirajte novu stavku za prijavu" }, "criticalApplicationsWithCount": { - "message": "Critical applications ($COUNT$)", + "message": "Kritične aplikacije ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -60,7 +63,7 @@ } }, "notifiedMembersWithCount": { - "message": "Notified members ($COUNT$)", + "message": "Obavešteni članovi ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -69,7 +72,7 @@ } }, "noAppsInOrgTitle": { - "message": "No applications found in $ORG NAME$", + "message": "Nije pronađena nijedna aplikacija u $ORG NAME$", "placeholders": { "org name": { "content": "$1", @@ -78,7 +81,7 @@ } }, "noAppsInOrgDescription": { - "message": "As users save logins, applications appear here, showing any at-risk passwords. Mark critical apps and notify users to update passwords." + "message": "Dok korisnici čuvaju prijave, aplikacije se pojavljuju ovde, prikazujući sve rizične lozinke. Označite kritične aplikacije i obavestite korisnike da ažuriraju lozinke." }, "noCriticalAppsTitle": { "message": "You haven't marked any applications as a Critical" @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "Kog je tipa ovaj unos?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Beleške" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Note" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Sortiraj prevlačenjem" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Tekst" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Urеdi fasciklu" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "Osnovni domen", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "npr.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filter" }, - "moveSelectedToOrg": { - "message": "Move selected to organization" - }, "deleteSelected": { "message": "Obriši izabrane stavke" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "Ne" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "Ulogujte se ili napravite novi nalog kako biste pristupili Vašem trezoru." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Pošalji" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Zapamti me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Povežite Vaš YubiKey preko USB porta na vašem računaru, pa pritisnite dugme na njemu." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Lost access to all of your two-step login providers? Use your recovery code to turn off all two-step login providers from your account." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Migrated from FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "Email" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." }, - "moveManyToOrgDesc": { - "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved." - }, "collectionsDesc": { "message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Verification code (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Regeneriši lozinku" - }, "length": { "message": "Dužina" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Opasna zona" }, - "dangerZoneDesc": { - "message": "Pažljivo, ove odluke se ne mogu poništiti!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Deauthorize sessions" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Proceeding will also log you out of your current session, requiring you to log back in. You will also be prompted for two-step login again, if set up. Active sessions on other devices may continue to remain active for up to one hour." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "All sessions deauthorized" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Setting up two-step login can permanently lock you out of your Bitwarden account. A recovery code allows you to access your account in the event that you can no longer use your normal two-step login provider (example: you lose your device). Bitwarden support will not be able to assist you if you lose access to your account. We recommend you write down or print the recovery code and keep it in a safe place." }, + "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." + }, "viewRecoveryCode": { "message": "View recovery code" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Upravljaj" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Turn off" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Revoke access" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "This two-step login provider is active on your account." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "There was a problem reading the security key. Try again." }, - "twoFactorWebAuthnWarning": { - "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used. Supported platforms:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Web vault and browser extensions on a desktop/laptop with a WebAuthn supported browser (Chrome, Opera, Vivaldi, or Firefox with FIDO U2F turned on)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Your Bitwarden two-step login recovery code" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "If you cannot access your account through your normal two-step login methods, you can use your two-step login recovery code to turn off all two-step providers on your account." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Recover account two-step login" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Set requirements for master password strength." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Require two-step login" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Set requirements for password generator." }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." - }, "masterPasswordPolicyInEffect": { "message": "One or more organization policies require your master password to meet the following requirements:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Organization owners and admins are exempt from this policy's enforcement." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "File" }, "sendTypeText": { "message": "Text" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Napravi novo slanje", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Obriši slanje", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Are you sure you want to delete this Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "What type of Send is this?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maximum access count" }, - "maxAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Disabled" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Are you sure you want to remove the password?" }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, - "disableThisSend": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Sva slanja" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Pending deletion" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Expired" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "The following organization policies are currently in effect:" - }, - "sendDisableHideEmailInEffect": { - "message": "Users are not allowed 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." - }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Remove individual ownership for organization users" }, - "textHiddenByDefault": { - "message": "When accessing the Send, hide the text by default", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to Send." - }, - "sendFileDesc": { - "message": "The file you want to Send." - }, - "copySendLinkOnSave": { - "message": "Copy the link to share this Send to my clipboard upon save." - }, - "sendLinkLabel": { - "message": "Pošalji vezu", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Slanje", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "To verify your 2FA please click the button below." }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn is not supported in this browser." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, - "regenerateUsername": { - "message": "Regenerate username" - }, "generateUsername": { "message": "Generate username" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Username type" - }, "plusAddressedEmail": { "message": "Plus addressed email", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Hostname", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API access token" - }, "deviceVerification": { "message": "Device verification" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Number of users" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "pickAnAvatarColor": { "message": "Pick an avatar color" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "No collection" }, - "canView": { - "message": "Can view" - }, - "canViewExceptPass": { - "message": "Can view, except passwords" - }, - "canEdit": { - "message": "Can edit" - }, - "canEditExceptPass": { - "message": "Can edit, except passwords" - }, "noCollectionsAdded": { "message": "No collections added" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domain" - }, "alreadyHaveAccount": { "message": "Already have an account?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index 14d7bc4572c..f7d6414c36c 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Kritiska applikationer" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "Vilken typ av objekt är detta?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Anteckningar" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Anteckning" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Dra för att sortera" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Text" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Redigera mapp" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "Basdomän", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Objektnamn" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "t.ex.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filter" }, - "moveSelectedToOrg": { - "message": "Flytta valda till organisation" - }, "deleteSelected": { "message": "Radera markerade" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "De valda objekten flyttades till $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "Nej" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "Logga in eller skapa ett nytt konto för att komma åt ditt valv." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Logga in på Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Inloggning påbörjad" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Skicka" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Lösenordsledtråd" - }, - "enterEmailToGetHint": { - "message": "Ange din e-postadress för att få din huvudlösenordsledtråd." - }, "getMasterPasswordHint": { "message": "Hämta huvudlösenordsledtråd" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "En avisering har skickats till din enhet." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Kom ihåg mig" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Skicka e-postmeddelandet med verifieringskoden igen" }, "useAnotherTwoStepMethod": { "message": "Använd en annan metod för tvåstegsverifiering" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Sätt in din YubiKey i din dators USB-port och tryck på dess knapp." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Alternativ för tvåstegsverifiering" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Förlorat åtkomst till alla dina metoder för tvåstegsverifiering? Använd din återställningskod för att inaktivera tvåstegsverifiering på ditt konto." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Migrerad från FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "E-post" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Välj en organisation som du vill flytta detta objektet till. Flytt till en organisation överför ägandet av objektet till den organisationen. Du kommer inte längre att vara direkt ägare till detta objekt när det har flyttats." }, - "moveManyToOrgDesc": { - "message": "Välj en organisation som du vill flytta dessa objekt till. Flytt till en organisation överför ägandet av objekten till den organisationen. Du kommer inte längre att vara direkt ägare till dessa objekt när de har flyttats." - }, "collectionsDesc": { "message": "Redigera de samlingar som detta objekt delas med. Endast organisationsanvändare med tillgång till dessa samlingar kommer att kunna se detta objekt." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "Du har valt $COUNT$ objekt. $MOVEABLE_COUNT$ objekt kan flyttas till en organisation, $NONMOVEABLE_COUNT$ kan det inte.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Verifieringskod (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Undvik tvetydiga tecken", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Återskapa lösenord" - }, "length": { "message": "Längd" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Vänligen logga in igen." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Vänligen logga in igen. Om du använder andra Bitwarden-applikationer, logga ut och in igen i dem också." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Farozon" }, - "dangerZoneDesc": { - "message": "Var försiktig, dessa åtgärder går inte att ångra!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Avauktorisera sessioner" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Om du fortsätter kommer du loggas ut från sin nuvarande session vilket kräver att du loggar in igen. Du kommer även behöva verifiera med tvåstegsverifiering om det är aktiverat. Aktiva sessioner på andra enheter kan fortsätta vara aktiva i upp till en timme." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "Alla sessioner avauktoriserades" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Att aktivera tvåstegsverifiering kan låsa ute dig från ditt Bitwarden-konto permanent. En återställningskod låter dig komma åt ditt konto om du inte längre kan använda din vanliga metod för tvåstegsverifiering (t.ex. om du förlorar din enhet). Bitwardens kundservice kommer inte att kunna hjälpa dig om du förlorar åtkomst till ditt konto. Vi rekommenderar att du skriver ner eller skriver ut återställningskoden och förvarar den på ett säkert ställe." }, + "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." + }, "viewRecoveryCode": { "message": "Visa återställningskod" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Hantera" }, - "canManage": { - "message": "Kan hantera" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Stäng av" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Återkalla åtkomst" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "Denna metod för tvåstegsverifiering är aktiverad på ditt konto." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "Det gick inte att läsa säkerhetsnyckeln. Försök igen." }, - "twoFactorWebAuthnWarning": { - "message": "På grund av plattformsbegränsningar kan WebAuthn inte användas i alla Bitwarden-applikationer. Du bör aktivera en annan metod för tvåstegsverifiering så att du kan komma åt ditt konto när WebAuthn inte kan användas. Plattformar som stöds:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Webbvalvet och webbläsartillägg på en stationär eller bärbar dator med en WebAuthn-aktiverad webbläsare (Chrome, Opera, Vivaldi eller Firefox med FIDO U2F aktiverat)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Din återställningskod för tvåstegsverifiering" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "Denna användare använder tvåstegsverifiering för att skydda sitt konto." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Olänkad SSO för användare $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Enhet" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Skapa konto på" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "Du använder en webbläsare som inte stöds. Webbvalvet kanske inte fungerar som det ska." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "Om du inte kan komma åt ditt konto genom dina vanliga metoder för tvåstegsverifiering kan du använda din återställningskod för att inaktivera alla metoder för tvåstegsverifiering på ditt konto." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Återställ kontots tvåstegsverifiering" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Ange minimikrav för huvudlösenordsstyrka." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Kräv tvåstegsverifiering" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Ange minimikrav för lösenordsgeneratorn." }, - "passwordGeneratorPolicyInEffect": { - "message": "En eller flera organisationspolicyer påverkar dina generatorinställningar." - }, "masterPasswordPolicyInEffect": { "message": "En eller flera organisationspolicyer kräver att ditt huvudlösenord uppfyller följande krav:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Organisationens ägare och administratörer är undantagna från denna policy." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "Fil" }, "sendTypeText": { "message": "Text" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Ny Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Radera Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Är du säker på att du vill radera denna Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "Vilken typ av Send är detta?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Raderingsdatum" }, - "deletionDateDesc": { - "message": "Denna Send kommer att raderas permanent på angivet datum och klockslag.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maximalt antal åtkomster" }, - "maxAccessCountDesc": { - "message": "Om angivet kommer användare inte längre kunna komma åt denna Send när det maximala antalet åtkomster har uppnåtts.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Nuvarande antal åtkomster" - }, - "sendPasswordDesc": { - "message": "Kräv valfritt ett lösenord för att användare ska komma åt denna Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Privata anteckningar om denna Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Inaktiverad" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Är du säker på att du vill ta bort lösenordet?" }, - "hideEmail": { - "message": "Dölj min e-postadress för mottagare." - }, - "disableThisSend": { - "message": "Inaktivera denna Send så att ingen kan komma åt den.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Alla Sends" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Väntar på radering" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Utgången" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "Följande organisationspolicyer är aktiva just nu:" - }, - "sendDisableHideEmailInEffect": { - "message": "Användare tillåts inte dölja sin e-postadress för 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." - }, "modifiedPolicyId": { "message": "Ändrade policyn $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Inaktivera personligt ägarskap för organisationens användare" }, - "textHiddenByDefault": { - "message": "When accessing the Send, hide the text by default", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Texten du vill skicka." - }, - "sendFileDesc": { - "message": "Filen du vill skicka." - }, - "copySendLinkOnSave": { - "message": "Copy the link to share this Send to my clipboard upon save." - }, - "sendLinkLabel": { - "message": "Send-länk", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "Det gick inte att spara raderings- och utgångsdatum." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "Klicka på knappen nedan för att verifiera din 2FA." }, "webAuthnAuthenticate": { "message": "Autentisera WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn stöds inte i denna webbläsare." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Fel" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "Vad skulle du vilja generera?" - }, - "passwordType": { - "message": "Lösenordstyp" - }, - "regenerateUsername": { - "message": "Återskapa användarnamn" - }, "generateUsername": { "message": "Generera användarnamn" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Användarnamnstyp" - }, "plusAddressedEmail": { "message": "Plus addressed email", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Slumpmässigt", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Värdnamn", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API-åtkomsttoken" - }, "deviceVerification": { "message": "Device verification" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Starta Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Antal användare" }, - "loggingInAs": { - "message": "Loggar in som" - }, - "notYou": { - "message": "Är det inte du?" - }, "pickAnAvatarColor": { "message": "Välj en avatarfärg" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "Ingen samling" }, - "canView": { - "message": "Kan visa" - }, - "canViewExceptPass": { - "message": "Kan visa, utom lösenord" - }, - "canEdit": { - "message": "Kan redigera" - }, - "canEditExceptPass": { - "message": "Kan redigera, utom lösenord" - }, "noCollectionsAdded": { "message": "Ingen samlingar lades till" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Godkänn med huvudlösenord" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Betrodda enheter" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Inga enhetsförfrågningar" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "Du kommer att meddelas vid godkännande." - }, "troubleLoggingIn": { "message": "Problem med att logga in?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Ägare och administratörer kan hantera alla samlingar och objekt" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Aliasdomän" - }, "alreadyHaveAccount": { "message": "Har du redan ett konto?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Ge grupper eller medlemmar tillgång till denna samling." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "månad per medlem" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Läs mer om Bitwardens API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Nyckelalgoritm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingeravtryck" }, @@ -9774,10 +10088,6 @@ "message": "Inkludera specialtecken", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Lägg till bilaga" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index 36ab0050700..f334131b69b 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Critical applications" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "What type of item is this?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Notes" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Note" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Drag to sort" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Text" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Edit folder" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "Base domain", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "ex.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filter" }, - "moveSelectedToOrg": { - "message": "Move selected to organization" - }, "deleteSelected": { "message": "Delete selected" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "No" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "Log in or create a new account to access your secure vault." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Submit" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Password hint" - }, - "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." - }, "getMasterPasswordHint": { "message": "Get master password hint" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Two-step login options" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Lost access to all of your two-step login providers? Use your recovery code to turn off all two-step login providers from your account." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Migrated from FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "Email" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." }, - "moveManyToOrgDesc": { - "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved." - }, "collectionsDesc": { "message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Verification code (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Regenerate password" - }, "length": { "message": "Length" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Danger zone" }, - "dangerZoneDesc": { - "message": "Careful, these actions are not reversible!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Deauthorize sessions" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Proceeding will also log you out of your current session, requiring you to log back in. You will also be prompted for two-step login again, if set up. Active sessions on other devices may continue to remain active for up to one hour." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "All sessions deauthorized" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Setting up two-step login can permanently lock you out of your Bitwarden account. A recovery code allows you to access your account in the event that you can no longer use your normal two-step login provider (example: you lose your device). Bitwarden support will not be able to assist you if you lose access to your account. We recommend you write down or print the recovery code and keep it in a safe place." }, + "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." + }, "viewRecoveryCode": { "message": "View recovery code" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Manage" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Turn off" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Revoke access" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "This two-step login provider is active on your account." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "There was a problem reading the security key. Try again." }, - "twoFactorWebAuthnWarning": { - "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used. Supported platforms:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Web vault and browser extensions on a desktop/laptop with a WebAuthn supported browser (Chrome, Opera, Vivaldi, or Firefox with FIDO U2F turned on)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Your Bitwarden two-step login recovery code" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "If you cannot access your account through your normal two-step login methods, you can use your two-step login recovery code to turn off all two-step providers on your account." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Recover account two-step login" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Set requirements for master password strength." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Require two-step login" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Set requirements for password generator." }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." - }, "masterPasswordPolicyInEffect": { "message": "One or more organization policies require your master password to meet the following requirements:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Organization owners and admins are exempt from this policy's enforcement." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "File" }, "sendTypeText": { "message": "Text" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Delete Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Are you sure you want to delete this Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "What type of Send is this?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maximum access count" }, - "maxAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Disabled" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Are you sure you want to remove the password?" }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, - "disableThisSend": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "All Sends" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Pending deletion" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Expired" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "The following organization policies are currently in effect:" - }, - "sendDisableHideEmailInEffect": { - "message": "Users are not allowed 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." - }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Remove individual ownership for organization users" }, - "textHiddenByDefault": { - "message": "When accessing the Send, hide the text by default", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to Send." - }, - "sendFileDesc": { - "message": "The file you want to Send." - }, - "copySendLinkOnSave": { - "message": "Copy the link to share this Send to my clipboard upon save." - }, - "sendLinkLabel": { - "message": "Send link", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "To verify your 2FA please click the button below." }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn is not supported in this browser." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, - "regenerateUsername": { - "message": "Regenerate username" - }, "generateUsername": { "message": "Generate username" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Username type" - }, "plusAddressedEmail": { "message": "Plus addressed email", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Hostname", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API access token" - }, "deviceVerification": { "message": "Device verification" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Number of users" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "pickAnAvatarColor": { "message": "Pick an avatar color" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "No collection" }, - "canView": { - "message": "Can view" - }, - "canViewExceptPass": { - "message": "Can view, except passwords" - }, - "canEdit": { - "message": "Can edit" - }, - "canEditExceptPass": { - "message": "Can edit, except passwords" - }, "noCollectionsAdded": { "message": "No collections added" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domain" - }, "alreadyHaveAccount": { "message": "Already have an account?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index c731b9ff87e..7d2685807b3 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Critical applications" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "What type of item is this?" }, @@ -159,6 +201,9 @@ "notes": { "message": "หมายเหตุ" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Note" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "ลากเพื่อเรียงลำดับ" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "ข้อความ" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "แก้​ไข​โฟลเดอร์" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "โดเมนพื้นฐาน", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Item name" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "เช่น", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filter" }, - "moveSelectedToOrg": { - "message": "Move selected to organization" - }, "deleteSelected": { "message": "ลบรายการที่เลือก" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "ไม่ใช่" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "ล็อกอิน หรือ สร้างบัญชีใหม่ เพื่อใช้งานตู้นิรภัยของคุณ" }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Verify your Identity" }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "ส่ง" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "คำใบ้รหัสผ่าน" - }, - "enterEmailToGetHint": { - "message": "กรอกอีเมลของบัญชีของคุณ เพื่อรับคำใบ้เกี่ยวกับรหัสผ่านหลักของคุณ" - }, "getMasterPasswordHint": { "message": "รับคำใบ้เกี่ยวกับรหัสผ่านหลักของคุณ" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "A notification has been sent to your device." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "Version $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "จำการเข้าระบบของฉันไว้" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "ส่งรหัสยืนยันไปยังอีเมลอีกครั้ง" }, "useAnotherTwoStepMethod": { "message": "ใช้วิธีลงชื่อเข้าใช้แบบสองขั้นตอนวิธีอื่น" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "ตัวเลือกการเข้าสู่ระบบแบบสองขั้นตอน" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Lost access to all of your two-step login providers? Use your recovery code to turn off all two-step login providers from your account." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Migrated from FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "อีเมล" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "เลือกองค์กรที่คุณต้องการย้ายรายการนี้ไป การย้ายไปยังองค์กรจะโอนความเป็นเจ้าของรายการไปยังองค์กรนั้น คุณจะไม่ได้เป็นเจ้าของโดยตรงของรายการนี้อีกต่อไปเมื่อมีการย้ายแล้ว" }, - "moveManyToOrgDesc": { - "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved." - }, "collectionsDesc": { "message": "Edit the collections that this item is being shared with. Only organization users with access to these collections will be able to see this item." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Verification code (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Regenerate password" - }, "length": { "message": "Length" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Danger zone" }, - "dangerZoneDesc": { - "message": "Careful, these actions are not reversible!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Deauthorize sessions" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Proceeding will also log you out of your current session, requiring you to log back in. You will also be prompted for two-step login again, if set up. Active sessions on other devices may continue to remain active for up to one hour." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "All sessions deauthorized" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Setting up two-step login can permanently lock you out of your Bitwarden account. A recovery code allows you to access your account in the event that you can no longer use your normal two-step login provider (example: you lose your device). Bitwarden support will not be able to assist you if you lose access to your account. We recommend you write down or print the recovery code and keep it in a safe place." }, + "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." + }, "viewRecoveryCode": { "message": "View recovery code" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Manage" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Turn off" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Revoke access" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "This two-step login provider is active on your account." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "There was a problem reading the security key. Try again." }, - "twoFactorWebAuthnWarning": { - "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used. Supported platforms:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Web vault and browser extensions on a desktop/laptop with a WebAuthn supported browser (Chrome, Opera, Vivaldi, or Firefox with FIDO U2F turned on)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Your Bitwarden two-step login recovery code" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "If you cannot access your account through your normal two-step login methods, you can use your two-step login recovery code to turn off all two-step providers on your account." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Recover account two-step login" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Set requirements for master password strength." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Require two-step login" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Set requirements for password generator." }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." - }, "masterPasswordPolicyInEffect": { "message": "One or more organization policies require your master password to meet the following requirements:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Organization owners and admins are exempt from this policy's enforcement." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "File" }, "sendTypeText": { "message": "Text" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Delete Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Are you sure you want to delete this Send?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "What type of Send is this?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maximum access count" }, - "maxAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Disabled" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Are you sure you want to remove the password?" }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, - "disableThisSend": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "All Sends" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Pending deletion" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Expired" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "The following organization policies are currently in effect:" - }, - "sendDisableHideEmailInEffect": { - "message": "Users are not allowed 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." - }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Remove individual ownership for organization users" }, - "textHiddenByDefault": { - "message": "When accessing the Send, hide the text by default", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to Send." - }, - "sendFileDesc": { - "message": "The file you want to Send." - }, - "copySendLinkOnSave": { - "message": "Copy the link to share this Send to my clipboard upon save." - }, - "sendLinkLabel": { - "message": "Send link", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "To verify your 2FA please click the button below." }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn is not supported in this browser." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Generator", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, - "regenerateUsername": { - "message": "Regenerate username" - }, "generateUsername": { "message": "Generate username" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Username type" - }, "plusAddressedEmail": { "message": "Plus addressed email", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Hostname", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API access token" - }, "deviceVerification": { "message": "Device verification" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Number of users" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "pickAnAvatarColor": { "message": "Pick an avatar color" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "No collection" }, - "canView": { - "message": "Can view" - }, - "canViewExceptPass": { - "message": "Can view, except passwords" - }, - "canEdit": { - "message": "Can edit" - }, - "canEditExceptPass": { - "message": "Can edit, except passwords" - }, "noCollectionsAdded": { "message": "No collections added" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Owners and admins can manage all collections and items" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias domain" - }, "alreadyHaveAccount": { "message": "Already have an account?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index ba53ebe7dd4..030fd0dd9b2 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Kritik uygulamalar" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "Riskli üyeler" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Riskli uygulamalar ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Toplam üye" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Toplam uygulama" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Kritik uygulama işareti başarıyla kaldırıldı" + }, "whatTypeOfItem": { "message": "Bu kaydın türü nedir?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Notlar" }, + "privateNote": { + "message": "Özel not" + }, "note": { "message": "Not" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Sıralamak için sürükleyin" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Metin" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Klasörü düzenle" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "Yeni klasör" + }, + "folderName": { + "message": "Klasör adı" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Bu klasörü kalıcı olarak silmek istediğinizden emin misiniz?" + }, "baseDomain": { "message": "Ana alan adı", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Kayıt adı" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "örn.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Filtre" }, - "moveSelectedToOrg": { - "message": "Seçilenleri kuruluşa taşı" - }, "deleteSelected": { "message": "Seçilenleri sil" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Seçilen kayıtlar $ORGNAME$ kuruluşuna taşındı", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Kayıtlar $ORGNAME$ kuruluşuna taşındı", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "Hayır" }, + "location": { + "message": "Konum" + }, "loginOrCreateNewAccount": { "message": "Güvenli kasanıza ulaşmak için giriş yapın veya yeni bir hesap oluşturun." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "E-posta adresinize gönderilen kodu girin" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Kimlik doğrulama uygulamanızdaki kodu girin" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { - "message": "Kimliğinizi doğrulayın" + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "Bu cihazı tanıyamadık. Kimliğinizi doğrulamak için e-postanıza gönderilen kodu girin." + }, + "continueLoggingIn": { + "message": "Giriş yapmaya devam et" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." }, "logInInitiated": { "message": "Giriş başlatıldı" }, + "logInRequestSent": { + "message": "İstek gönderildi" + }, "submit": { "message": "Gönder" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Hesabınızın e-posta adresini girdiğinizde parola ipucunuz size gönderilecektir" }, - "passwordHint": { - "message": "Parola ipucu" - }, - "enterEmailToGetHint": { - "message": "Ana parola ipucunu almak için hesabınızın e-posta adresini girin." - }, "getMasterPasswordHint": { "message": "Ana parola ipucunu al" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "Cihazınıza bir bildirim gönderildi." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Hesabınıza erişmeye mi çalışıyorsunuz?" + }, + "accessAttemptBy": { + "message": "$EMAIL$ erişim denemesi", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Erişimi onayla" + }, + "denyAccess": { + "message": "Erişimi reddet" + }, + "notificationSentDeviceAnchor": { + "message": "web uygulaması" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "Cihazınıza bir bildirim gönderildi" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Lütfen hesabınızın kilidinin açık olduğundan ve parmak izi ifadesinin diğer cihazla eşleştiğinden emin olun" - }, "versionNumber": { "message": "Sürüm $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Beni hatırla" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Doğrulama kodu e-postasını yeniden gönder" }, "useAnotherTwoStepMethod": { "message": "Başka bir iki aşamalı giriş yöntemini kullan" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "YubiKey'inizi bilgisayarınızın USB portuna takın, ardından düğmesine dokunun." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "İki aşamalı giriş seçenekleri" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "İki aşamalı giriş sağlayıcılarınıza ulaşamıyor musunuz? Kurtarma kodunuzu kullanarak hesabınızdaki tüm iki aşamalı giriş sağlayıcılarını devre dışı bırakabilirsiniz." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(FIDO'dan taşındı)" }, + "openInNewTab": { + "message": "Yeni sekmede aç" + }, "emailTitle": { "message": "E-posta" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Bu kaydı taşımak istediğiniz kuruluşu seçin. Taşıdığınız kaydın sahipliği seçtiğiniz kuruluşa aktarılacak. Artık bu kaydın doğrudan sahibi olmayacaksınız." }, - "moveManyToOrgDesc": { - "message": "Bu kayıtları taşımak istediğiniz kuruluşu seçin. Taşıdığınız kayıtların sahipliği seçtiğiniz kuruluşa aktarılacak. Artık bu kayıtların doğrudan sahibi olmayacaksınız." - }, "collectionsDesc": { "message": "Bu kaydın şu anda paylaşıldığı koleksiyonları düzenler. Kuruluştaki kullanıcılardan yalnızca bu koleksiyonlara erişimi olanlar bu kaydı görebilir." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "$COUNT$ kayıt seçtiniz. $MOVEABLE_COUNT$ kayıt bir kuruluşa taşınabilir, $NONMOVEABLE_COUNT$ kayıt taşınamaz.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Doğrulama kodu (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Okurken karışabilecek karakterleri kullanma", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Yeni parola oluştur" - }, "length": { "message": "Uzunluk" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Lütfen yeniden giriş yapın." }, + "currentSession": { + "message": "Geçerli oturum" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Lütfen yeniden oturum açın. Diğer Bitwarden uygulamalarını kullanıyorsanız onlarda da oturumunuzu kapatıp yeniden açın." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Tehlikeli bölge" }, - "dangerZoneDesc": { - "message": "Dikkatli olun, bu işlemleri geri alamazsınız!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Oturumları kapat" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Devam ederseniz geçerli oturumunuz da sonlanacak ve yeniden oturum açmanız gerekecek. İki aşamalı girişi etkinleştirdiyseniz onu da tamamlamanız gerekecek. Diğer cihazlardaki aktif oturumlar bir saate kadar aktif kalabilir." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "Tüm oturumlar kapatıldı" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "İki aşamalı girişi etkinleştirmek, Bitwarden hesabınızı kalıcı olarak kilitleyebilir. Kurtarma kodunuz, iki aşamalı giriş sağlayıcınızı kullanamamanız durumunda hesabınıza erişmenize olanak sağlar (ör. cihazınızı kaybedersiniz). Hesabınıza erişiminizi kaybederseniz Bitwarden destek ekibi size yardımcı olamaz. Kurtarma kodunu not almanızı veya yazdırmanızı ve güvenli bir yerde saklamanızı öneririz." }, + "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." + }, "viewRecoveryCode": { "message": "Kurtarma kodunu göster" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Yönet" }, - "canManage": { - "message": "Yönetebilir" + "manageCollection": { + "message": "Koleksiyonu yönet" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Devre dışı bırak" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Erişimi iptal et" }, + "revoke": { + "message": "İptal et" + }, "twoStepLoginProviderEnabled": { "message": "Bu iki aşamalı giriş sağlayıcısı hesabınızda etkin durumda." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "Güvenlik anahtarını okurken bir sorun oluştu. Tekrar deneyin." }, - "twoFactorWebAuthnWarning": { - "message": "Platform sınırlamaları nedeniyle WebAuthn tüm Bitwarden uygulamalarında kullanılamaz. WebAuthn kullanılamadığında hesabınıza erişebilmek için başka bir iki aşamalı doğrulama yöntemi ayarlamanız gerekir. Desteklenen platformlar:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "WebAuthn uyumlu bir tarayıcıya (FIDO U2F uyumlu Chrome, Opera, Vivaldi veya Firefox) sahip bir bilgisayardaki tarayıcı uzantıları." + "twoFactorWebAuthnWarning1": { + "message": "Platform sınırlamaları nedeniyle WebAuthn tüm Bitwarden uygulamalarında kullanılamaz. WebAuthn kullanılamadığında hesabınıza erişebilmek için başka bir iki aşamalı doğrulama yöntemi ayarlamanız gerekir." }, "twoFactorRecoveryYourCode": { "message": "Bitwarden iki aşamalı giriş kurtarma kodunuz" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "1 davetiyeniz kaldı." + }, + "inviteZeroEmailDesc": { + "message": "0 davetiyeniz kaldı." + }, "userUsingTwoStep": { "message": "Bu kullanıcı hesabını korumak için iki aşamalı giriş kullanıyor." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "SSO bağlantısı kesildi." + }, "unlinkedSsoUser": { "message": "$ID$ kullanıcısı için SSO bağlantısı kesildi.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Cihaz" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Onay gerekiyor" + }, + "areYouTryingtoLogin": { + "message": "Giriş yapmaya mı çalışıyorsunuz?" + }, + "logInAttemptBy": { + "message": "$EMAIL$ giriş denemesi", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Cihaz türü" + }, + "ipAddress": { + "message": "IP adresi" + }, + "confirmLogIn": { + "message": "Girişi onayla" + }, + "denyLogIn": { + "message": "Girişi reddet" + }, + "thisRequestIsNoLongerValid": { + "message": "Bu istek artık geçerli değil." + }, + "logInConfirmedForEmailOnDevice": { + "message": "$DEVICE$ cihazında $EMAIL$ girişi onaylandı", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "Başka bir cihazdan giriş isteğini reddettiniz. Yanlışlıkla yaptıysanız aynı cihazdan yeniden giriş yapmayı deneyin." + }, + "loginRequestHasAlreadyExpired": { + "message": "Giriş isteğinin süresi doldu." + }, + "justNow": { + "message": "Az önce" + }, + "requestedXMinutesAgo": { + "message": "$MINUTES$ dakika önce istendi", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Hesap oluşturuluyor:" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "Desteklenmeyen bir web tarayıcısı kullanıyorsunuz. Web kasası düzgün çalışmayabilir." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "Hesabınıza normalde kullandığınız iki aşamalı giriş yöntemleriyle erişemiyorsanız iki aşamalı giriş kurtarma kodunuzu kullanarak heasbınızdaki tüm iki aşamalı giriş sağlayıcılarını kapatabilirsiniz." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Hesabı iki aşamalı girişten kurtar" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Şifreleme anahtarı güncellemesine devam edilemiyor" }, + "editFieldLabel": { + "message": "$LABEL$ alanını düzenle", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Ana parola gücü gereksinimlerini ayarlayın." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "İki adımlı girişi zorunlu tut" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Parola üreticisi gereksinimleri ayarlayın." }, - "passwordGeneratorPolicyInEffect": { - "message": "Bir ya da daha fazla kuruluş ilkesi, oluşturucu ayarlarınızı etkiliyor." - }, "masterPasswordPolicyInEffect": { "message": "Bir veya daha fazla kuruluş ilkesi gereğince ana parolanız aşağıdaki gereksinimleri karşılamalıdır:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Kuruluş sahipleri ve yöneticileri bu ilkenin uygulanmasından muaf tutulur." }, + "limitSendViews": { + "message": "Gösterimi sınırla" + }, + "limitSendViewsHint": { + "message": "Bu sınıra ulaşıldıktan sonra bu Send'i kimse göremez.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ gösterim kaldı", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send ayrıntıları", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Paylaşılacak metin" + }, "sendTypeFile": { "message": "Dosya" }, "sendTypeText": { "message": "Metin" }, + "sendPasswordDescV3": { + "message": "Alıcıların bu Send'e erişmesi için isterseniz parola ekleyebilirsiniz.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Yeni Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Send'i sil", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Bu Send'i silmek istediğinizden emin misiniz?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "Bu ne tür bir Send?", + "deleteSendPermanentConfirmation": { + "message": "Bu Send'i kalıcı olarak silmek istediğinizden emin misiniz?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Silinme tarihi" }, - "deletionDateDesc": { - "message": "Bu Send belirtilen tarih ve saatte kalıcı olacak silinecek.", + "deletionDateDescV2": { + "message": "Bu Send belirtilen tarihte kalıcı olacak silinecek.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maksimum erişim sayısı" }, - "maxAccessCountDesc": { - "message": "Bunu ayarlarsanız maksimum erişim sayısına ulaşıldıktan sonra bu Send'e erişilemeyecektir.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Mevcut erişim sayısı" - }, - "sendPasswordDesc": { - "message": "Kullanıcıların bu Send'e erişmek için parola girmelerini isteyebilirsiniz.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Bu Send ile ilgili özel notlar.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Devre dışı" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Parolayı kaldırmak istediğinizden emin misiniz?" }, - "hideEmail": { - "message": "E-posta adresimi alıcılardan gizle." - }, - "disableThisSend": { - "message": "Kimsenin erişememesi için bu Send'i devre dışı bırak.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Tüm Send'ler" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Silinmesi bekleniyor" }, + "hideTextByDefault": { + "message": "Metni varsayılan olarak gizle" + }, "expired": { "message": "Süresi dolmuş" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "Şu anda yürüklükte olan kuruluş ilkeleri:" - }, - "sendDisableHideEmailInEffect": { - "message": "Kullanıcıların Send oluştururken veya düzenlerken alıcılardan e-posta adreslerini gizlemelerine izin verilmiyor.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "modifiedPolicyId": { "message": "$ID$ ilkesi düzenlendi.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Kuruluş kullanıcıları için kişisel sahipliği kapat" }, - "textHiddenByDefault": { - "message": "Send'e erişirken varsayılan olarak metni gizle", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Bu Send'i açıklayan anlaşılır bir ad.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Göndermek istediğiniz metin." - }, - "sendFileDesc": { - "message": "Göndermek istediğiniz dosya." - }, - "copySendLinkOnSave": { - "message": "Kaydettikten sonra bu Send'i paylaşma linkini panoya kopyala." - }, - "sendLinkLabel": { - "message": "Send bağlantısı", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "Silinme ve son kullanma tarihleriniz kaydedilirken bir hata oluştu." }, + "hideYourEmail": { + "message": "E-posta adresimi Send'i görüntüleyenlerden gizle." + }, "webAuthnFallbackMsg": { "message": "İki aşamalı doğrulamanızı onaylamak için aşağıdaki düğmeye tıklayın." }, "webAuthnAuthenticate": { "message": "WebAutn ile doğrula" }, + "readSecurityKey": { + "message": "Güvenlik anahtarını oku" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn bu tarayıcıda desteklenmiyor." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Hata" }, + "decryptionError": { + "message": "Şifre çözme sorunu" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Oluşturucu", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "Ne oluşturmak istersiniz?" - }, - "passwordType": { - "message": "Parola türü" - }, - "regenerateUsername": { - "message": "Kullanıcı adını yeniden oluştur" - }, "generateUsername": { "message": "Kullanıcı adı oluştur" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Kullanıcı adı türü" - }, "plusAddressedEmail": { "message": "Artı adresli e-posta", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Alan adınızın tüm iletileri yakalamaya ayarlanmış adresini kullanın." }, + "useThisEmail": { + "message": "Bu e-postayı kullan" + }, "random": { "message": "Rastgele", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Sunucu", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API erişim token'ı" - }, "deviceVerification": { "message": "Cihaz doğrulama" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Hesabınız için Duo iki adımlı giriş gereklidir." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Duo'yu başlat" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Kullanıcı sayısı" }, - "loggingInAs": { - "message": "Giriş yapılan kullanıcı:" - }, - "notYou": { - "message": "Siz değil misiniz?" - }, "pickAnAvatarColor": { "message": "Bir avatar rengi seçin" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "Koleksiyon yok" }, - "canView": { - "message": "Görüntüleyebilir" - }, - "canViewExceptPass": { - "message": "Parolalar hariç görüntüleyebilir" - }, - "canEdit": { - "message": "Düzenleyebilir" - }, - "canEditExceptPass": { - "message": "Parolalar hariç düzenleyebilir" - }, "noCollectionsAdded": { "message": "Hiç koleksiyon eklenmedi" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Yönetici onayı iste" }, - "approveWithMasterPassword": { - "message": "Ana parola ile onayla" - }, "trustedDeviceEncryption": { "message": "Güvenilir cihaz şifrelemesi" }, "trustedDevices": { "message": "Güvenilen cihazlar" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Üyeler kimliklerini doğruladıktan sonra, cihazlarında saklanan anahtarı kullanarak kasa verilerinin şifresini çözebilecektir. Bu seçeneği kullanırsanız", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "tek kuruluş", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "single organization", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": "ilkesi,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "policy,", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "SSO zorunlu", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO required", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "ilkesi ve", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "policy, and", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "hesap kurtarma yönetimi", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "account recovery administration", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "ilkesi otomatik kayıtla birlikte açılacaktır.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Kuruluş izinleriniz güncellendi ve bir ana parola belirlemeniz gerekiyor.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "İsteği onayla" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Cihazı kaldır" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Cihaz isteği yok" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "İsteğiniz yöneticinize gönderildi." }, - "youWillBeNotifiedOnceApproved": { - "message": "Onaylandıktan sonra bilgilendirileceksiniz." - }, "troubleLoggingIn": { "message": "Giriş yaparken sorun mu yaşıyorsunuz?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Koleksiyon silmeyi sahipler ve yöneticilerle sınırlandırın" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Sahipler ve yöneticiler tüm koleksiyonları ve öğeleri yönetebilir" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Alias alan adı" - }, "alreadyHaveAccount": { "message": "Zaten hesabınız var mı?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Gruplara veya üyelere bu koleksiyona erişim izni verin." @@ -9032,7 +9313,7 @@ "message": "SCIM" }, "scimIntegrationDescStart": { - "message": "Configure ", + "message": "Yapılandır ", "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, "scimIntegrationDescEnd": { @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9312,7 +9611,7 @@ "message": "Export client report" }, "memberAccessReport": { - "message": "Member access" + "message": "Üye erişimi" }, "memberAccessReportDesc": { "message": "Ensure members have access to the right credentials and their accounts are secure. Use this report to obtain a CSV of member access and account configurations." @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "Dosya Send'i" + }, "fileSends": { "message": "Dosya Send'leri" }, + "textSend": { + "message": "Metin Send'i" + }, "textSends": { "message": "Metin Send'leri" }, @@ -9638,11 +9943,20 @@ "message": "Access to Premium features" }, "additionalStorageGbMessage": { - "message": "GB additional storage" + "message": "GB ek depolama" }, "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Özel anahtar" + }, + "sshPublicKey": { + "message": "Ortak anahtar" + }, + "sshFingerprint": { + "message": "Parmak izi" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9656,13 +9970,13 @@ "message": "ED25519" }, "sshKeyAlgorithmRSA2048": { - "message": "RSA 2048-Bit" + "message": "RSA 2048 bit" }, "sshKeyAlgorithmRSA3072": { - "message": "RSA 3072-Bit" + "message": "RSA 3072 bit" }, "sshKeyAlgorithmRSA4096": { - "message": "RSA 4096-Bit" + "message": "RSA 4096 bit" }, "premiumAccounts": { "message": "6 premium hesap" @@ -9704,7 +10018,7 @@ "message": "20 makine hesabı" }, "current": { - "message": "Current" + "message": "Geçerli" }, "secretsManagerSubscriptionInfo": { "message": "Secrets Manager aboneliğiniz seçtiğiniz plana göre yükseltilecektir" @@ -9774,10 +10088,6 @@ "message": "Özel karakterleri dahil et", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Dosya ekle" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Önemli uyarı" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Cihazlar" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Hesabınıza aşağıdaki cihazlardan giriş yapıldı." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "Girdiğiniz parola yanlış." + }, + "importSshKey": { + "message": "İçe aktar" + }, + "confirmSshKeyPassword": { + "message": "Parolayı onaylayın" + }, + "enterSshKeyPasswordDesc": { + "message": "SSH anahtarının parolasını girin." + }, + "enterSshKeyPassword": { + "message": "Parolayı girin" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Bir şeyler ters gitti..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Uzantıyı aç" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Uzantıyı yükle" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Ücretsiz kuruluşların en fazla 2 koleksiyonu olabilir. Daha fazla koleksiyon eklemek için ücretli bir plana geçin." } } diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index c17befc27ce..7d9eb316821 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Критичні програми" }, + "noCriticalAppsAtRisk": { + "message": "Немає критичних програм із ризиком" + }, "accessIntelligence": { "message": "Управління доступом" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "Ризиковані учасники" }, + "atRiskMembersWithCount": { + "message": "Учасники з ризиком ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Програми з ризиком ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "Ці учасники використовують у програмах слабкі, викриті, або повторювані паролі." + }, + "atRiskApplicationsDescription": { + "message": "Ці програми мають слабкі, викриті, або повторювані паролі." + }, + "atRiskMembersDescriptionWithApp": { + "message": "Ці учасники використовують у $APPNAME$ слабкі, викриті, або повторювані паролі.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Всього учасників" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Всього програм" }, + "unmarkAsCriticalApp": { + "message": "Зняти позначку критичної програми" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Позначку критичної програми знято" + }, "whatTypeOfItem": { "message": "Який це тип запису?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Нотатки" }, + "privateNote": { + "message": "Приватна нотатка" + }, "note": { "message": "Нотатка" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Перетягніть, щоб відсортувати" }, + "dragToReorder": { + "message": "Потягніть, щоб упорядкувати" + }, "cfTypeText": { "message": "Текст" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Редагування" }, + "editWithName": { + "message": "Редагувати $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "Нова тека" + }, + "folderName": { + "message": "Назва теки" + }, + "folderHintText": { + "message": "Зробіть теку вкладеною, вказавши після основної теки \"/\". Наприклад: Обговорення/Форуми" + }, + "deleteFolderPermanently": { + "message": "Ви дійсно хочете остаточно видалити цю теку?" + }, "baseDomain": { "message": "Основний домен", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Назва запису" }, - "cannotRemoveViewOnlyCollections": { - "message": "Ви не можете вилучати збірки, маючи дозвіл лише на перегляд: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "зразок", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Фільтр" }, - "moveSelectedToOrg": { - "message": "Перемістити вибране до організації" - }, "deleteSelected": { "message": "Видалити вибране" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Вибрані записи переміщено до $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Записи переміщено до $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "Ні" }, + "location": { + "message": "Розташування" + }, "loginOrCreateNewAccount": { "message": "Для доступу до сховища увійдіть в обліковий запис, або створіть новий." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Увійти в Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Введіть код, надісланий вам електронною поштою" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Введіть код з програми автентифікації" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Натисніть свій YubiKey для автентифікації" + }, "authenticationTimeout": { "message": "Час очікування автентифікації" }, "authenticationSessionTimedOut": { "message": "Час очікування сеансу автентифікації завершився. Перезапустіть процес входу в систему." }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "Підтвердьте свою особу" }, + "weDontRecognizeThisDevice": { + "message": "Ми не розпізнаємо цей пристрій. Введіть код, надісланий на вашу електронну пошту, щоб підтвердити вашу особу." + }, + "continueLoggingIn": { + "message": "Продовжити вхід" + }, + "whatIsADevice": { + "message": "Що таке пристрій?" + }, + "aDeviceIs": { + "message": "Пристрій – це унікальне встановлення програми Bitwarden, де ви увійшли в систему. Перевстановлення, очищення даних програми або очищення файлів cookie може призвести до того, що пристрій з'являтиметься кілька разів." + }, "logInInitiated": { "message": "Ініційовано вхід" }, + "logInRequestSent": { + "message": "Запит надіслано" + }, "submit": { "message": "Відправити" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Введіть адресу е-пошти свого облікового запису і вам буде надіслано підказку для пароля" }, - "passwordHint": { - "message": "Підказка для пароля" - }, - "enterEmailToGetHint": { - "message": "Введіть свою адресу е-пошти, щоб отримати підказку для головного пароля." - }, "getMasterPasswordHint": { "message": "Отримати підказку для головного пароля" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "Сповіщення було надіслано на ваш пристрій." }, + "notificationSentDevicePart1": { + "message": "Розблокуйте Bitwarden на своєму пристрої або у " + }, + "areYouTryingToAccessYourAccount": { + "message": "Ви намагаєтесь отримати доступ до свого облікового запису?" + }, + "accessAttemptBy": { + "message": "Спроба доступу з $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Підтвердити доступ" + }, + "denyAccess": { + "message": "Заборонити доступ" + }, + "notificationSentDeviceAnchor": { + "message": "вебпрограмі" + }, + "notificationSentDevicePart2": { + "message": "Перш ніж підтверджувати, обов'язково перевірте відповідність зазначеної нижче фрази відбитка." + }, + "notificationSentDeviceComplete": { + "message": "Розблокуйте Bitwarden на своєму пристрої. Перш ніж підтверджувати переконайтеся, що фраза відбитка збігається з наведеною нижче." + }, "aNotificationWasSentToYourDevice": { "message": "Сповіщення надіслано на ваш пристрій" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Переконайтеся, що ваш обліковий запис розблоковано і фраза відбитка на іншому пристрої збігається" - }, "versionNumber": { "message": "Версія $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Запам'ятати мене" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Більше не запитувати на цьому пристрої протягом 30 днів" + }, "sendVerificationCodeEmailAgain": { "message": "Надіслати код підтвердження ще раз" }, "useAnotherTwoStepMethod": { "message": "Інший спосіб двоетапної перевірки" }, + "selectAnotherMethod": { + "message": "Обрати інший спосіб", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Скористайтеся своїм кодом відновлення" + }, "insertYubiKey": { "message": "Вставте свій YubiKey в USB порт комп'ютера, потім торкніться цієї кнопки." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Налаштування двоетапної перевірки" }, + "selectTwoStepLoginMethod": { + "message": "Виберіть спосіб двоетапної перевірки" + }, "recoveryCodeDesc": { "message": "Втратили доступ до всіх провайдерів двоетапної перевірки? Скористайтеся кодом відновлення, щоб вимкнути двоетапну перевірку для свого облікового запису." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Перенесено з FIDO)" }, + "openInNewTab": { + "message": "Відкрити в новій вкладці" + }, "emailTitle": { "message": "Е-пошта" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Виберіть організацію, до якої ви бажаєте перемістити цей запис. Переміщуючи до організації, власність запису передається тій організації. Ви більше не будете єдиним власником цього запису після переміщення." }, - "moveManyToOrgDesc": { - "message": "Виберіть організацію, до якої ви бажаєте перемістити ці записи. Переміщуючи до організації, власність записів передається тій організації. Ви більше не будете єдиним власником цих записів після переміщення." - }, "collectionsDesc": { "message": "Редагуйте збірки, з якими цей запис знаходиться в спільному доступі. Лише учасники організацій з доступом до цих збірок матимуть можливість бачити цей запис." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "Ви вибрали $COUNT$ запис(ів). $MOVEABLE_COUNT$ запис(ів) можна перемістити до організації, $NONMOVEABLE_COUNT$ не можна.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Код підтвердження (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Уникати неоднозначних символів", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Генерувати новий" - }, "length": { "message": "Довжина" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Повторно виконайте вхід." }, + "currentSession": { + "message": "Поточний сеанс" + }, + "requestPending": { + "message": "Запит в очікуванні" + }, "logBackInOthersToo": { "message": "Будь ласка, повторно виконайте вхід. Якщо ви користуєтесь іншими програмами Bitwarden, також вийдіть із них, і знову увійдіть." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Небезпечна зона" }, - "dangerZoneDesc": { - "message": "Обережно, ці дії неможливо скасувати!" - }, - "dangerZoneDescSingular": { - "message": "Обережно, цю дію неможливо скасувати!" - }, "deauthorizeSessions": { "message": "Завершити сеанси" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Продовжуючи, ви також вийдете з поточного сеансу і необхідно буде виконати вхід знову. Ви також отримаєте повторний запит двоетапної перевірки, якщо вона увімкнена. Активні сеанси на інших пристроях можуть залишатися активними протягом години." }, + "newDeviceLoginProtection": { + "message": "Вхід з нового пристрою" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Вимкнути захист входу з нового пристрою" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Увімкнути захист входу з нового пристрою" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Виконайте наведені нижче дії, щоб вимкнути надсилання підтверджень під час входу з нового пристрою." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Виконайте наведені нижче дії, щоб увімкнути надсилання підтверджень під час входу з нового пристрою." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "Якщо захист входу з нового пристрою вимкнено, будь-хто може отримати доступ до вашого облікового запису з будь-якого пристрою, знаючи головний пароль. Щоб захистити свій обліковий запис і не вмикати надсилання підтверджень, налаштуйте двоетапну перевірку." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "Зміни захисту входу з нового пристрою збережено" + }, "sessionsDeauthorized": { "message": "Усі сеанси завершено" }, @@ -1988,7 +2102,7 @@ "message": "Показувати піктограми вебсайтів" }, "faviconDesc": { - "message": "Показувати впізнаване зображення біля кожного запису." + "message": "Показувати зображення біля кожного запису." }, "default": { "message": "Типово" @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Увімкнення двоетапної перевірки може цілком заблокувати доступ до облікового запису Bitwarden. Код відновлення дає вам змогу отримати доступ до свого облікового запису у випадку, якщо ви не можете скористатися провайдером двоетапної перевірки (наприклад, якщо втрачено пристрій). Служба підтримки Bitwarden не зможе допомогти відновити доступ до вашого облікового запису. Ми радимо вам записати чи надрукувати цей код відновлення і зберігати його в надійному місці." }, + "yourSingleUseRecoveryCode": { + "message": "Одноразовий код відновлення можна використати для вимкнення двоетапної перевірки у випадку, якщо ви втратите доступ до вашого провайдера двоетапної перевірки. Bitwarden рекомендує вам записати код відновлення і зберігати його в надійному місці." + }, "viewRecoveryCode": { "message": "Переглянути код відновлення" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Керувати" }, - "canManage": { - "message": "Може керувати" + "manageCollection": { + "message": "Керувати збіркою" + }, + "viewItems": { + "message": "Переглянути записи" + }, + "viewItemsHidePass": { + "message": "Перегляд записів, прихованих паролів" + }, + "editItems": { + "message": "Редагувати записи" + }, + "editItemsHidePass": { + "message": "Редагування записів, прихованих паролів" }, "disable": { "message": "Вимкнути" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Відкликати доступ" }, + "revoke": { + "message": "Відкликати" + }, "twoStepLoginProviderEnabled": { "message": "Для вашого облікового запису увімкнено цей спосіб двоетапної перевірки." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "Сталася проблема при читанні ключа безпеки. Спробуйте знову." }, - "twoFactorWebAuthnWarning": { - "message": "У зв'язку з обмеженнями платформи, WebAuthn не можна використовувати в усіх програмах Bitwarden. Вам слід активувати іншого провайдера двоетапної перевірки, щоб мати змогу отримати доступ до свого облікового запису, коли неможливо скористатися WebAuthn. Підтримувані платформи:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Вебсховище і розширення браузера на комп'ютерах і ноутбуках з браузерами, що мають підтримку WebAuthn (Chrome, Opera, Vivaldi, або Firefox з увімкненим FIDO U2F)." + "twoFactorWebAuthnWarning1": { + "message": "У зв'язку з обмеженнями платформи, WebAuthn не можна використовувати в усіх програмах Bitwarden. Вам слід активувати іншого провайдера двоетапної перевірки, щоб мати змогу отримати доступ до свого облікового запису, коли неможливо скористатися WebAuthn." }, "twoFactorRecoveryYourCode": { "message": "Ваш код відновлення двоетапної перевірки Bitwarden" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "У вас залишилось 1 запрошення." + }, + "inviteZeroEmailDesc": { + "message": "У вас залишилося 0 запрошень." + }, "userUsingTwoStep": { "message": "Цей користувач використовує двоетапну перевірку для захисту свого облікового запису." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Від'єднаний SSO." + }, "unlinkedSsoUser": { "message": "Користувач $ID$ не має пов'язаного SSO.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Пристрій" }, + "loginStatus": { + "message": "Стан входу в систему" + }, + "firstLogin": { + "message": "Перший вхід" + }, + "trusted": { + "message": "Надійний" + }, + "needsApproval": { + "message": "Потребує підтвердження" + }, + "areYouTryingtoLogin": { + "message": "Ви намагаєтесь увійти?" + }, + "logInAttemptBy": { + "message": "Спроба входу з $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Тип пристрою" + }, + "ipAddress": { + "message": "IP-адреса" + }, + "confirmLogIn": { + "message": "Підтвердити вхід" + }, + "denyLogIn": { + "message": "Заборонити вхід" + }, + "thisRequestIsNoLongerValid": { + "message": "Цей запит більше недійсний." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Підтверджено вхід для $EMAIL$ на $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "Ви відхилили спробу входу з іншого пристрою. Якщо це були дійсно ви, спробуйте увійти з пристроєм знову." + }, + "loginRequestHasAlreadyExpired": { + "message": "Термін дії запиту на вхід завершився." + }, + "justNow": { + "message": "Щойно" + }, + "requestedXMinutesAgo": { + "message": "Запитано $MINUTES$ хвилин тому", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Створення облікового запису" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "Ви використовуєте непідтримуваний браузер. Вебсховище може працювати неправильно." }, + "youHaveAPendingLoginRequest": { + "message": "Ви маєте запит на вхід до системи з іншого пристрою." + }, + "reviewLoginRequest": { + "message": "Переглянути запит входу" + }, "freeTrialEndPromptCount": { "message": "Ваш безплатний пробний період завершується через $COUNT$ днів.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "Якщо вам не вдається отримати доступ до свого облікового запису з використанням звичайної двоетапної перевірки, ви можете скористатися своїм кодом відновлення, щоб вимкнути всіх провайдерів двоетапної перевірки для вашого облікового запису." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Увійдіть в систему нижче, використовуючи одноразовий код відновлення. Ця дія вимкне всіх провайдерів двоетапної перевірки для вашого облікового запису." + }, "recoverAccountTwoStep": { "message": "Відновити вхід з використанням двоетапної перевірки" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Неможливо продовжити оновлення ключа шифрування" }, + "editFieldLabel": { + "message": "Редагувати $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Перевпорядкувати $LABEL$. Використовуйте клавіші зі стрілками для переміщення.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ переміщено вгору, позиція $INDEX$ з $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ переміщено вниз, позиція $INDEX$ з $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "Не вдалося розшифрувати ваші теки під час оновлення ключа шифрування. Щоб продовжити оновлення, необхідно видалити теки. Якщо ви продовжите, записи у сховищі не будуть видалені." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Встановіть вимоги надійності головного пароля." }, + "passwordStrengthScore": { + "message": "Рейтинг надійності пароля $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Вимагати двоетапну перевірку" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Встановити вимоги для генерування пароля." }, - "passwordGeneratorPolicyInEffect": { - "message": "На параметри генератора впливають одна чи декілька політик організації." - }, "masterPasswordPolicyInEffect": { "message": "Політика однієї або декількох організацій зобов'язує дотримання таких вимог для головного пароля:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Власники організації та адміністратори звільняються від дотримання цієї політики." }, + "limitSendViews": { + "message": "Ліміт переглядів" + }, + "limitSendViewsHint": { + "message": "Ніхто не може переглянути це відправлення після досягнення ліміту.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "Залишок переглядів: $ACCESSCOUNT$", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Подробиці відправлення", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Текст для поширення" + }, "sendTypeFile": { "message": "Файл" }, "sendTypeText": { "message": "Текст" }, + "sendPasswordDescV3": { + "message": "За бажання додайте пароль для отримувачів цього відправлення.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Нове відправлення", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Видалити відправлення", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Ви дійсно хочете видалити це відправлення?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "Який це тип відправлення?", + "deleteSendPermanentConfirmation": { + "message": "Ви дійсно хочете остаточно видалити це відправлення?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Термін дії" }, - "deletionDateDesc": { - "message": "Відправлення буде остаточно видалено у вказаний час.", + "deletionDateDescV2": { + "message": "Відправлення буде остаточно видалено у вказану дату.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Максимальна кількість доступів" }, - "maxAccessCountDesc": { - "message": "Якщо встановлено, користувачі більше не зможуть отримати доступ до цього відправлення після досягнення максимальної кількості доступів.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Поточна кількість доступів" - }, - "sendPasswordDesc": { - "message": "Ви можете встановити пароль для доступу до цього відправлення.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Особисті нотатки про це відправлення.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Вимкнено" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Ви дійсно хочете вилучити пароль?" }, - "hideEmail": { - "message": "Приховувати мою адресу електронної пошти від отримувачів." - }, - "disableThisSend": { - "message": "Деактивувати це відправлення для скасування доступу до нього.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Усі відправлення" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Очікується видалення" }, + "hideTextByDefault": { + "message": "Типово приховувати текст" + }, "expired": { "message": "Термін дії завершився" }, @@ -5161,13 +5441,6 @@ "message": "Завжди показувати отримувачам адресу е-пошти учасників під час створення чи редагування відправлень.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendOptionsPolicyInEffect": { - "message": "Наразі діють такі політики організації:" - }, - "sendDisableHideEmailInEffect": { - "message": "Користувачам не дозволяється приховувати свою адресу електронної пошти від отримувачів під час створення чи редагування відправлень.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "modifiedPolicyId": { "message": "Змінено політику $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Вилучити особисту власність для учасників організації" }, - "textHiddenByDefault": { - "message": "При доступі до відправлення типово приховувати текст", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Опис цього відправлення.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Текст, який ви хочете відправити." - }, - "sendFileDesc": { - "message": "Файл, який ви хочете відправити." - }, - "copySendLinkOnSave": { - "message": "Копіювати посилання, щоб поділитися відправленням після збереження." - }, - "sendLinkLabel": { - "message": "Посилання на відправлення", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Відправлення", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "При збереженні дат видалення і терміну дії виникла помилка." }, + "hideYourEmail": { + "message": "Приховати адресу е-пошти від отримувачів." + }, "webAuthnFallbackMsg": { "message": "Щоб засвідчити ваш 2FA, натисніть кнопку внизу." }, "webAuthnAuthenticate": { "message": "Автентифікація WebAuthn" }, + "readSecurityKey": { + "message": "Зчитати ключ безпеки" + }, + "awaitingSecurityKeyInteraction": { + "message": "Очікується взаємодія з ключем безпеки..." + }, "webAuthnNotSupported": { "message": "WebAuthn не підтримується в цьому браузері." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Помилка" }, + "decryptionError": { + "message": "Помилка розшифрування" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden не зміг розшифрувати вказані нижче елементи сховища." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Зверніться до служби підтримки клієнтів,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "щоб уникнути втрати даних.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Разом із дозволом на керування відновленням облікового запису також необхідно надати дозвіл на керування користувачами" }, @@ -6516,15 +6791,6 @@ "message": "Генератор", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "Що ви бажаєте згенерувати?" - }, - "passwordType": { - "message": "Тип пароля" - }, - "regenerateUsername": { - "message": "Повторно генерувати ім'я користувача" - }, "generateUsername": { "message": "Генерувати ім'я користувача" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Тип імені користувача" - }, "plusAddressedEmail": { "message": "Адреса е-пошти з плюсом", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Використовуйте свою скриньку вхідних Catch-All власного домену." }, + "useThisEmail": { + "message": "Використати цю е-пошту" + }, "random": { "message": "Випадково", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ відхилив ваш запит. Зверніться до провайдера послуг по допомогу.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ відхилив ваш запит: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Не вдалося отримати ідентифікатор замаскованої е-пошти облікового запису для $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Ім'я вузла", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "Токен доступу до API" - }, "deviceVerification": { "message": "Перевірка пристрою" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Для вашого облікового запису необхідна двоетапна перевірка з Duo." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Для вашого облікового запису необхідно пройти двоетапну перевірку з Duo. Виконайте наведені нижче кроки, щоб завершити вхід." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Виконайте наведені нижче кроки, щоб завершити вхід." + }, "launchDuo": { "message": "Запустити Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Кількість користувачів" }, - "loggingInAs": { - "message": "Вхід у систему як" - }, - "notYou": { - "message": "Не ви?" - }, "pickAnAvatarColor": { "message": "Оберіть колір аватара" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "Немає збірки" }, - "canView": { - "message": "Може переглядати" - }, - "canViewExceptPass": { - "message": "Може переглядати, крім паролів" - }, - "canEdit": { - "message": "Може редагувати" - }, - "canEditExceptPass": { - "message": "Може редагувати, крім паролів" - }, "noCollectionsAdded": { "message": "Не додано жодної збірки" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Запит підтвердження адміністратора" }, - "approveWithMasterPassword": { - "message": "Затвердити з головним паролем" - }, "trustedDeviceEncryption": { "message": "Шифрування довіреного пристрою" }, "trustedDevices": { "message": "Довірені пристрої" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Після автентифікації учасники розшифровуватимуть дані сховища з використанням ключа, збереженого на їхньому пристрої.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Учасникам не потрібен головний пароль під час входу з використанням SSO. Натомість використовується ключ шифрування, що зберігається на пристрої, роблячи цей пристрій довіреним. Перший пристрій, на якому учасник реєструється і входить до системи, буде довіреним. Нові пристрої необхідно схвалити за допомогою наявного довіреного пристрою або адміністратором.", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "Політику єдиної", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "організації,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "політику обов'язкового", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "SSO та", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "політику адміністрування облікового", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "запису з автоматичним розгортанням буде увімкнено, якщо використовується ця опція.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "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.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Оновлено дозволи вашої організації – вимагається встановлення головного пароля.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Схвалити запит" }, + "deviceApproved": { + "message": "Пристрій схвалено" + }, + "deviceRemoved": { + "message": "Пристрій вилучено" + }, + "removeDevice": { + "message": "Вилучити пристрій" + }, + "removeDeviceConfirmation": { + "message": "Ви дійсно хочете вилучити цей пристрій?" + }, "noDeviceRequests": { "message": "Немає запитів з пристрою" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Ваш запит відправлено адміністратору." }, - "youWillBeNotifiedOnceApproved": { - "message": "Ви отримаєте сповіщення після затвердження." - }, "troubleLoggingIn": { "message": "Проблема під час входу?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Дозволити видалення збірок лише власникам та адміністраторам" }, + "limitItemDeletionDesc": { + "message": "Обмежити видалення записів для учасників, які мають дозвіл \"Може керувати\"" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Власники та адміністратори можуть керувати всіма збірками та записами" }, @@ -8494,9 +8778,6 @@ "message": "URL-адреса власного сервера", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Псевдонім домену" - }, "alreadyHaveAccount": { "message": "Вже маєте обліковий запис?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "У вас немає доступу до керування цією збіркою." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Немає дозволу \"Може керувати\"" + "grantManageCollectionWarningTitle": { + "message": "Відсутній дозвіл на керування збіркою" }, - "grantAddAccessCollectionWarning": { - "message": "Надайте дозвіл \"Може керувати\" для можливості повноцінного керування збірками, включно з видаленням." + "grantManageCollectionWarning": { + "message": "Надайте дозвіл для можливості повного керування збіркою, включно з видаленням збірки." }, "grantCollectionAccess": { "message": "Надайте групам або учасникам доступ до цієї збірки." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Налаштуйте керування пристроями для Bitwarden, використовуючи посібник із впровадження для вашої платформи." }, + "deviceIdMissing": { + "message": "Відсутній ID пристрою" + }, + "deviceTypeMissing": { + "message": "Відсутній тип пристрою" + }, + "deviceCreationDateMissing": { + "message": "Відсутня дата створення пристрою" + }, + "desktopRequired": { + "message": "Потрібен комп'ютер" + }, + "reopenLinkOnDesktop": { + "message": "Відкрийте це посилання з електронної пошти на комп'ютері." + }, "integrationCardTooltip": { "message": "Відкрити посібник із впровадження $INTEGRATION$.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "на місяць за учасника" }, + "monthPerMemberBilledAnnually": { + "message": "місяць за учасника сплачується щороку" + }, "seats": { "message": "Місця" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Докладніше про Bitwarden API" }, + "fileSend": { + "message": "Відправлення файлу" + }, "fileSends": { "message": "Відправлення файлів" }, + "textSend": { + "message": "Відправлення тексту" + }, "textSends": { "message": "Відправлення тексту" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Алгоритм ключа" }, + "sshPrivateKey": { + "message": "Закритий ключ" + }, + "sshPublicKey": { + "message": "Відкритий ключ" + }, + "sshFingerprint": { + "message": "Цифровий відбиток" + }, "sshKeyFingerprint": { "message": "Цифровий відбиток" }, @@ -9774,10 +10088,6 @@ "message": "Спеціальні символи", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Додати вкладення" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Код дескриптора" }, + "cannotRemoveViewOnlyCollections": { + "message": "Ви не можете вилучати збірки, маючи дозвіл лише на перегляд: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Важлива інформація" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Вилучити учасників" }, + "devices": { + "message": "Пристрої" + }, + "deviceListDescription": { + "message": "Вхід у ваш обліковий запис виконано на пристроях, зазначених нижче. Якщо ви не розпізнаєте пристрій, вилучіть його." + }, + "deviceListDescriptionTemp": { + "message": "Вхід у ваш обліковий запис виконано на пристроях, зазначених нижче." + }, "claimedDomains": { "message": "Заявлені домени" }, @@ -10020,7 +10348,69 @@ "organizationNameMaxLength": { "message": "Назва організації не може перевищувати 50 символів." }, - "resellerRenewalWarning": { + "sshKeyWrongPassword": { + "message": "Ви ввели неправильний пароль." + }, + "importSshKey": { + "message": "Імпорт" + }, + "confirmSshKeyPassword": { + "message": "Підтвердити пароль" + }, + "enterSshKeyPasswordDesc": { + "message": "Введіть пароль для ключа SSH." + }, + "enterSshKeyPassword": { + "message": "Введіть пароль" + }, + "invalidSshKey": { + "message": "Ключ SSH недійсний" + }, + "sshKeyTypeUnsupported": { + "message": "Тип ключа SSH не підтримується" + }, + "importSshKeyFromClipboard": { + "message": "Імпортувати ключ із буфера обміну" + }, + "sshKeyImported": { + "message": "Ключ SSH успішно імпортовано" + }, + "copySSHPrivateKey": { + "message": "Копіювати закритий ключ" + }, + "openingExtension": { + "message": "Відкриття розширення браузера Bitwarden" + }, + "somethingWentWrong": { + "message": "Щось пішло не так..." + }, + "openingExtensionError": { + "message": "Виникли проблеми з відкриттям розширення браузера Bitwarden. Натисніть кнопку, щоб відкрити його зараз." + }, + "openExtension": { + "message": "Відкрити розширення" + }, + "doNotHaveExtension": { + "message": "Не встановлено розширення браузера Bitwarden?" + }, + "installExtension": { + "message": "Встановити розширення" + }, + "openedExtension": { + "message": "Відкрито розширення браузера" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Розширення браузера Bitwarden успішно відкрито. Тепер ви можете переглянути свої ризиковані паролі." + }, + "openExtensionManuallyPart1": { + "message": "Виникли проблеми з відкриттям розширення браузера Bitwarden. Відкрийте піктограму Bitwarden", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "з панелі інструментів.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { "message": "Ваша передплата невдовзі поновиться. Щоб забезпечити безперебійну роботу, зверніться до $RESELLER$ для підтвердження поновлення до $RENEWAL_DATE$.", "placeholders": { "reseller": { @@ -10033,7 +10423,7 @@ } } }, - "resellerOpenInvoiceWarning": { + "resellerOpenInvoiceWarningMgs": { "message": "Рахунок за вашу передплату випущено $ISSUED_DATE$. Щоб забезпечити безперебійну роботу, зверніться до $RESELLER$ для підтвердження поновлення до $DUE_DATE$.", "placeholders": { "reseller": { @@ -10050,7 +10440,7 @@ } } }, - "resellerPastDueWarning": { + "resellerPastDueWarningMsg": { "message": "Рахунок за вашу передплату ще не сплачено. Щоб забезпечити безперебійну роботу, зверніться до $RESELLER$ для підтвердження поновлення до $GRACE_PERIOD_END$.", "placeholders": { "reseller": { @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Передплату організації розпочато повторно" + }, + "restartSubscription": { + "message": "Розпочніть повторно свою передплату" + }, + "suspendedManagedOrgMessage": { + "message": "Звернутися до $PROVIDER$ по допомогу.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Адміністратори тепер мають можливість видаляти облікові записи учасників, що належать до заявленого домену." + }, + "deleteManagedUserWarningDesc": { + "message": "Ця дія видалить обліковий запис учасника, включно з усіма записами в сховищі. Ця функція замінює попередню дію вилучення." + }, + "deleteManagedUserWarning": { + "message": "Видалення – нова дія!" + }, + "seatsRemaining": { + "message": "У вас залишилося $REMAINING$ місць із загальної кількості ($TOTAL$) місць, призначених цій організації. Зверніться до свого провайдера, щоб керувати передплатою.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Наявна організація" + }, + "selectOrganizationProviderPortal": { + "message": "Виберіть організацію, щоб додати до вашого порталу провайдера." + }, + "noOrganizations": { + "message": "Немає організацій для показу" + }, + "yourProviderSubscriptionCredit": { + "message": "Ваш провайдер отримає кредит за будь-який час, що залишився у передплаті організації." + }, + "doYouWantToAddThisOrg": { + "message": "Ви хочете додати цю організацію до $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Додано наявну організацію" + }, + "assignedExceedsAvailable": { + "message": "Призначені місця перевищують доступні місця." + }, + "changeAtRiskPassword": { + "message": "Змінити ризикований пароль" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Вилучити розблокування з PIN-кодом" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Не дозволяти учасникам розблокувати свій обліковий запис за допомогою PIN-коду." + }, + "limitedEventLogs": { + "message": "Тарифні плани $PRODUCT_TYPE$ не мають доступу до реальних журналів подій", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Отримайте повний доступ до журналу подій організації, передплативши тарифний план Teams або Enterprise." + }, + "upgradeEventLogTitle": { + "message": "Оновіть тарифний план, щоб переглядати реальні дані журналу подій" + }, + "upgradeEventLogMessage": { + "message": "Ці події є лише зразками. Вони не відображають реальних подій у вашій організації Bitwarden." + }, + "cannotCreateCollection": { + "message": "Безплатні організації можуть мати до 2 збірок. Передплатіть тарифний план, щоб додати більше збірок." } } diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index 9f4156014c6..f0a0215f823 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -5,6 +5,9 @@ "criticalApplications": { "message": "Critical applications" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "Access Intelligence" }, @@ -113,6 +116,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "Mục này là gì?" }, @@ -159,6 +201,9 @@ "notes": { "message": "Ghi chú" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "Ghi chú" }, @@ -383,6 +428,9 @@ "dragToSort": { "message": "Kéo để sắp xếp" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "Văn bản" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "Chỉnh sửa thư mục" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "Tên miền cơ sở", "description": "Domain name. Example: website.com" @@ -689,15 +762,6 @@ "itemName": { "message": "Tên mục" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "vd.", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "Bộ lọc" }, - "moveSelectedToOrg": { - "message": "Chuyển mục đã chọn tới Tổ chức" - }, "deleteSelected": { "message": "Xóa mục đã chọn" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "Đã di chuyển các mục đã chọn đến $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "Không" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "Đăng nhập hoặc tạo tài khoản mới để truy cập kho mật khẩu của bạn." }, @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "verifyIdentity": { - "message": "Xác minh danh tính của bạn" + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." }, "logInInitiated": { "message": "Log in initiated" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "Gửi" }, @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "Gợi ý mật khẩu" - }, - "enterEmailToGetHint": { - "message": "Nhập địa chỉ email của tài khoản bạn để nhận gợi ý mật khẩu." - }, "getMasterPasswordHint": { "message": "Nhận gợi ý mật khẩu chính" }, @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "Một thông báo đã được gửi đến thiết bị của bạn." }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, "aNotificationWasSentToYourDevice": { "message": "A notification was sent to your device" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" - }, "versionNumber": { "message": "Phiên bản $VERSION_NUMBER$", "placeholders": { @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "Ghi nhớ đăng nhập" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Gửi lại email xác minh" }, "useAnotherTwoStepMethod": { "message": "Dùng phương pháp xác mnih hai bước khác" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Cắm YubiKey vào cổng USB trên máy tính bạn và bấm nút trên Yubikey." }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "Tùy chọn xác minh hai bước" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "Bạn bị mất quyền truy cập vào tất cả các dịch vụ xác minh hai bước? Sử dụng mã phục hồi của bạn để tắt chúng trên tài khoản bạn." }, @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(Migrated from FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "Email" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." }, - "moveManyToOrgDesc": { - "message": "Choose an organization that you wish to move these items to. Moving to an organization transfers ownership of the items to that organization. You will no longer be the direct owner of these items once they have been moved." - }, "collectionsDesc": { "message": "Chỉnh sửa những bộ sưu tập mà bạn có chia sẻ mục này. Chỉ những thành viên của tổ chức có quyền quản lý những bộ sưu tập đó mới có thể xem được mục này." }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "You have selected $COUNT$ item(s). $MOVEABLE_COUNT$ item(s) can be moved to an organization, $NONMOVEABLE_COUNT$ cannot.", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "Mã xác thực (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "Tạo lại mật khẩu" - }, "length": { "message": "Độ dài" }, @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "Hãy đăng nhập lại." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Vui lòng đăng nhập lại. Nếu bạn đang dùng những ứng dụng Bitwarden khác, vui lòng đăng xuất and đăng nhập lại những ứng dụng đó." }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "Vùng nguy hiểm" }, - "dangerZoneDesc": { - "message": "Cẩn thận, thao tác này không thể khôi phục!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "Gỡ phiên" }, @@ -1797,6 +1890,27 @@ "deauthorizeSessionsWarning": { "message": "Sẽ đăng xuất bạn ra khỏi phiên hiện tại, sau đó cần đăng nhập lại. Bạn cũng sẽ phải đăng nhập hai bước lại nếu bạn có đăng nhập hai bước. Những phiên đăng nhập trên các thiết bị khác sẽ tiếp tục có hiệu lực lên đến 1 tiếng." }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "Tất cả phiên đăng nhập đã bị gỡ" }, @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "Setting up two-step login can permanently lock you out of your Bitwarden account. A recovery code allows you to access your account in the event that you can no longer use your normal two-step login provider (example: you lose your device). Bitwarden support will not be able to assist you if you lose access to your account. We recommend you write down or print the recovery code and keep it in a safe place." }, + "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." + }, "viewRecoveryCode": { "message": "Xem mã khôi phục" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "Quản lý" }, - "canManage": { - "message": "Can manage" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "Vô hiệu hoá" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "Thu hồi quyền truy cập" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "This two-step login provider is active on your account." }, @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "There was a problem reading the security key. Try again." }, - "twoFactorWebAuthnWarning": { - "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used. Supported platforms:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "Web vault and browser extensions on a desktop/laptop with a WebAuthn supported browser (Chrome, Opera, Vivaldi, or Firefox with FIDO U2F turned on)." + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "Your Bitwarden two-step login recovery code" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "Thiết bị" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -3888,6 +4096,12 @@ "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { "message": "Your free trial ends in $COUNT$ days.", "placeholders": { @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "If you cannot access your account through your normal two-step login methods, you can use your two-step login recovery code to turn off all two-step providers on your account." }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "Recover account two-step login" }, @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Không thể tiếp tục cập nhật khóa mã hóa" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "Khi cập nhật khóa mã hóa, các thư mục của bạn không thể được giải mã. Để tiếp tục cập nhật, các thư mục của bạn phải bị xóa. Sẽ không có mục nào bị xóa nếu bạn tiếp tục." }, @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "Set requirements for master password strength." }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "Require two-step login" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "Set requirements for password generator." }, - "passwordGeneratorPolicyInEffect": { - "message": "Các chính sách của tổ chức đang ảnh hưởng đến cài đặt tạo mật khẩu của bạn." - }, "masterPasswordPolicyInEffect": { "message": "One or more organization policies require your master password to meet the following requirements:" }, @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "Organization owners and admins are exempt from this policy's enforcement." }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "Tập tin" }, "sendTypeText": { "message": "Văn bản" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "Mục Gửi mới", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "Xóa mục Gửi", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "Bạn có chắc muốn xóa mục Gửi này?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "Đây là kiểu Gửi gì?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "Mục Gửi sẽ được xóa vĩnh viễn vào ngày và giờ được chỉ định.", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "Maximum access count" }, - "maxAccessCountDesc": { - "message": "Nếu được thiết lập, khi đã đạt tới số lượng truy cập tối đa, người dùng sẽ không thể truy cập mục Gửi này nữa.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Số lần truy cập hiện tại" - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "Đã tắt" }, @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "Are you sure you want to remove the password?" }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, - "disableThisSend": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "Tất cả mục Gửi" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "Pending deletion" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "Expired" }, @@ -5161,13 +5441,6 @@ "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." }, - "sendOptionsPolicyInEffect": { - "message": "The following organization policies are currently in effect:" - }, - "sendDisableHideEmailInEffect": { - "message": "Users are not allowed 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." - }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "Remove individual ownership for organization users" }, - "textHiddenByDefault": { - "message": "When accessing the Send, hide the text by default", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "Một tên gợi nhớ để mô tả mục Gửi này.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "Văn bản bạn muốn gửi." - }, - "sendFileDesc": { - "message": "Tập tin bạn muốn gửi." - }, - "copySendLinkOnSave": { - "message": "Copy the link to share this Send to my clipboard upon save." - }, - "sendLinkLabel": { - "message": "Send link", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Gửi", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "To verify your 2FA please click the button below." }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "WebAuthn is not supported in this browser." }, @@ -5655,6 +5916,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -6516,15 +6791,6 @@ "message": "Trình tạo", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, - "regenerateUsername": { - "message": "Regenerate username" - }, "generateUsername": { "message": "Generate username" }, @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "Username type" - }, "plusAddressedEmail": { "message": "Địa chỉ email có hậu tố", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "Use your domain's configured catch-all inbox." }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "Random", "description": "Generates domain-based username using random letters" @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "Hostname", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API access token" - }, "deviceVerification": { "message": "Device verification" }, @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "Số lượng người dùng" }, - "loggingInAs": { - "message": "Logging in as" - }, - "notYou": { - "message": "Not you?" - }, "pickAnAvatarColor": { "message": "Pick an avatar color" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "No collection" }, - "canView": { - "message": "Can view" - }, - "canViewExceptPass": { - "message": "Can view, except passwords" - }, - "canEdit": { - "message": "Can edit" - }, - "canEditExceptPass": { - "message": "Can edit, except passwords" - }, "noCollectionsAdded": { "message": "No collections added" }, @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "Request admin approval" }, - "approveWithMasterPassword": { - "message": "Approve with master password" - }, "trustedDeviceEncryption": { "message": "Trusted device encryption" }, "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "Your organization permissions were updated, requiring you to set a master password.", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." }, - "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." - }, "troubleLoggingIn": { "message": "Trouble logging in?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Chủ sở hữu và quản trị viên có thể quản lý tất cả các bộ sưu tập và mục" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "Tên miền thay thế" - }, "alreadyHaveAccount": { "message": "Bạn đã có tài khoản?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "Seats" }, @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, @@ -9774,10 +10088,6 @@ "message": "Include special characters", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "Add attachment" }, @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9935,6 +10254,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index f293adcf62a..00bf9bc5973 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -5,8 +5,11 @@ "criticalApplications": { "message": "关键应用程序" }, + "noCriticalAppsAtRisk": { + "message": "没有关键应用程序存在风险" + }, "accessIntelligence": { - "message": "Access Intelligence" + "message": "智慧访问" }, "riskInsights": { "message": "风险洞察" @@ -15,7 +18,7 @@ "message": "密码风险" }, "reviewAtRiskPasswords": { - "message": "跨应用程序审查有风险的密码(弱密码、暴露的密码或重复使用的密码)。选择最关键的应用程序,优先为用户采取安全措施,以解决密码风险问题。" + "message": "审查各个应用程序中存在风险的密码(弱、暴露或重复使用)。选择最关键的应用程序,优先为用户采取安全措施,以解决存在风险的密码问题。" }, "dataLastUpdated": { "message": "数据最后更新于:$DATE$", @@ -78,13 +81,13 @@ } }, "noAppsInOrgDescription": { - "message": "当用户保存登录信息时,应用程序会出现在这里,显示所有有风险的密码。标记关键应用程序并通知用户更新密码。" + "message": "当用户保存登录信息时,应用程序会出现在这里,显示所有存在风险的密码。标记关键应用程序并通知用户更新密码。" }, "noCriticalAppsTitle": { "message": "您未将任何应用程序标记为关键" }, "noCriticalAppsDescription": { - "message": "选择最关键的应用程序来发现有风险的密码,并通知用户更改这些密码。" + "message": "选择最关键的应用程序来发现存在风险的密码,并通知用户更改这些密码。" }, "markCriticalApps": { "message": "标记关键应用程序" @@ -99,7 +102,7 @@ "message": "应用程序" }, "atRiskPasswords": { - "message": "有风险的密码" + "message": "存在风险的密码" }, "requestPasswordChange": { "message": "请求更改密码" @@ -111,17 +114,56 @@ "message": "搜索应用程序" }, "atRiskMembers": { - "message": "有风险的成员" + "message": "存在风险的成员" + }, + "atRiskMembersWithCount": { + "message": "存在风险的成员 ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "存在风险的应用程序 ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "这些成员正在使用弱的、暴露的或重复使用的密码登录到应用程序。" + }, + "atRiskApplicationsDescription": { + "message": "这些应用程序具有弱的、暴露的或重复使用的密码。" + }, + "atRiskMembersDescriptionWithApp": { + "message": "这些成员正在使用弱的、暴露的或重复使用的密码登录到 $APPNAME$。", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } }, "totalMembers": { "message": "总的成员" }, "atRiskApplications": { - "message": "有风险的应用程序" + "message": "存在风险的应用程序" }, "totalApplications": { "message": "总的应用程序" }, + "unmarkAsCriticalApp": { + "message": "取消标记为关键应用程序" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "关键应用程序已成功取消标记" + }, "whatTypeOfItem": { "message": "这是什么类型的项目?" }, @@ -159,8 +201,11 @@ "notes": { "message": "备注" }, + "privateNote": { + "message": "私密备注" + }, "note": { - "message": "备注" + "message": "笔记" }, "customFields": { "message": "自定义字段" @@ -172,7 +217,7 @@ "message": "登录凭据" }, "personalDetails": { - "message": "个人信息" + "message": "个人详细信息" }, "identification": { "message": "身份" @@ -181,10 +226,10 @@ "message": "联系信息" }, "cardDetails": { - "message": "支付卡详情" + "message": "支付卡详细信息" }, "cardBrandDetails": { - "message": "$BRAND$ 详情", + "message": "$BRAND$ 详细信息", "placeholders": { "brand": { "content": "$1", @@ -215,7 +260,7 @@ } }, "websiteAdded": { - "message": "网址已添加" + "message": "网站已添加" }, "addWebsite": { "message": "添加网站" @@ -383,6 +428,9 @@ "dragToSort": { "message": "拖动排序" }, + "dragToReorder": { + "message": "拖动以重新排序" + }, "cfTypeText": { "message": "文本型" }, @@ -412,7 +460,7 @@ "message": "未分配" }, "noneFolder": { - "message": "无文件夹", + "message": "默认文件夹", "description": "This is the folder for uncategorized items" }, "selfOwnershipLabel": { @@ -425,8 +473,33 @@ "editFolder": { "message": "编辑文件夹" }, + "editWithName": { + "message": "编辑 $ITEM$:$NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "新增文件夹" + }, + "folderName": { + "message": "文件夹名称" + }, + "folderHintText": { + "message": "通过在父文件夹名后面添加「/」来嵌套文件夹。示例:Social/Forums" + }, + "deleteFolderPermanently": { + "message": "确定要永久删除这个文件夹吗?" + }, "baseDomain": { - "message": "基础域", + "message": "基础域名", "description": "Domain name. Example: website.com" }, "domainName": { @@ -684,20 +757,11 @@ "message": "项目" }, "itemDetails": { - "message": "项目详情" + "message": "项目详细信息" }, "itemName": { "message": "项目名称" }, - "cannotRemoveViewOnlyCollections": { - "message": "您无法删除仅具有「查看」权限的集合:$COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "例如", "description": "Short abbreviation for 'example'." @@ -815,9 +879,6 @@ "filter": { "message": "筛选" }, - "moveSelectedToOrg": { - "message": "移动所选项目到组织" - }, "deleteSelected": { "message": "删除所选" }, @@ -837,7 +898,7 @@ "message": "添加新附件" }, "deletedAttachment": { - "message": "附件已删除" + "message": "删除了附件" }, "deleteAttachmentConfirmation": { "message": "确定要删除此附件吗?" @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "所选项目已移动到 $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "项目已移动到 $ORGNAME$", "placeholders": { @@ -958,13 +1010,13 @@ "message": "您的登录会话已过期。" }, "restartRegistration": { - "message": "重新开始注册" + "message": "重启注册" }, "expiredLink": { "message": "失效链接" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "请重新注册或尝试登录。" + "message": "请重启注册或尝试登录。" }, "youMayAlreadyHaveAnAccount": { "message": "您可能已经有一个账户了" @@ -984,6 +1036,9 @@ "no": { "message": "否" }, + "location": { + "message": "位置" + }, "loginOrCreateNewAccount": { "message": "登录或创建一个新账户以访问您的安全密码库。" }, @@ -1069,7 +1124,7 @@ "message": "已用于加密" }, "loginWithPasskeyEnabled": { - "message": "已启用通行密钥登录" + "message": "通行密钥登录已开启" }, "passkeySaved": { "message": "$NAME$ 已保存", @@ -1090,10 +1145,10 @@ "message": "如果所有通行密钥被移除,不使用主密码您将无法登录新的设备。" }, "passkeyLimitReachedInfo": { - "message": "通行密钥已达上限。移除一个通行密钥以添加另一个。" + "message": "已达到通行密钥上限。请移除一个通行密钥后再添加其他通行密钥。" }, "tryAgain": { - "message": "请重试" + "message": "重试" }, "createAccount": { "message": "创建账户" @@ -1119,18 +1174,42 @@ "logInToBitwarden": { "message": "登录到 Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "输入发送到您的电子邮箱的代码" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "输入来自您的验证器 App 的代码" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "按下 YubiKey 以验证身份" + }, "authenticationTimeout": { "message": "身份验证超时" }, "authenticationSessionTimedOut": { "message": "身份验证会话超时。请重新启动登录过程。" }, - "verifyIdentity": { + "verifyYourIdentity": { "message": "验证您的身份" }, + "weDontRecognizeThisDevice": { + "message": "我们无法识别这个设备。请输入发送到您电子邮箱中的代码以验证您的身份。" + }, + "continueLoggingIn": { + "message": "继续登录" + }, + "whatIsADevice": { + "message": "什么是设备?" + }, + "aDeviceIs": { + "message": "设备是您登录过的 Bitwarden App 的独立安装。重新安装、清除 App 数据或清除 Cookie,可能会导致设备多次出现。" + }, "logInInitiated": { "message": "登录已发起" }, + "logInRequestSent": { + "message": "请求已发送" + }, "submit": { "message": "提交" }, @@ -1147,7 +1226,7 @@ "message": "主密码" }, "masterPassDesc": { - "message": "主密码是您访问密码库的密码。它非常重要,请您不要忘记。一旦忘记,无任何办法恢复此密码。" + "message": "主密码是用于访问您的密码库的密码。不要忘记您的主密码,这一点非常重要。一旦忘记,无任何办法恢复此密码。" }, "masterPassImportant": { "message": "主密码忘记后,将无法恢复!" @@ -1162,7 +1241,7 @@ "message": "主密码提示(可选)" }, "newMasterPassHint": { - "message": "新的主密码提示(可选)" + "message": "新主密码提示(可选)" }, "masterPassHintLabel": { "message": "主密码提示" @@ -1195,12 +1274,6 @@ "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "输入您的账户电子邮箱地址,您的密码提示将发送给您" }, - "passwordHint": { - "message": "密码提示" - }, - "enterEmailToGetHint": { - "message": "请输入您的账户电子邮箱地址来接收主密码提示。" - }, "getMasterPasswordHint": { "message": "获取主密码提示" }, @@ -1312,7 +1385,7 @@ "message": "没有可列出的事件。" }, "newOrganization": { - "message": "新建组织" + "message": "新增组织" }, "noOrganizationsList": { "message": "您没有加入任何组织。同一组织的用户可以安全地与其他用户共享项目。" @@ -1320,12 +1393,39 @@ "notificationSentDevice": { "message": "通知已发送到您的设备。" }, + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " + }, + "areYouTryingToAccessYourAccount": { + "message": "您正在尝试访问您的账户吗?" + }, + "accessAttemptBy": { + "message": "$EMAIL$ 的访问尝试", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "确认访问" + }, + "denyAccess": { + "message": "拒绝访问" + }, + "notificationSentDeviceAnchor": { + "message": "网页 App" + }, + "notificationSentDevicePart2": { + "message": "在批准前,请确保指纹短语与下面的相匹配。" + }, + "notificationSentDeviceComplete": { + "message": "解锁您设备上的 Bitwarden。批准前,请确保指纹短语与下面的相匹配。" + }, "aNotificationWasSentToYourDevice": { "message": "通知已发送到您的设备" }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "确保您的账户已解锁,并且指纹短语与其他设备上的相匹配。" - }, "versionNumber": { "message": "版本: $VERSION_NUMBER$", "placeholders": { @@ -1359,17 +1459,27 @@ "rememberMe": { "message": "记住我" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "30 天内在此设备上不再询问" + }, "sendVerificationCodeEmailAgain": { "message": "再次发送验证码电子邮件" }, "useAnotherTwoStepMethod": { "message": "使用其他两步登录方式" }, + "selectAnotherMethod": { + "message": "选择其他方式", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "使用您的恢复代码" + }, "insertYubiKey": { "message": "将您的 YubiKey 插入计算机的 USB 端口,然后触摸其按钮。" }, "insertU2f": { - "message": "将您的安全钥匙插入计算机的 USB 端口。如果它有按钮,请触摸它。" + "message": "将您的安全密钥插入计算机的 USB 端口。如果它有按钮,请触摸它。" }, "loginUnavailable": { "message": "登录不可用" @@ -1378,13 +1488,16 @@ "message": "此账户已启用两步登录,但此浏览器不支持任何已配置的两步登录提供程序。" }, "noTwoStepProviders2": { - "message": "请使用支持的网页浏览器(例如 Chrome)和/或添加其他支持更广泛的提供程序(例如验证器 App)。" + "message": "请使用受支持的网页浏览器(例如 Chrome),和/或添加其他跨网页浏览器支持更好的提供程序(例如验证器 App)。" }, "twoStepOptions": { "message": "两步登录选项" }, + "selectTwoStepLoginMethod": { + "message": "选择两步登录方式" + }, "recoveryCodeDesc": { - "message": "失去对您所有的双重身份验证设备的访问?请使用您的恢复代码来停用您账户中所有的两步登录提供程序。" + "message": "失去对您所有的双重身份验证设备的访问?请使用您的恢复代码来关闭您账户中所有的两步登录提供程序。" }, "recoveryCodeTitle": { "message": "恢复代码" @@ -1397,7 +1510,7 @@ "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP 安全钥匙" + "message": "Yubico OTP 安全密钥" }, "yubiKeyDesc": { "message": "使用 YubiKey 4、5 或 NEO 设备。" @@ -1407,24 +1520,27 @@ "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { - "message": "为您的组织使用 Duo Security 的 Duo 移动 App、短信、电话或 U2F 安全钥匙来进行验证。", + "message": "为您的组织使用 Duo Security 的 Duo 移动 App、短信、电话或 U2F 安全密钥来进行验证。", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "u2fDesc": { - "message": "使用任何 FIDO U2F 兼容的安全钥匙访问您的账户。" + "message": "使用任何 FIDO U2F 兼容的安全密钥访问您的账户。" }, "u2fTitle": { - "message": "FIDO U2F 安全钥匙" + "message": "FIDO U2F 安全密钥" }, "webAuthnTitle": { - "message": "FIDO2 WebAuthn" + "message": "通行密钥" }, "webAuthnDesc": { - "message": "使用您设备的生物识别或 WebAuthn 兼容的安全钥匙。" + "message": "使用您设备的生物识别或 FIDO2 兼容的安全密钥。" }, "webAuthnMigrated": { "message": "(迁移自 FIDO)" }, + "openInNewTab": { + "message": "在新标签页中打开" + }, "emailTitle": { "message": "电子邮箱" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "选择一个您想将此项目移至的组织。移动到组织会将该项目的所有权转让给该组织。移动后,您将不再是此项目的直接所有者。" }, - "moveManyToOrgDesc": { - "message": "选择一个您想将这些项目移至的组织。移动到组织会将这些项目的所有权转让给该组织。移动后,您将不再是这些项目的直接所有者。" - }, "collectionsDesc": { "message": "编辑与其共享此项目的集合。只有具有这些集合访问权限的组织用户才能看到此项目。" }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "您选择了 $COUNT$ 个项目。$MOVEABLE_COUNT$ 个项目可以移动到组织,$NONMOVEABLE_COUNT$ 不能。", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "验证码 (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "避免易混淆的字符", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "重新生成密码" - }, "length": { "message": "长度" }, @@ -1692,7 +1785,7 @@ "message": "继续操作将更改您的账户电子邮箱地址。这不会更改用于双重身份验证的电子邮箱地址。您可以在两步登录设置中更改它。" }, "newEmail": { - "message": "新的电子邮箱" + "message": "新电子邮箱" }, "code": { "message": "代码" @@ -1707,7 +1800,7 @@ } }, "loggedOutWarning": { - "message": "接下来将会注销您当前的会话,要求您重新登录。其他设备上的活动会话可能会继续保持最多一小时。" + "message": "继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" }, "emailChanged": { "message": "电子邮箱已保存" @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "请重新登录。" }, + "currentSession": { + "message": "当前会话" + }, + "requestPending": { + "message": "请求待处理" + }, "logBackInOthersToo": { "message": "请重新登录。如果您还在使用其他 Bitwarden 应用程序,也请注销并重新登陆。" }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "危险操作区" }, - "dangerZoneDesc": { - "message": "当心,这些操作无法撤销!" - }, - "dangerZoneDescSingular": { - "message": "当心,此操作无法撤销!" - }, "deauthorizeSessions": { "message": "取消会话授权" }, @@ -1795,7 +1888,28 @@ "message": "您是否担心自己的账户在其他设备上登录过?请按照以下步骤取消对之前使用过的所有计算机或设备的授权。如果您以前使用过公共计算机或不小心曾将密码保存在不属于您的设备上,则建议执行此安全步骤。此步骤还将清除所有以前记住的两步登录会话。" }, "deauthorizeSessionsWarning": { - "message": "接下来将会注销您当前的会话,并要求您重新登录。如果有设置两步登录,也需要重新认证。其他设备上的活动会话可能会继续保持最多一小时。" + "message": "继续操作还将使您退出当前会话,并要求您重新登录。如果有设置两步登录,也需要重新验证。其他设备上的活动会话可能会继续保持活动状态长达一小时。" + }, + "newDeviceLoginProtection": { + "message": "新设备登录" + }, + "turnOffNewDeviceLoginProtection": { + "message": "关闭新设备登录保护" + }, + "turnOnNewDeviceLoginProtection": { + "message": "开启新设备登录保护" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "继续下面的操作以关闭 Bitwarden 在您从新设备登录时发送验证电子邮件的功能。" + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "继续下面的操作以开启 Bitwarden 在您从新设备登录时发送验证电子邮件的功能。" + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "关闭新设备登录保护后,任何拥有您的主密码的人都可以从任何设备访问您的账户。要在没有验证电子邮件的情况下保护您的账户,请设置两步登录。" + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "新设备登录保护更改已保存" }, "sessionsDeauthorized": { "message": "已取消所有会话授权" @@ -1973,7 +2087,7 @@ "message": "偏好设置" }, "preferencesDesc": { - "message": "自定义您的网页版密码库。" + "message": "自定义您的网页密码库。" }, "preferencesUpdated": { "message": "偏好设置已保存" @@ -2015,10 +2129,10 @@ "message": "自定义" }, "newCustomDomain": { - "message": "添加自定义域名" + "message": "新增自定义域名" }, "newCustomDomainDesc": { - "message": "输入用逗号分隔的域名列表。只能输入「基础」域名,不要输入子域名。例如,输入「google.com」而不是「www.google.com」。您也可以输入「androidapp://package.name」以将 Android App 与其他网站域名关联。" + "message": "输入用逗号分隔的域名列表。只支持「基础」域名,不要输入子域名。例如,输入「google.com」而不是「www.google.com」。您也可以输入「androidapp://package.name」以将 Android App 与其他网站域名关联。" }, "customDomainX": { "message": "自定义域名 $INDEX$", @@ -2045,20 +2159,23 @@ "message": "为您的组织启用两步登录。" }, "twoStepLoginEnterpriseDescStart": { - "message": "要为成员实施 Bitwarden 两步登录选项,请使用 ", + "message": "要为成员强制实施 Bitwarden 两步登录选项,请使用 ", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Enforce Bitwarden Two-step Login options for members by using the Two-step Login Policy.'" }, "twoStepLoginPolicy": { "message": "两步登录策略" }, "twoStepLoginOrganizationDuoDesc": { - "message": "要实施 Duo 方式的两步登录,请使用下面的选项。" + "message": "要强制实施 Duo 方式的两步登录,请使用下面的选项。" }, "twoStepLoginOrganizationSsoDesc": { "message": "如果您已设置或计划设置 SSO,两步登录可能已经通过您的身份提供程序强制实施了。" }, "twoStepLoginRecoveryWarning": { - "message": "启用两步登录可能会将您永久锁定在 Bitwarden 账户之外。如果您无法使用常规的两步登录提供程序(例如您丢失了设备),则可以使用恢复代码访问您的账户。如果您失去对您账户的访问,Bitwarden 支持也无法帮助您。我们建议您记下或打印恢复代码,并将其妥善保管。" + "message": "启用两步登录可能会将您永久锁定在 Bitwarden 账户之外。当您无法使用常规的两步登录提供程序(例如您丢失了设备)时,可以使用恢复代码访问您的账户。如果您失去对您账户的访问,Bitwarden 支持也无法帮助您。我们建议您写下或打印恢复代码,并将其妥善保管。" + }, + "yourSingleUseRecoveryCode": { + "message": "当您无法访问两步登录提供程序时,您的一次性恢复代码可用于关闭两步登录。Bitwarden 建议您写下恢复代码,并将其妥善保管。" }, "viewRecoveryCode": { "message": "查看恢复代码" @@ -2068,10 +2185,10 @@ "description": "Two-step login providers such as YubiKey, Duo, Authenticator apps, Email, etc." }, "enable": { - "message": "启用" + "message": "开启" }, "enabled": { - "message": "已启用" + "message": "已开启" }, "restoreAccess": { "message": "恢复访问权限" @@ -2098,15 +2215,30 @@ "manage": { "message": "管理" }, - "canManage": { - "message": "可以管理" + "manageCollection": { + "message": "管理集合" + }, + "viewItems": { + "message": "查看项目" + }, + "viewItemsHidePass": { + "message": "查看项目,隐藏密码" + }, + "editItems": { + "message": "编辑项目" + }, + "editItemsHidePass": { + "message": "编辑项目,隐藏密码" }, "disable": { - "message": "停用" + "message": "关闭" }, "revokeAccess": { "message": "撤销访问权限" }, + "revoke": { + "message": "撤销" + }, "twoStepLoginProviderEnabled": { "message": "您的账户已启用此两步登录提供程序。" }, @@ -2141,7 +2273,7 @@ "message": "前往 bitwarden.com 吗?" }, "twoStepContinueToBitwardenUrlDesc": { - "message": "Bitwarden 验证器允许您存储验证器密钥以及为两步验证流程生成 TOTP 代码。在 bitwarden.com 网站上了解更多信息。" + "message": "Bitwarden 验证器允许您存储验证器密钥以及为两步验证流程生成 TOTP 代码。访问 bitwarden.com 网站了解更多信息。" }, "twoStepAuthenticatorScanCodeV2": { "message": "用您的验证器 App 扫描下面的二维码或输入密钥。" @@ -2159,10 +2291,10 @@ "message": "如果您要把它添加到其他设备,下面是您的验证器 App 所需要的二维码(或密钥)。" }, "twoStepDisableDesc": { - "message": "确定要停用此两步登录提供程序吗?" + "message": "确定要关闭此两步登录提供程序吗?" }, "twoStepDisabled": { - "message": "此两步登录提供程序已停用。" + "message": "两步登录提供程序已关闭。" }, "twoFactorYubikeyAdd": { "message": "添加一个新的 YubiKey 到您的账户" @@ -2183,7 +2315,7 @@ "message": "由于平台限制,YubiKey 不能在所有 Bitwarden 应用程序上使用。您应该启用另一个两步登录提供程序,以便在无法使用 YubiKey 时可以访问您的账户。支持的平台:" }, "twoFactorYubikeySupportUsb": { - "message": "具有可使用 YubiKey 的 USB 端口的设备上的网页版密码库、桌面应用程序、CLI 以及浏览器扩展。" + "message": "具有可使用 YubiKey 的 USB 端口的设备上的网页密码库、桌面应用程序、CLI 以及浏览器扩展。" }, "twoFactorYubikeySupportMobile": { "message": "具有 NFC 功能或可使用 YubiKey 的数据端口的设备上的移动 App。" @@ -2219,7 +2351,7 @@ "message": "NFC 支持" }, "twoFactorYubikeySupportsNfc": { - "message": "我的一把钥匙支持 NFC。" + "message": "我的一把密钥支持 NFC。" }, "twoFactorYubikeySupportsNfcDesc": { "message": "如果您的某个 YubiKey 支持 NFC(例如 YubiKey NEO),移动设备在检测到 NFC 可用时将提示您。" @@ -2228,7 +2360,7 @@ "message": "YubiKey 已更新" }, "disableAllKeys": { - "message": "禁用全部钥匙" + "message": "禁用全部密钥" }, "twoFactorDuoDesc": { "message": "输入 Duo 管理面板提供的 Bitwarden 应用程序信息。" @@ -2243,7 +2375,7 @@ "message": "API 主机名" }, "twoFactorEmailDesc": { - "message": "按照以下步骤设置使用电子邮箱的两步登录:" + "message": "按照以下步骤设置电子邮箱方式的两步登录:" }, "twoFactorEmailEnterEmail": { "message": "输入您希望用于接收验证码的电子邮箱" @@ -2255,28 +2387,28 @@ "message": "发送电子邮件" }, "twoFactorU2fAdd": { - "message": "添加一个 FIDO U2F 安全钥匙到您的帐户" + "message": "添加一个 FIDO U2F 安全密钥到您的账户" }, "removeU2fConfirmation": { - "message": "确认要删除此安全钥匙吗?" + "message": "确定要移除此安全密钥吗?" }, "twoFactorWebAuthnAdd": { - "message": "添加一个 WebAuthn 安全钥匙到您的账户" + "message": "添加一个 WebAuthn 安全密钥到您的账户" }, "readKey": { - "message": "读取钥匙" + "message": "读取密钥" }, "keyCompromised": { - "message": "密钥被破坏。" + "message": "密钥已损坏。" }, "twoFactorU2fGiveName": { - "message": "给安全钥匙起一个友好的名称来标识它。" + "message": "给安全密钥起一个友好的名称来标识它。" }, "twoFactorU2fPlugInReadKey": { - "message": "将安全钥匙插入计算机的 USB 端口,然后点击「读取钥匙」按钮。" + "message": "将安全密钥插入计算机的 USB 端口,然后点击「读取密钥」按钮。" }, "twoFactorU2fTouchButton": { - "message": "如果安全钥匙有按钮,请触摸它。" + "message": "如果安全密钥有按钮,请触摸它。" }, "twoFactorU2fSaveForm": { "message": "保存表单。" @@ -2285,22 +2417,19 @@ "message": "由于平台限制,FIDO U2F 不能在所有 Bitwarden 应用程序上使用。您应该启用另一个两步登录提供程序,以便在无法使用 FIDO U2F 时可以访问您的账户。支持的平台:" }, "twoFactorU2fSupportWeb": { - "message": "桌面/笔记本电脑上支持 U2F 的浏览器(启用了 FIDO U2F 的 Chrome、Opera、Vivaldi 或 Firefox)中的网页版密码库和浏览器扩展。" + "message": "桌面/笔记本电脑上支持 U2F 的浏览器(开启了 FIDO U2F 的 Chrome、Opera、Vivaldi 或 Firefox)中的网页密码库和浏览器扩展。" }, "twoFactorU2fWaiting": { - "message": "等待您触摸安全钥匙上的按钮" + "message": "等待您触摸安全密钥上的按钮" }, "twoFactorU2fClickSave": { - "message": "单击下面的「保存」按钮,以启用此安全钥匙用于两步登录。" + "message": "单击下面的「保存」按钮,以启用此安全密钥用于两步登录。" }, "twoFactorU2fProblemReadingTryAgain": { - "message": "读取安全钥匙时出现问题,请重试。" + "message": "读取安全密钥时出现问题。请重试。" }, - "twoFactorWebAuthnWarning": { - "message": "由于平台限制,WebAuthn 不能在所有 Bitwarden 应用程序上使用。您应该启用另一个两步登录提供程序,以便在 WebAuthn 无法使用时可以访问您的账户。支持的平台有:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "桌面/笔记本电脑上支持 WebAuthn 的浏览器(启用了 FIDO U2F 的 Chrome、Opera、Vivaldi 或 Firefox)中的网页密码库和浏览器扩展。" + "twoFactorWebAuthnWarning1": { + "message": "由于平台限制,WebAuthn 不能在所有 Bitwarden 应用程序上使用。您应该启用另一个两步登录提供程序,以便在无法使用 WebAuthn 时可以访问您的账户。" }, "twoFactorRecoveryYourCode": { "message": "您的 Bitwarden 两步登录恢复代码" @@ -2346,7 +2475,7 @@ } }, "noUnsecuredWebsites": { - "message": "你的密码库中没有带不安全 URI 的项目。" + "message": "您的密码库中没有带不安全 URI 的项目。" }, "inactive2faReport": { "message": "未激活两步登录" @@ -2371,7 +2500,7 @@ } }, "noInactive2fa": { - "message": "没有在您的密码库发现未配置两步登录的网站。" + "message": "在您的密码库中没有发现未配置两步登录的网站。" }, "instructions": { "message": "说明" @@ -2386,7 +2515,7 @@ "message": "发现暴露的密码" }, "exposedPasswordsFoundReportDesc": { - "message": "我们在您的 $VAULT$ 中发现了 $COUNT$ 个在已知的数据泄露事件中暴露了密码的项目。您应更改它们以使用新的密码。", + "message": "我们在您的 $VAULT$ 中发现了 $COUNT$ 个在已知的数据泄露中暴露了密码的项目。您应更改它们以使用新的密码。", "placeholders": { "count": { "content": "$1", @@ -2399,7 +2528,7 @@ } }, "noExposedPasswords": { - "message": "您的密码库中没有在已知数据泄露事件中被暴露密码的项目。" + "message": "您的密码库中没有在已知数据泄露中暴露了密码的项目。" }, "checkExposedPasswords": { "message": "检查暴露的密码" @@ -2467,7 +2596,7 @@ } }, "noReusedPasswords": { - "message": "您密码库中没有密码重复使用的项目。" + "message": "您密码库中没有密码重复使用的登录项目。" }, "timesReused": { "message": "重复使用次数" @@ -2538,7 +2667,7 @@ "message": "泄漏报告于" }, "reportError": { - "message": "加载报告时发生错误,请重试。" + "message": "加载报告时发生错误。请重试" }, "billing": { "message": "计费" @@ -2562,7 +2691,7 @@ "description": "Add more credit to your account's balance." }, "amount": { - "message": "合计", + "message": "金额", "description": "Dollar amount, or quantity." }, "creditDelayed": { @@ -2572,7 +2701,7 @@ "message": "请确保您的账户有足够的信用额度来用于此购买。如果您的账户信用额度不足,您的默认付款方式将用于补足差额。您可以从「计费」页面向您的账户添加信用额度。" }, "creditAppliedDesc": { - "message": "您账户的信用额度可用于进行消费。任何可用的信用额度将用于自动支付此账户的账单。" + "message": "您账户的信用额度可用于进行消费。任何可用的信用额度将用于自动抵扣此账户的账单。" }, "goPremium": { "message": "升级高级会员", @@ -2606,7 +2735,7 @@ "message": "未来的更多高级功能。敬请期待!" }, "premiumPrice": { - "message": "全部仅需 $PRICE$ /年!", + "message": "所有功能仅需 $PRICE$ /年!", "placeholders": { "price": { "content": "$1", @@ -2615,7 +2744,7 @@ } }, "premiumPriceWithFamilyPlan": { - "message": "升级高级会员仅需 $PRICE$/年,或成为具有 $FAMILYPLANUSERCOUNT$ 位用户以及无限制的家庭共享的高级账户,通过 ", + "message": "升级高级会员仅需 $PRICE$ /年,或成为具有 $FAMILYPLANUSERCOUNT$ 位用户以及无限制的家庭共享的高级账户,通过 ", "placeholders": { "price": { "content": "$1", @@ -2631,7 +2760,7 @@ "message": "Bitwarden 家庭版计划。" }, "addons": { - "message": "附加" + "message": "附加项目" }, "premiumAccess": { "message": "高级会员" @@ -2707,7 +2836,7 @@ "message": "任何未付费订阅都将通过您的付款方式收取费用。" }, "paymentChargedWithTrial": { - "message": "您的计划包含了 7 天的免费试用期。在试用期结束前,不会从您的付款方式中扣款。您可以随时取消。" + "message": "您的计划包含了 7 天的免费试用。在试用期结束前,不会从您的付款方式中扣款。您可以随时取消。" }, "paymentInformation": { "message": "支付信息" @@ -2743,7 +2872,7 @@ "message": "恢复订阅" }, "reinstateConfirmation": { - "message": "确定要移除待处理的取消请求并恢复订阅吗?" + "message": "确定要移除待取消的请求并恢复订阅吗?" }, "reinstated": { "message": "您的订阅已恢复。" @@ -2819,10 +2948,10 @@ "message": "账单" }, "noUnpaidInvoices": { - "message": "没有未支付的账单。" + "message": "无未支付的账单。" }, "noPaidInvoices": { - "message": "没有已支付的账单。" + "message": "无已支付的账单。" }, "paid": { "message": "已支付", @@ -2848,7 +2977,7 @@ "description": "Noun. A refunded payment that was charged." }, "chargesStatement": { - "message": "任何费用将在您的对账单上以 $STATEMENT_NAME$ 显示。", + "message": "任何费用将以 $STATEMENT_NAME$ 出现在您的对账单上。", "placeholders": { "statement_name": { "content": "$1", @@ -2866,10 +2995,10 @@ "message": "添加存储空间将会调整计费总金额,并立即通过您的付款方式进行扣款。第一笔费用将按当前计费周期的剩余时间按比例分配。" }, "storageRemoveNote": { - "message": "移除存储空间将会调整计费总金额,这笔费用将按比例返回下一笔账单费用中。" + "message": "移除存储空间将会调整计费总金额,这笔调整费用将按比例作为信用额度抵扣您的下一笔账单费用。" }, "adjustedStorage": { - "message": "已调整 $AMOUNT$ GB 的存储空间。", + "message": "调整了 $AMOUNT$ GB 存储空间。", "placeholders": { "amount": { "content": "$1", @@ -2911,7 +3040,7 @@ "message": "必须验证您的账户电子邮箱地址。" }, "newOrganizationDesc": { - "message": "组织允许您与他人共享您的密码库的部分内容,以及管理特定实体(例如家族、小型团队或大型公司)的相关用户。" + "message": "组织允许您与他人共享您的密码库的部分内容,以及管理特定实体(例如家庭、小型团队或大型公司)的相关用户。" }, "generalInformation": { "message": "常规信息" @@ -2979,7 +3108,7 @@ "message": "适用于个人使用,与家人和朋友共享。" }, "planNameTeams": { - "message": "团队" + "message": "团队版" }, "planDescTeams": { "message": "适用于企业和其他团队组织。" @@ -2988,7 +3117,7 @@ "message": "团队入门版" }, "planNameEnterprise": { - "message": "企业" + "message": "企业版" }, "planDescEnterprise": { "message": "适用于企业和其他大型组织。" @@ -3090,7 +3219,7 @@ } }, "trialThankYou": { - "message": "感谢您注册 Bitwarden $PLAN$!", + "message": "感谢您注册适用于 $PLAN$ 的 Bitwarden!", "placeholders": { "plan": { "content": "$1", @@ -3117,7 +3246,7 @@ } }, "trialConfirmationEmail": { - "message": "我们已经发送了一封确认邮件到您的团队的计费电子邮箱 " + "message": "我们已经发送了一封确认邮件到您团队的计费电子邮箱 " }, "monthly": { "message": "每月" @@ -3201,7 +3330,7 @@ } }, "removeUserConfirmation": { - "message": "确实要移除此用户吗?" + "message": "确定要移除此用户吗?" }, "removeOrgUserConfirmation": { "message": "移除成员后,他们将不再具有对组织数据的访问权限,并且此操作无法撤销。要将此成员添加回组织,必须再次邀请他们并加入。" @@ -3240,7 +3369,7 @@ "message": "集合信息" }, "deleteCollectionConfirmation": { - "message": "你确定要删除此集合吗?" + "message": "确定要删除此集合吗?" }, "editMember": { "message": "编辑成员" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "您还剩下 1 个邀请。" + }, + "inviteZeroEmailDesc": { + "message": "您还剩下 0 个邀请。" + }, "userUsingTwoStep": { "message": "此用户正在使用两步登录来保护他们的账户。" }, @@ -3354,16 +3489,16 @@ "message": "登录了" }, "changedPassword": { - "message": "更改了帐户密码" + "message": "更改了账户密码" }, "enabledUpdated2fa": { - "message": "保存了两步登录" + "message": "两步登录已保存" }, "disabled2fa": { - "message": "停用了两步登录" + "message": "两步登录已关闭" }, "recovered2fa": { - "message": "从两步登录中恢复了账户" + "message": "从两步登录中恢复了账户。" }, "failedLogin": { "message": "登录失败,密码不正确。" @@ -3385,7 +3520,7 @@ "description": "PIN code. Ex. The short code (often numeric) that you use to unlock a device." }, "exportedVault": { - "message": "导出了密码库" + "message": "密码库已导出" }, "exportedOrganizationVault": { "message": "导出了组织密码库。" @@ -3412,7 +3547,7 @@ } }, "deletedItemId": { - "message": "发送了项目 $ID$ 到回收站。", + "message": "发送项目 $ID$ 到回收站。", "placeholders": { "id": { "content": "$1", @@ -3553,7 +3688,7 @@ } }, "deletedCollections": { - "message": "已删除集合" + "message": "删除了集合" }, "deletedCollectionId": { "message": "删除了集合 $ID$。", @@ -3601,7 +3736,7 @@ } }, "deletedManyGroups": { - "message": "已删除 $QUANTITY$ 个群组。", + "message": "删除了 $QUANTITY$ 个群组。", "placeholders": { "quantity": { "content": "$1", @@ -3655,7 +3790,7 @@ } }, "createdAttachmentForItem": { - "message": "为项目 $ID$ 创建了附件。", + "message": "创建了项目 $ID$ 的附件。", "placeholders": { "id": { "content": "$1", @@ -3673,7 +3808,7 @@ } }, "editedCollectionsForItem": { - "message": "编辑了项目 $ID$ 集合。", + "message": "编辑了项目 $ID$ 的集合。", "placeholders": { "id": { "content": "$1", @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "取消链接 SSO。" + }, "unlinkedSsoUser": { "message": "为用户 $ID$ 取消链接 SSO。", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "设备" }, + "loginStatus": { + "message": "登录状态" + }, + "firstLogin": { + "message": "首次登录" + }, + "trusted": { + "message": "信任" + }, + "needsApproval": { + "message": "需要批准" + }, + "areYouTryingtoLogin": { + "message": "您正在尝试登录吗?" + }, + "logInAttemptBy": { + "message": "$EMAIL$ 的登录尝试", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "设备类型" + }, + "ipAddress": { + "message": "IP 地址" + }, + "confirmLogIn": { + "message": "确认登录" + }, + "denyLogIn": { + "message": "拒绝登录" + }, + "thisRequestIsNoLongerValid": { + "message": "此请求已失效。" + }, + "logInConfirmedForEmailOnDevice": { + "message": "已确认 $EMAIL$ 在 $DEVICE$ 上的登录", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "您拒绝了一个来自其他设备的登录尝试。若确实是您本人,请尝试再次发起设备登录。" + }, + "loginRequestHasAlreadyExpired": { + "message": "登录请求已过期。" + }, + "justNow": { + "message": "刚刚" + }, + "requestedXMinutesAgo": { + "message": "请求于 $MINUTES$ 分钟前", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "创建账户至" }, @@ -3871,7 +4079,7 @@ "message": "电子邮箱已验证" }, "emailVerifiedFailed": { - "message": "无法验证您的电子邮箱。尝试发送新的验证电子邮件。" + "message": "无法验证您的电子邮箱。请尝试发送新的验证电子邮件。" }, "emailVerificationRequired": { "message": "需要验证电子邮箱" @@ -3886,7 +4094,13 @@ "message": "正在生成风险洞察..." }, "updateBrowserDesc": { - "message": "您使用的是不受支持的 Web 浏览器。网页密码库可能无法正常运行。" + "message": "您使用的是不受支持的网页浏览器。网页密码库可能无法正常运行。" + }, + "youHaveAPendingLoginRequest": { + "message": "您有一个来自其他设备的待处理登录请求。" + }, + "reviewLoginRequest": { + "message": "审查登录请求" }, "freeTrialEndPromptCount": { "message": "您的免费试用将于 $COUNT$ 天后结束。", @@ -3950,7 +4164,7 @@ } }, "joinOrganizationDesc": { - "message": "您已被邀请加入上面的组织。要接受邀请,您需要登录或者创建一个 Bitwarden 账户。" + "message": "您已被邀请加入上面的组织。要接受邀请,您需要登录或者创建一个新的 Bitwarden 账户。" }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "通过设置主密码完成加入此组织。" @@ -3980,13 +4194,16 @@ "message": "记住电子邮箱" }, "recoverAccountTwoStepDesc": { - "message": "如果您无法通过常规的两步登录方式访问您的账户,您可以使用两步登录恢复代码来停用账户上的所有两步登录提供程序。" + "message": "如果您无法通过常规的两步登录方式访问您的账户,您可以使用两步登录恢复代码来关闭账户上的所有两步登录提供程序。" + }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "在下方使用您的一次性恢复代码登录。这将关闭您账户上的所有两步提供程序。" }, "recoverAccountTwoStep": { "message": "恢复账户两步登录" }, "twoStepRecoverDisabled": { - "message": "两步登录已在您的账户中停用。" + "message": "两步登录已在您的账户中关闭。" }, "learnMore": { "message": "进一步了解" @@ -4148,7 +4365,7 @@ "description": "Seat = User Seat" }, "subscriptionDesc": { - "message": "调整订阅将导致按比例调整您的计费总金额。如果新邀请的用户超过了您的订阅席位,您将立即收到按比例的额外用户费用。" + "message": "调整订阅将导致按比例调整您的计费总金额。如果新邀请的用户超过了您的订阅席位,您将立即收到按比例的附加用户费用。" }, "subscriptionUserSeats": { "message": "您的订阅一共允许 $COUNT$ 位成员。", @@ -4175,10 +4392,10 @@ "message": "附加选项" }, "additionalOptionsDesc": { - "message": "如需更多订阅管理的帮助,请联系客服支持。" + "message": "如需更多管理您的订阅的帮助,请联系客服支持。" }, "subscriptionUserSeatsUnlimitedAutoscale": { - "message": "调整订阅将导致按比例调整您的计费总金额。如果新邀请的成员超过了您的订阅席位,您将立即收到按比例的额外成员费用。" + "message": "调整订阅将导致按比例调整您的计费总金额。如果新邀请的成员超过了您的订阅席位,您将立即收到按比例的附加成员费用。" }, "smStandaloneTrialSeatCountUpdateMessageFragment1": { "message": "如果您想要添加额外的" @@ -4187,7 +4404,7 @@ "message": "无捆绑优惠的席位,请联系" }, "subscriptionUserSeatsLimitedAutoscale": { - "message": "调整订阅将导致按比例调整您的计费总金额。如果新邀请的成员超过了您的订阅席位,您将立即收到按比例的额外成员费用,直到达到您的 $MAX$ 席位限制。", + "message": "调整订阅将导致按比例调整您的计费总金额。如果新邀请的成员超过了您的订阅席位,您将立即收到按比例的附加成员费用,直到达到您的 $MAX$ 席位限制。", "placeholders": { "max": { "content": "$1", @@ -4259,7 +4476,7 @@ "message": "添加用户席位将会调整计费总金额,并立即通过您的付款方式进行扣款。第一笔费用将按当前计费周期的剩余时间按比例分配。" }, "seatsRemoveNote": { - "message": "移除用户席位将会调整计费总金额,这笔费用将按比例返回下一笔账单费用中。" + "message": "移除用户席位将会调整计费总金额,这笔调整费用将按比例作为信用额度抵扣您的下一笔账单费用。" }, "adjustedSeats": { "message": "调整了 $AMOUNT$ 个用户席位。", @@ -4273,8 +4490,60 @@ "encryptionKeyUpdateCannotProceed": { "message": "无法继续加密密钥更新" }, + "editFieldLabel": { + "message": "编辑 $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "重新排序 $LABEL$。使用方向键向上或向下移动项目。", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ 已上移,位置 $INDEX$ / $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ 已下移,位置 $INDEX$ / $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { - "message": "更新您的加密密钥时,无法解密您的文件夹。要继续更新,文件夹必须被删除。继续操作不会删除密码库项目。" + "message": "更新加密密钥时,无法解密您的文件夹。要继续更新,必须删除文件夹。继续操作不会删除任何密码库项目。" }, "keyUpdated": { "message": "密钥已更新" @@ -4286,7 +4555,7 @@ "message": "为了提高安全性,我们更改了加密方案。请在下方输入您的主密码以立即更新您的加密密钥。" }, "updateEncryptionKeyWarning": { - "message": "更新加密密钥后,您需要注销所有正在使用的 Bitwarden 应用程序(比如移动 App 或者浏览器扩展)后重新登录。注销或者重新登录(这将下载新的加密密钥)失败可能会导致数据损坏。我们会尝试自动为您注销,但是,可能会有所延迟。" + "message": "更新加密密钥后,您需要注销所有当前使用的 Bitwarden 应用程序(例如移动 App 或浏览器扩展)然后重新登录。注销或者重新登录(这将下载新的加密密钥)失败可能会导致数据损坏。我们会尝试自动为您注销,但可能会有所延迟。" }, "updateEncryptionKeyExportWarning": { "message": "您保存的任何已加密导出也将变为无效。" @@ -4313,7 +4582,7 @@ "message": "在创建组织之前,首先需要创建一个免费的个人账户。" }, "refunded": { - "message": "退款" + "message": "已退款" }, "nothingSelected": { "message": "您尚未选择任何内容。" @@ -4397,25 +4666,25 @@ "message": "组织已停用" }, "secretsAccessSuspended": { - "message": "无法访问已停用的组织。请联系您的组织所有者获取协助。" + "message": "无法访问已停用的组织。请联系您的组织所有者寻求帮助。" }, "secretsCannotCreate": { - "message": "无法在已停用的组织中创建机密。请联系您的组织所有者获取协助。" + "message": "无法在已停用的组织中创建机密。请联系您的组织所有者寻求帮助。" }, "projectsCannotCreate": { - "message": "无法在已停用的组织中创建工程。请联系您的组织所有者获取协助。" + "message": "无法在已停用的组织中创建工程。请联系您的组织所有者寻求帮助。" }, "serviceAccountsCannotCreate": { - "message": "无法在已停用的组织中创建服务账户。请联系您的组织所有者获取协助。" + "message": "无法在已停用的组织中创建服务账户。请联系您的组织所有者寻求帮助。" }, "disabledOrganizationFilterError": { - "message": "无法访问已停用组织中的项目。请联系您的组织所有者获取协助。" + "message": "无法访问已停用组织中的项目。请联系您的组织所有者寻求帮助。" }, "licenseIsExpired": { "message": "授权已过期" }, "updatedUsers": { - "message": "用户已更新" + "message": "更新了用户" }, "selected": { "message": "已选择" @@ -4434,7 +4703,7 @@ "description": "ex. A strong password. Scale: Very Weak -> Weak -> Good -> Strong" }, "good": { - "message": "良好", + "message": "良", "description": "ex. A good password. Scale: Very Weak -> Weak -> Good -> Strong" }, "weak": { @@ -4449,7 +4718,7 @@ "message": "脆弱的主密码" }, "weakMasterPasswordDesc": { - "message": "识别到弱密码。请使用一个强密码以保护你的账户。确定要使用弱密码吗?" + "message": "识别到弱密码。请使用一个强密码以保护您的账户。确定要使用弱密码吗?" }, "rotateAccountEncKey": { "message": "同时轮换账户的加密密钥" @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "设置主密码强度要求。" }, + "passwordStrengthScore": { + "message": "密码强度评分 $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "要求两步登录" }, @@ -4554,14 +4832,11 @@ "message": "非所有者或管理员并且其账户未启用两步登录的组织成员将从组织中移除,并将收到一封关于此更改的电子邮件通知。" }, "twoStepLoginPolicyUserWarning": { - "message": "您的组织要求您在您的用户账户上启用两步登录。如果您停用所有两步登录提供程序,您将被自动从这些组织中移除。" + "message": "您是组织的成员,该组织要求在您的用户账户上设置两步登录。如果您关闭所有两步登录提供程序,您将被自动从这些组织中移除。" }, "passwordGeneratorPolicyDesc": { "message": "设置密码生成器要求。" }, - "passwordGeneratorPolicyInEffect": { - "message": "一个或多个组织策略正在影响您的生成器设置。" - }, "masterPasswordPolicyInEffect": { "message": "一个或多个组织策略要求您的主密码满足以下要求:" }, @@ -4662,7 +4937,7 @@ } }, "permanentlyDeletedItemId": { - "message": "永久删除了项目 $ID$", + "message": "项目 $ID$ 已永久删除", "placeholders": { "id": { "content": "$1", @@ -4683,7 +4958,7 @@ "message": "项目已恢复" }, "restoredItemId": { - "message": "恢复了项目 $ID$", + "message": "项目 $ID$ 已恢复", "placeholders": { "id": { "content": "$1", @@ -4807,7 +5082,7 @@ "message": "不是所有者或管理员并且已是其他组织的成员的组织成员将从您的组织中移除。" }, "singleOrgPolicyMemberWarning": { - "message": "不符合要求的成员将被置于撤销状态,直到他们离开所有其他组织。管理员可以豁免,达到要求后,管理员可以恢复他们的成员资格。" + "message": "不符合要求的成员将被置于已撤销状态,直到他们退出所有其他组织,管理员不受此约束。达到要求后,管理员可以恢复他们的成员资格。" }, "requireSso": { "message": "要求单点登录身份验证" @@ -4819,13 +5094,37 @@ "message": "先决条件" }, "requireSsoPolicyReq": { - "message": "激活此策略前,需先开启「单一组织」企业策略。" + "message": "必须先开启「单一组织」企业策略,然后才能激活此策略。" }, "requireSsoPolicyReqError": { "message": "单一组织策略未启用。" }, "requireSsoExemption": { - "message": "组织的所有者和管理员豁免此策略。" + "message": "组织的所有者和管理员不受此策略的约束。" + }, + "limitSendViews": { + "message": "查看次数限制" + }, + "limitSendViewsHint": { + "message": "达到限额后,任何人无法查看此 Send。", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "剩余 $ACCESSCOUNT$ 次查看次数", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send 详细信息", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "要分享的文本" }, "sendTypeFile": { "message": "文件" @@ -4833,8 +5132,12 @@ "sendTypeText": { "message": "文本" }, + "sendPasswordDescV3": { + "message": "添加一个用于接收者访问此 Send 的可选密码。", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { - "message": "创建 Send", + "message": "新增 Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editSend": { @@ -4857,19 +5160,15 @@ "message": "删除 Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "确定要删除此 Send 吗?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "这是什么类型的 Send?", + "deleteSendPermanentConfirmation": { + "message": "您确定要永久删除这个 Send 吗?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "删除日期" }, - "deletionDateDesc": { - "message": "此 Send 将在指定的日期和时间后被永久删除。", + "deletionDateDescV2": { + "message": "此 Send 将在此日期后被永久删除。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "最大访问次数" }, - "maxAccessCountDesc": { - "message": "设置后,当达到最大访问次数时用户将不再能够访问此 Send。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "当前访问次数" - }, - "sendPasswordDesc": { - "message": "可选。用户需要提供密码才能访问此 Send。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "关于此 Send 的私密备注。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "已禁用" }, @@ -4921,14 +5205,7 @@ "message": "密码已移除" }, "removePasswordConfirmation": { - "message": "确定移除此密码?" - }, - "hideEmail": { - "message": "对收件人隐藏我的电子邮箱地址。" - }, - "disableThisSend": { - "message": "停用此 Send 则任何人无法访问它。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + "message": "确定要移除此密码吗?" }, "allSends": { "message": "所有 Send" @@ -4938,7 +5215,10 @@ "description": "This text will be displayed after a Send has been accessed the maximum amount of times." }, "pendingDeletion": { - "message": "等待删除" + "message": "待删除" + }, + "hideTextByDefault": { + "message": "默认隐藏文本" }, "expired": { "message": "已过期" @@ -4952,7 +5232,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendProtectedPasswordDontKnow": { - "message": "不知道密码?请向提供此 Send 的发件人索要密码。", + "message": "不知道密码吗?请向发送者索取访问此 Send 所需的密码。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendHiddenByDefault": { @@ -4978,7 +5258,7 @@ "message": "紧急访问" }, "emergencyAccessDesc": { - "message": "授予和管理可信联系人的紧急访问权限。可信联系人可以在紧急情况下请求获取查看或接管您账户的权限。查阅我们的帮助页面以了解更多关于零知识共享的工作原理和细节。" + "message": "授予和管理可信联系人的紧急访问权限。可信联系人可以在紧急情况下请求获取查看或接管您账户的权限。请访问我们的帮助页面,了解更多关于零知识共享的工作原理和细节。" }, "emergencyAccessOwnerWarning": { "message": "您是一个或多个组织的拥有者。如果您授予紧急联系人接管权限,他们在接管后可作为拥有者持有您的所有权限。" @@ -4993,7 +5273,7 @@ "message": "添加紧急联系人" }, "designatedEmergencyContacts": { - "message": "已指定为紧急联系人" + "message": "指定为紧急联系人" }, "noGrantedAccess": { "message": "您尚未被任何人指定为紧急联系人。" @@ -5123,10 +5403,10 @@ "message": "通过禁用个人密码库选项,要求成员将项目保存到组织。" }, "personalOwnershipExemption": { - "message": "组织的所有者和管理员豁免此策略。" + "message": "组织的所有者和管理员不受此策略的约束。" }, "personalOwnershipSubmitError": { - "message": "由于某个企业策略,您不能将项目保存到您的个人密码库。将所有权选项更改为组织,并从可用的集合中选择。" + "message": "由于某个企业策略,您不能将项目保存到您的个人密码库。请将所有权选项更改为组织,然后选择可用的集合。" }, "disableSend": { "message": "禁用 Send" @@ -5136,7 +5416,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "disableSendExemption": { - "message": "可以管理组织策略的组织成员豁免此策略。" + "message": "可以管理组织策略的组织成员不受此策略的约束。" }, "sendDisabled": { "message": "Send 已禁用", @@ -5155,17 +5435,10 @@ "description": "'Sends' is a plural noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendOptionsExemption": { - "message": "可以管理组织策略的组织成员豁免此策略。" + "message": "可以管理组织策略的组织成员不受此策略的约束。" }, "disableHideEmail": { - "message": "在创建或编辑 Send 时,始终向收件人显示成员的电子邮箱地址。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendOptionsPolicyInEffect": { - "message": "以下组织策略目前正起作用:" - }, - "sendDisableHideEmailInEffect": { - "message": "创建或编辑 Send 时,不允许用户对收件人隐藏他们的电子邮箱地址。", + "message": "创建或编辑 Send 时,始终向接收者显示成员的电子邮箱地址。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "modifiedPolicyId": { @@ -5250,7 +5523,7 @@ "message": "管理账户恢复" }, "disableRequiredError": { - "message": "您必须先手动切换 $POLICYNAME$ 策略,然后才能停用此策略。", + "message": "您必须先手动切换 $POLICYNAME$ 策略,然后才能关闭此策略。", "placeholders": { "policyName": { "content": "$1", @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "移除组织用户的个人所有权" }, - "textHiddenByDefault": { - "message": "访问此 Send 时,默认隐藏文本内容", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "用于描述此 Send 的友好名称。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "您想要发送的文本。" - }, - "sendFileDesc": { - "message": "您想要发送的文件。" - }, - "copySendLinkOnSave": { - "message": "保存时复制链接到剪贴板以便分享此 Send。" - }, - "sendLinkLabel": { - "message": "Send 链接", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5326,7 +5578,7 @@ "message": "集中管理机密。" }, "centralizeSecretsManagementDescription": { - "message": "在一个位置安全地存储和管理机密,以防止机密扩散到您的组织。" + "message": "在一个位置安全地存储和管理机密,防止机密在组织内扩散。" }, "preventSecretLeaks": { "message": "防止机密遭泄漏。" @@ -5353,7 +5605,7 @@ "message": "发送请求" }, "addANote": { - "message": "添加备注" + "message": "添加笔记" }, "bitwardenSecretsManager": { "message": "Bitwarden 机密管理器" @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "保存您的删除和过期日期时出错。" }, + "hideYourEmail": { + "message": "对查看者隐藏您的电子邮箱地址。" + }, "webAuthnFallbackMsg": { "message": "要验证您的 2FA,请点击下面的按钮。" }, "webAuthnAuthenticate": { "message": "验证 WebAuthn" }, + "readSecurityKey": { + "message": "读取安全密钥" + }, + "awaitingSecurityKeyInteraction": { + "message": "等待安全密钥交互..." + }, "webAuthnNotSupported": { "message": "此浏览器不支持 WebAuthn。" }, @@ -5542,10 +5803,10 @@ "message": "基于加密方式,当主密码或受信任设备被遗忘或丢失时恢复账户。" }, "accountRecoveryPolicyWarning": { - "message": "对于具有主密码的现有账户,需要成员自行注册后,管理员才可以恢复他们的账户。对于新的成员,自动注册后将打开账户恢复功能。" + "message": "对于具有主密码的现有账户,需要成员自行注册后,管理员才可以恢复他们的账户。对于新的成员,自动注册后将开启账户恢复功能。" }, "accountRecoverySingleOrgRequirementDesc": { - "message": "激活此策略前,需先开启「单一组织」企业策略。" + "message": "必须先开启「单一组织」企业策略,然后才能激活此策略。" }, "resetPasswordPolicyAutoEnroll": { "message": "自动注册" @@ -5554,7 +5815,7 @@ "message": "为新用户启用自动注册" }, "resetPasswordAutoEnrollInviteWarning": { - "message": "此组织有一个企业策略,将自动为你注册密码重置。注册后将允许组织管理员更改您的主密码。" + "message": "此组织有一个企业策略,将自动为您注册密码重置。注册后将允许组织管理员更改您的主密码。" }, "resetPasswordOrgKeysError": { "message": "组织密钥响应为空" @@ -5655,6 +5916,20 @@ "error": { "message": "错误" }, + "decryptionError": { + "message": "解密错误" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden 无法解密下列密码库项目。" + }, + "contactCSToAvoidDataLossPart1": { + "message": "联系客户成功团队", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "以避免额外的数据丢失。", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "授予「管理账户恢复」权限时,必须同时授予「管理用户」权限" }, @@ -5702,7 +5977,7 @@ "message": "您已被邀请加入上面列出的提供商。要接受邀请,您需要登录或创建一个新的 Bitwarden 账户。" }, "providerInviteAcceptFailed": { - "message": "无法接受邀请。请联系提供商管理员发送新的邀请。" + "message": "无法接受邀请。请向提供商管理员请求发送新的邀请。" }, "providerInviteAcceptedDesc": { "message": "管理员确认您的成员资格后,您就可以访问此提供商了。届时我们会向您发送一封电子邮件。" @@ -5714,10 +5989,10 @@ "message": "提供商" }, "newClientOrganization": { - "message": "新建客户组织" + "message": "新增客户组织" }, "newClientOrganizationDesc": { - "message": "创建一个新的客户组织,该组织将作为提供商与你关联。您将可以访问和管理这个组织。" + "message": "创建一个新的客户组织,该组织将作为提供商与您关联。您将可以访问和管理这个组织。" }, "newClient": { "message": "新增客户" @@ -5793,10 +6068,10 @@ "message": "更新主密码" }, "updateMasterPasswordWarning": { - "message": "您的主密码最近被您组织的管理员更改过。要访问此密码库,必须立即更新您的主密码。继续操作将使您退出当前会话,要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" + "message": "您的主密码最近被您组织的管理员更改过。要访问密码库,必须立即更新您的主密码。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" }, "masterPasswordInvalidWarning": { - "message": "您的主密码不符合此组织的策略要求。要加入此组织,必须立即更新您的主密码。继续操作将使您退出当前会话,要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" + "message": "您的主密码不符合此组织的策略要求。要加入此组织,必须立即更新您的主密码。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" }, "updateWeakMasterPasswordWarning": { "message": "您的主密码不符合某一项或多项组织策略要求。要访问密码库,必须立即更新您的主密码。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" @@ -5805,7 +6080,7 @@ "message": "为允许的应用程序自动登录用户" }, "automaticAppLoginDesc": { - "message": "从您配置的身份提供程序启动的 App 的登录表单将自动填写并提交。" + "message": "从您配置的身份提供程序启动的 App 的登录表单将自动填充并提交。" }, "automaticAppLoginIdpHostLabel": { "message": "身份提供程序主机" @@ -5914,7 +6189,7 @@ "message": "为所有现有成员和新成员激活浏览器扩展上的页面加载时的自动填充设置。" }, "experimentalFeature": { - "message": "不完整或不信任的网站可以使用页面加载时自动填充。" + "message": "被入侵或不受信任的网站可以利用页面加载时的自动填充功能。" }, "learnMoreAboutAutofill": { "message": "进一步了解自动填充" @@ -5998,7 +6273,7 @@ "message": "最小入站签名算法" }, "spWantAssertionsSigned": { - "message": "要求使用签名的断言" + "message": "期望已签名的断言" }, "spValidateCertificates": { "message": "验证证书" @@ -6034,7 +6309,7 @@ "message": "允许出站注销请求" }, "idpSignAuthenticationRequests": { - "message": "签名身份验证请求" + "message": "签署身份验证请求" }, "ssoSettingsSaved": { "message": "单点登录配置已保存" @@ -6061,7 +6336,7 @@ "message": "链接已失效。请让赞助方重新发送邀请。" }, "reclaimedFreePlan": { - "message": "重新认领免费计划" + "message": "收回了免费计划" }, "redeem": { "message": "兑换" @@ -6076,7 +6351,7 @@ "message": "输入您的个人电子邮箱以兑换 Bitwarden 家庭" }, "sponsoredFamiliesLeaveCopy": { - "message": "如果您移除邀请或邀请被赞助组织移除,您的家庭赞助将在下一个续费日到期。" + "message": "如果您移除邀请或您被从赞助组织中移除,您的家庭赞助将在下一个续费日到期。" }, "acceptBitwardenFamiliesHelp": { "message": "接受现有组织的邀请或创建一个新的家庭组织。" @@ -6133,7 +6408,7 @@ "message": "立即兑换" }, "recipient": { - "message": "收件人" + "message": "接收者" }, "removeSponsorship": { "message": "移除赞助" @@ -6234,17 +6509,17 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Connect login with SSO to your self-hosted decryption key server. Using this option, members won’t need to use their master passwords to decrypt vault data. The require SSO authentication and single organization policies are required to set up Key Connector decryption. Contact Bitwarden Support for set up assistance.'" }, "memberDecryptionKeyConnectorDescEnd": { - "message": "以用于设置 Key Connector 解密。请联系 Bitwarden 支持获取协助。", + "message": "以用于设置 Key Connector 解密。请联系 Bitwarden 支持寻求帮助。", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Connect login with SSO to your self-hosted decryption key server. Using this option, members won’t need to use their master passwords to decrypt vault data. The require SSO authentication and single organization policies are required to set up Key Connector decryption. Contact Bitwarden Support for set up assistance.'" }, "keyConnectorPolicyRestriction": { "message": "「SSO 登录和 Key Connector 解密」已启用。此策略仅适用于所有者和管理员。" }, "enabledSso": { - "message": "启用了 SSO" + "message": "SSO 已开启" }, "disabledSso": { - "message": "停用了 SSO" + "message": "SSO 已关闭" }, "enabledKeyConnector": { "message": "Key Connector 已启用" @@ -6516,15 +6791,6 @@ "message": "生成器", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "您想要生成什么?" - }, - "passwordType": { - "message": "密码类型" - }, - "regenerateUsername": { - "message": "重新生成用户名" - }, "generateUsername": { "message": "生成用户名" }, @@ -6546,7 +6812,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " 使用 $RECOMMENDED$ 或更多个字符生成强大的密码。", + "message": " 使用 $RECOMMENDED$ 个或更多字符生成强大的密码。", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -6556,7 +6822,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " 使用 $RECOMMENDED$ 或更多个单词生成强大的密码短语。", + "message": " 使用 $RECOMMENDED$ 个或更多单词生成强大的密码短语。", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "用户名类型" - }, "plusAddressedEmail": { "message": "附加地址电子邮箱", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "使用您的域名配置的 Catch-all 收件箱。" }, + "useThisEmail": { + "message": "使用此电子邮箱" + }, "random": { "message": "随机", "description": "Generates domain-based username using random letters" @@ -6612,7 +6878,7 @@ "message": "服务" }, "unknownCipher": { - "message": "未知项目,你可能需要申请权限才能访问这个项目。" + "message": "未知项目,您可能需要申请权限才能访问这个项目。" }, "cannotSponsorSelf": { "message": "您不能为活动账户兑换。请输入其他电子邮箱。" @@ -6661,7 +6927,7 @@ } }, "billingContactProviderForAssistance": { - "message": "请联系他们以获得进一步的协助", + "message": "请联系他们以寻求进一步的帮助", "description": "This text is displayed if an organization's billing is managed by a Provider. It tells the user to contact the Provider for assistance." }, "forwardedEmail": { @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ 拒绝了您的请求。请联系您的服务提供商寻求帮助。", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ 拒绝了您的请求:$ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "无法获取 $SERVICENAME$ 电子邮箱账户 ID。", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,20 +7074,17 @@ "message": "主机名", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API 访问令牌" - }, "deviceVerification": { "message": "设备验证" }, "enableDeviceVerification": { - "message": "启用设备验证" + "message": "开启设备验证" }, "deviceVerificationDesc": { "message": "登录未识别的设备时,验证码会发送到您的电子邮箱地址" }, "updatedDeviceVerification": { - "message": "已更新设备验证" + "message": "更新了设备验证" }, "areYouSureYouWantToEnableDeviceVerificationTheVerificationCodeEmailsWillArriveAtX": { "message": "确定要开启设备验证吗?验证码电子邮件将发送到:$EMAIL$", @@ -6876,7 +7163,7 @@ "message": "输入的不是电子邮箱地址。" }, "inputMinLength": { - "message": "至少输入 $COUNT$ 个字符。", + "message": "输入长度不能低于 $COUNT$ 个字符。", "placeholders": { "count": { "content": "$1", @@ -6954,7 +7241,7 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "与 Duo 服务连接时出错。请使用不同的两步登录方式或联系 Duo 获取协助。" + "message": "与 Duo 服务连接时出错。请使用其他两步登录方式或联系 Duo 寻求帮助。" }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "启动 Duo 然后按照步骤完成登录。" @@ -6962,6 +7249,12 @@ "duoRequiredByOrgForAccount": { "message": "您的账户要求使用 Duo 两步登录。" }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "您的账户要求使用 Duo 两步登录。请按照以下步骤完成登录。" + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "按照以下步骤完成登录。" + }, "launchDuo": { "message": "启动 Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "用户数量" }, - "loggingInAs": { - "message": "正登录为" - }, - "notYou": { - "message": "不是您吗?" - }, "pickAnAvatarColor": { "message": "选择头像颜色" }, @@ -7176,7 +7463,7 @@ "description": "Message to encourage the user to start creating service accounts." }, "serviceAccountsNoItemsTitle": { - "message": "还没有要显示的内容", + "message": "还没有可显示的内容", "description": "Title to indicate that there are no service accounts to display." }, "searchSecrets": { @@ -7460,7 +7747,7 @@ "message": "更新" }, "plusNMore": { - "message": "+ $QUANTITY$ 及更多", + "message": "还有 $QUANTITY$ 个", "placeholders": { "quantity": { "content": "$1", @@ -7498,18 +7785,6 @@ "noCollection": { "message": "没有集合" }, - "canView": { - "message": "可以查看" - }, - "canViewExceptPass": { - "message": "除密码外,可以查看" - }, - "canEdit": { - "message": "可以编辑" - }, - "canEditExceptPass": { - "message": "除密码外,可以编辑" - }, "noCollectionsAdded": { "message": "未添加任何集合" }, @@ -7529,7 +7804,7 @@ "message": "新增域名" }, "noDomains": { - "message": "还没有域名" + "message": "没有域名" }, "noDomainsSubText": { "message": "连接域名允许成员在使用 SSO 登录时跳过 SSO 标识符字段。" @@ -7556,7 +7831,7 @@ "message": "自动域名验证" }, "automaticDomainVerificationProcess": { - "message": "Bitwarden 将尝试在 72 小时内验证此域名 3 次。如果此域名无法验证,请检查您的主机中的 DNS 记录并手动验证。如果此域名未通过验证,7 天内将从您的组织中移除。" + "message": "Bitwarden 将在最初的 72 小时内尝试验证域名 3 次。如果此域名无法验证,请检查您主机中的 DNS 记录并手动验证。如果此域名在 7 天内未验证,它将被从您的组织中移除。" }, "invalidDomainNameMessage": { "message": "输入的格式无效。格式:mydomain.com。子域名需要单独的条目进行验证。" @@ -7589,7 +7864,7 @@ } }, "domainNotVerified": { - "message": "$DOMAIN$ 未通过验证。请检查您的 DNS 记录。", + "message": "$DOMAIN$ 无法验证。请检查您的 DNS 记录。", "placeholders": { "DOMAIN": { "content": "$1", @@ -7646,7 +7921,7 @@ } }, "domainNotVerifiedEvent": { - "message": "$DOMAIN$ 未通过验证", + "message": "$DOMAIN$ 无法验证", "placeholders": { "DOMAIN": { "content": "$1", @@ -7754,7 +8029,7 @@ } }, "teamsStarterPlanInvLimitReachedNoManageBilling": { - "message": "团队入门版计划最多拥有 $SEATCOUNT$ 位成员。要升级您的计划及邀请更多成员,请联系您的组织所有者。", + "message": "团队入门版计划最多拥有 $SEATCOUNT$ 位成员。要升级您的计划并邀请更多成员,请联系您的组织所有者。", "placeholders": { "seatcount": { "content": "$1", @@ -7763,7 +8038,7 @@ } }, "freeOrgMaxCollectionReachedManageBilling": { - "message": "免费组织最多拥有 $COLLECTIONCOUNT$ 个集合。升级到付费计划以添加更多集合。", + "message": "免费组织最多拥有 $COLLECTIONCOUNT$ 个集合。要添加更多集合,请升级到付费计划。", "placeholders": { "COLLECTIONCOUNT": { "content": "$1", @@ -7838,10 +8113,10 @@ "message": "同步许可证" }, "licenseSyncSuccess": { - "message": "成功同步许可证" + "message": "成功同步了许可证" }, "licenseUploadSuccess": { - "message": "成功上传许可证" + "message": "成功上传了许可证" }, "lastLicenseSync": { "message": "最后一次许可证同步" @@ -8049,7 +8324,7 @@ "message": "主密码弱且曾经暴露" }, "weakAndBreachedMasterPasswordDesc": { - "message": "识别到弱密码且其出现在数据泄露中。请使用一个强且唯一的密码以保护你的账户。确定要使用这个密码吗?" + "message": "识别到弱密码且其出现在数据泄露中。请使用一个强且唯一的密码以保护您的账户。确定要使用这个密码吗?" }, "characterMinimum": { "message": "至少 $LENGTH$ 个字符", @@ -8077,7 +8352,7 @@ "message": "忽略" }, "notAvailableForFreeOrganization": { - "message": "此功能不适用于免费组织。请联系您的组织所有者寻求升级。" + "message": "此功能不适用于免费组织。请联系您的组织所有者升级。" }, "smProjectSecretsNoItemsNoAccess": { "message": "请联系您的组织的管理员来管理此工程的机密。", @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "请求管理员批准" }, - "approveWithMasterPassword": { - "message": "使用主密码批准" - }, "trustedDeviceEncryption": { "message": "受信任设备加密" }, "trustedDevices": { "message": "受信任设备" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "验证后,成员将使用存储在他们设备上的密钥解密密码库数据。使用此选项后,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "使用 SSO 登录时,成员无需主密码。主密码将被存储在设备上的加密密钥所取代,从而使该设备成为受信任设备。成员创建账户并登录的第一个设备将受到信任。新设备需要由现有受信任设备或管理员批准。", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "单一组织", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "策略,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "要求 SSO", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "策略以及具有自动注册的", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "账户恢复管理", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "策略将被开启。", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "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.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "您的组织权限已更新,要求您设置主密码。", @@ -8170,7 +8442,7 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "$TOTAL$ 不足", + "message": "满分 $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -8236,8 +8508,20 @@ "approveRequest": { "message": "批准请求" }, + "deviceApproved": { + "message": "设备已批准" + }, + "deviceRemoved": { + "message": "设备已移除" + }, + "removeDevice": { + "message": "移除设备" + }, + "removeDeviceConfirmation": { + "message": "确定要移除此设备吗?" + }, "noDeviceRequests": { - "message": "无设备请求" + "message": "没有设备请求" }, "noDeviceRequestsDesc": { "message": "成员的设备批准请求将显示在这里" @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "您的请求已发送给您的管理员。" }, - "youWillBeNotifiedOnceApproved": { - "message": "批准后,您将收到通知。" - }, "troubleLoggingIn": { "message": "登录遇到问题吗?" }, @@ -8440,10 +8721,13 @@ "message": "管理组织的集合行为" }, "limitCollectionCreationDesc": { - "message": "限制为仅所有者和管理员可以创建集合" + "message": "限制为所有者和管理员可以创建集合" }, "limitCollectionDeletionDesc": { - "message": "限制为仅所有者和管理员可以删除集合" + "message": "限制为所有者和管理员可以删除集合" + }, + "limitItemDeletionDesc": { + "message": "限制为拥有「可以管理」权限的成员可以删除项目" }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "所有者和管理员可以管理所有集合和项目" @@ -8494,11 +8778,8 @@ "message": "自托管服务器 URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "别名域" - }, "alreadyHaveAccount": { - "message": "已经拥有账户了吗?" + "message": "已经有账户了吗?" }, "toggleSideNavigation": { "message": "切换侧边导航" @@ -8507,7 +8788,7 @@ "message": "跳转到内容" }, "managePermissionRequired": { - "message": "必须至少有一个成员或群组拥有「可以管理」的权限。" + "message": "必须至少有一个成员或群组拥有「可以管理」权限。" }, "typePasskey": { "message": "通行密钥" @@ -8547,10 +8828,10 @@ "message": "已达到席位限制" }, "contactYourProvider": { - "message": "请联系您的提供商购买更多席位。" + "message": "请联系您的提供商购买附加席位。" }, "seatLimitReachedContactYourProvider": { - "message": "已达到席位限制。请联系您的提供商购买更多席位。" + "message": "已达到席位限制。请联系您的提供商购买附加席位。" }, "collectionAccessRestricted": { "message": "集合访问权限受限" @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "您没有管理此集合的权限。" }, - "grantAddAccessCollectionWarningTitle": { - "message": "缺少「可以管理」权限" + "grantManageCollectionWarningTitle": { + "message": "缺少「管理集合」权限" }, - "grantAddAccessCollectionWarning": { - "message": "授予「可以管理」权限以允许完整的集合管理,包括删除集合。" + "grantManageCollectionWarning": { + "message": "授予「管理集合」权限以允许完整的集合管理,包括删除集合。" }, "grantCollectionAccess": { "message": "授予群组或成员对此集合的访问权限。" @@ -8591,7 +8872,7 @@ } }, "addAPaymentMethod": { - "message": "请添加一个付款方式。", + "message": "请添加一个付款方式", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { @@ -8615,7 +8896,7 @@ "description": "Used as a form field label for a select input on the offboarding survey." }, "anyOtherFeedback": { - "message": "您还有其他反馈信息要分享吗?", + "message": "您还有其他反馈意见要分享吗?", "description": "Used as a form field label for a textarea input on the offboarding survey." }, "missingFeatures": { @@ -8642,7 +8923,7 @@ "message": "免费 1 年" }, "newWebApp": { - "message": "欢迎使用全新改进的网页 App。了解更多有关变更的信息。" + "message": "欢迎使用全新改进的网页 App。进一步了解有关变更的信息。" }, "releaseBlog": { "message": "阅读发行博客" @@ -8736,7 +9017,7 @@ "message": "已购买附加席位" }, "assignedSeatCannotUpdate": { - "message": "无法更新已分配的席位。请联系您的组织所有者获取协助。" + "message": "无法更新已分配的席位。请联系您的组织所有者寻求帮助。" }, "subscriptionUpdateFailed": { "message": "订阅更新失败" @@ -8794,7 +9075,7 @@ "description": "The date header used when a subscription is cancelled." }, "machineAccountsCannotCreate": { - "message": "无法在已停用的组织中创建机器账户。请联系您的组织所有者获取协助。" + "message": "无法在已停用的组织中创建机器账户。请联系您的组织所有者寻求帮助。" }, "machineAccount": { "message": "机器账户", @@ -8813,7 +9094,7 @@ "description": "Message to encourage the user to start creating machine accounts." }, "machineAccountsNoItemsTitle": { - "message": "暂无要显示的内容", + "message": "还没有可显示的内容", "description": "Title to indicate that there are no machine accounts to display." }, "deleteMachineAccounts": { @@ -8975,7 +9256,7 @@ } }, "deleteProviderWarningDescription": { - "message": "删除 $ID$ 之前,您必须取消链接所有的客户。", + "message": "您必须取消链接所有的客户,然后才能删除 $ID$。", "placeholders": { "id": { "content": "$1", @@ -9036,7 +9317,7 @@ "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, "scimIntegrationDescEnd": { - "message": "(跨域身份管理系统)(使用您的身份提供程序的实施指南)以自动向 Bitwarden 配置用户和群组。", + "message": "(跨域身份管理系统)以自动向 Bitwarden 配置用户和群组(使用您的身份提供程序的实施指南)。", "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, "bwdc": { @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "使用适合您平台的实施指南为 Bitwarden 配置设备管理。" }, + "deviceIdMissing": { + "message": "缺少设备 ID" + }, + "deviceTypeMissing": { + "message": "缺少设备类型" + }, + "deviceCreationDateMissing": { + "message": "缺少设备创建日期" + }, + "desktopRequired": { + "message": "需要桌面端" + }, + "reopenLinkOnDesktop": { + "message": "请在桌面端从您的电子邮件中重新打开此链接。" + }, "integrationCardTooltip": { "message": "打开 $INTEGRATION$ 实施指南。", "placeholders": { @@ -9121,7 +9417,10 @@ "message": "35% 折扣" }, "monthPerMember": { - "message": "每成员 /月" + "message": "月 /成员" + }, + "monthPerMemberBilledAnnually": { + "message": "月 /成员(按年计费)" }, "seats": { "message": "席位" @@ -9303,7 +9602,7 @@ "message": "通过审计成员访问权限来识别安全风险" }, "onlyAvailableForEnterpriseOrganization": { - "message": "通过升级为企业计划,快速查看整个组织的成员访问权限。" + "message": "通过升级为企业版计划,快速查看整个组织的成员访问权限。" }, "date": { "message": "日期" @@ -9318,7 +9617,7 @@ "message": "确保成员具有对合适的凭据的访问权限,以及他们的账户是安全的。使用此报告获取包含会员访问权限和账户配置的 CSV 文件 。" }, "memberAccessReportPageDesc": { - "message": "审计组织成员在各个群组、集合和集合项目之间的访问权限。CSV 导出文件提供了每位成员的详细信息,包括集合权限和账户配置的相关信息。" + "message": "审计组织成员在群组、集合和集合项目中的访问权限。CSV 导出文件提供了每位成员的详细信息,包括集合权限和账户配置信息。" }, "memberAccessReportNoCollection": { "message": "(无集合)" @@ -9547,9 +9846,15 @@ "learnMoreAboutApi": { "message": "进一步了解 Bitwarden API" }, + "fileSend": { + "message": "文件 Send" + }, "fileSends": { "message": "文件 Send" }, + "textSend": { + "message": "文本 Send" + }, "textSends": { "message": "文本 Send" }, @@ -9596,7 +9901,7 @@ } }, "familiesPlanInvLimitReachedNoManageBilling": { - "message": "家庭组织最多拥有 $SEATCOUNT$ 位成员。要升级,请联系您的组织所有者。", + "message": "家庭组织最多拥有 $SEATCOUNT$ 位成员。请联系您的组织所有者升级。", "placeholders": { "seatcount": { "content": "$1", @@ -9643,6 +9948,15 @@ "sshKeyAlgorithm": { "message": "密钥算法" }, + "sshPrivateKey": { + "message": "私钥" + }, + "sshPublicKey": { + "message": "公钥" + }, + "sshFingerprint": { + "message": "指纹" + }, "sshKeyFingerprint": { "message": "指纹" }, @@ -9707,7 +10021,7 @@ "message": "当前" }, "secretsManagerSubscriptionInfo": { - "message": "您的机密管理器订阅将根据选定的计划升级" + "message": "您的机密管理器订阅将基于选择的计划升级" }, "bitwardenPasswordManager": { "message": "Bitwarden 密码管理器" @@ -9774,10 +10088,6 @@ "message": "包含特殊字符", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { "message": "添加附件" }, @@ -9785,7 +10095,7 @@ "message": "最大文件大小为 500 MB" }, "permanentlyDeleteAttachmentConfirmation": { - "message": "您确定要永久删除此附件吗?" + "message": "确定要永久删除此附件吗?" }, "manageSubscriptionFromThe": { "message": "管理订阅,通过", @@ -9798,10 +10108,10 @@ "message": "自托管" }, "claim-domain-single-org-warning": { - "message": "声明域名将启用单一组织策略。" + "message": "声明域名将开启单一组织策略。" }, "single-org-revoked-user-warning": { - "message": "不符合要求的成员将被撤销。管理员可以在他们离开所有其他组织后恢复其成员资格。" + "message": "不符合要求的成员将被撤销。管理员可以在他们退出所有其他组织后恢复其成员资格。" }, "deleteOrganizationUser": { "message": "删除 $NAME$", @@ -9867,7 +10177,7 @@ } }, "suspendedUserOrgMessage": { - "message": "联系您的组织所有者获取协助。" + "message": "联系您的组织所有者寻求帮助。" }, "suspendedOwnerOrgMessage": { "message": "要重新访问您的组织,请添加一个付款方式。" @@ -9882,20 +10192,29 @@ "message": "删除成功" }, "freeFamiliesSponsorship": { - "message": "移除免费的 Bitwarden 家庭赞助" + "message": "禁用免费 Bitwarden 家庭赞助" }, "freeFamiliesSponsorshipPolicyDesc": { "message": "不允许成员通过此组织兑换家庭计划。" }, "verifyBankAccountWithStatementDescriptorWarning": { - "message": "使用银行账户付款仅对美国用户开放。您将被要求验证您的银行账户。我们将在 1-2 个工作日内进行一笔小额转账,请在组织的计费页面输入该转账的语句描述符代码以验证银行账户。验证银行账户失败将会错过支付,您的订阅将失效。" + "message": "使用银行账户付款仅对美国用户开放。您将被要求验证您的银行账户。我们将在 1-2 个工作日内进行一笔小额转账,请在组织的计费页面输入该转账的对账单描述符代码以验证银行账户。验证银行账户失败将会错过支付,您的订阅将失效。" }, "verifyBankAccountWithStatementDescriptorInstructions": { - "message": "我们已向您的银行账户存入了一笔小额转账(可能需要 1-2 个工作日到账)。请输入转账说明中以 \"SM\" 开头的六位数代码。验证银行账户失败将会错过支付,您的订阅将失效。" + "message": "我们已向您的银行账户存入了一笔小额转账(可能需要 1-2 个工作日到账)。请输入转账描述中以「SM」开头的六位数代码。验证银行账户失败将会错过支付,您的订阅将失效。" }, "descriptorCode": { "message": "描述符代码" }, + "cannotRemoveViewOnlyCollections": { + "message": "您无法删除仅具有「查看」权限的集合:$COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "重要通知" }, @@ -9935,8 +10254,17 @@ "removeMembers": { "message": "移除成员" }, + "devices": { + "message": "设备" + }, + "deviceListDescription": { + "message": "您的账户在以下设备上登录过。如果您无法识别某个设备,请立即将其移除。" + }, + "deviceListDescriptionTemp": { + "message": "您的账户在以下设备上登录过。" + }, "claimedDomains": { - "message": "已声明的域名" + "message": "声明域名" }, "claimDomain": { "message": "声明域名" @@ -9945,16 +10273,16 @@ "message": "重新声明域名" }, "claimDomainNameInputHint": { - "message": "示例:mydomain.com。子域名需要单独的条目才能声明。" + "message": "示例:mydomain.com。子域名需要单独的条目进行声明。" }, "automaticClaimedDomains": { "message": "自动声明域名" }, "automaticDomainClaimProcess": { - "message": "Bitwarden 将在前 72 小时内尝试声明域名 3 次。如果无法声明此域名,请检查主机中的 DNS 记录并手动声明。如果未声明,该域名将在 7 天内从您的组织中移除。" + "message": "Bitwarden 将在最初的 72 小时内尝试声明域名 3 次。如果此域名无法声明,请检查您主机中的 DNS 记录并手动声明。如果此域名在 7 天内未声明,它将被从您的组织中移除。" }, "domainNotClaimed": { - "message": "$DOMAIN$ 未声明。请检查 DNS 记录。", + "message": "$DOMAIN$ 无法声明。请检查您的 DNS 记录。", "placeholders": { "DOMAIN": { "content": "$1", @@ -9966,13 +10294,13 @@ "message": "已声明" }, "domainStatusUnderVerification": { - "message": "正在验证" + "message": "验证中" }, "claimedDomainsDesc": { "message": "声明一个域名,以拥有电子邮箱地址与该域名匹配的所有成员账户。成员登录时将可以跳过 SSO 标识符。管理员也可以删除成员账户。" }, "invalidDomainNameClaimMessage": { - "message": "输入的格式无效。格式:mydomain.com。子域名需要单独的条目才能声明。" + "message": "输入的格式无效。格式:mydomain.com。子域名需要单独的条目进行声明。" }, "domainClaimedEvent": { "message": "$DOMAIN$ 已声明", @@ -9993,7 +10321,7 @@ } }, "updatedRevokeSponsorshipConfirmationForSentSponsorship": { - "message": "如果您移除 $EMAIL$,将无法兑换此家庭计划赞助,确定要继续吗?", + "message": "如果您移除 $EMAIL$,将无法兑换此家庭计划赞助。确定要继续吗?", "placeholders": { "email": { "content": "$1", @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "组织名称不能超过 50 个字符。" }, - "resellerRenewalWarning": { - "message": "您的订阅即将续订。为确保服务不中断,请在 $RENEWAL_DATE$ 之前联系 $RESELLER$ 确认您的续订。", + "sshKeyWrongPassword": { + "message": "您输入的密码不正确。" + }, + "importSshKey": { + "message": "导入" + }, + "confirmSshKeyPassword": { + "message": "确认密码" + }, + "enterSshKeyPasswordDesc": { + "message": "输入 SSH 密钥的密码。" + }, + "enterSshKeyPassword": { + "message": "输入密码" + }, + "invalidSshKey": { + "message": "此 SSH 密钥无效" + }, + "sshKeyTypeUnsupported": { + "message": "不支持此 SSH 密钥类型" + }, + "importSshKeyFromClipboard": { + "message": "从剪贴板导入密钥" + }, + "sshKeyImported": { + "message": "SSH 密钥导入成功" + }, + "copySSHPrivateKey": { + "message": "复制私钥" + }, + "openingExtension": { + "message": "正在打开 Bitwarden 浏览器扩展" + }, + "somethingWentWrong": { + "message": "出错了..." + }, + "openingExtensionError": { + "message": "我们无法打开 Bitwarden 浏览器扩展。请点击按钮立即打开。" + }, + "openExtension": { + "message": "打开扩展" + }, + "doNotHaveExtension": { + "message": "没有 Bitwarden 浏览器扩展吗?" + }, + "installExtension": { + "message": "安装扩展" + }, + "openedExtension": { + "message": "已打开浏览器扩展" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "成功打开 Bitwarden 浏览器扩展。您现在可以审查存在风险的密码了。" + }, + "openExtensionManuallyPart1": { + "message": "我们无法打开 Bitwarden 浏览器扩展。请从工具栏中点击 Bitwarden 图标", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "来打开它。", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "您的订阅即将续期。为确保服务不会中断,请在 $RENEWAL_DATE$ 之前联系 $RESELLER$ 确认您的续订。", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "您的订阅账单已于 $ISSUED_DATE$ 开具。为确保服务不中断,请在 $DUE_DATE$ 之前联系 $RESELLER$ 确认您的续订。", + "resellerOpenInvoiceWarningMgs": { + "message": "您的订阅账单已于 $ISSUED_DATE$ 开具。为确保服务不会中断,请在 $DUE_DATE$ 之前联系 $RESELLER$ 确认您的续订。", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "您的订阅账单尚未支付。为确保服务不中断,请在 $GRACE_PERIOD_END$ 之前联系 $RESELLER$ 确认您的续订。", + "resellerPastDueWarningMsg": { + "message": "您的订阅账单尚未支付。为确保服务不会中断,请在 $GRACE_PERIOD_END$ 之前联系 $RESELLER$ 确认您的续订。", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "组织订阅已重启" + }, + "restartSubscription": { + "message": "重启您的订阅" + }, + "suspendedManagedOrgMessage": { + "message": "联系 $PROVIDER$ 寻求帮助。", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "管理员现在可以删除已声明域名下的成员账户。" + }, + "deleteManagedUserWarningDesc": { + "message": "此操作将删除成员账户及其密码库中的所有项目。其取代了之前的「移除」操作。" + }, + "deleteManagedUserWarning": { + "message": "「删除」是一个新的操作!" + }, + "seatsRemaining": { + "message": "该组织已分配 $TOTAL$ 个席位,您还剩余 $REMAINING$ 个席位。请联系提供商管理您的订阅。", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "现有组织" + }, + "selectOrganizationProviderPortal": { + "message": "选择一个要添加到您的提供商门户的组织。" + }, + "noOrganizations": { + "message": "没有可列出的组织" + }, + "yourProviderSubscriptionCredit": { + "message": "您的提供商订阅将获得此组织订阅剩余时间内的信用额度。" + }, + "doYouWantToAddThisOrg": { + "message": "您想将该组织添加到 $PROVIDER$ 吗?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "添加了现有组织" + }, + "assignedExceedsAvailable": { + "message": "分配的席位超过可用席位。" + }, + "changeAtRiskPassword": { + "message": "更改有风险的密码" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "禁用 PIN 码解锁" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "不允许成员使用 PIN 码解锁他们的账户。" + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ 计划无法访问真实的事件日志", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "升级到团队版或企业版计划,即可获得对组织事件日志的完整访问权限。" + }, + "upgradeEventLogTitle": { + "message": "升级以访问真实的事件日志数据" + }, + "upgradeEventLogMessage": { + "message": "这些事件仅为示例,并不反映您 Bitwarden 组织内的真实事件。" + }, + "cannotCreateCollection": { + "message": "免费组织最多拥有 2 个集合。要添加更多集合,请升级到付费计划。" } } diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index ae60ea3cdb7..beca8f74485 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -1,10 +1,13 @@ { "allApplications": { - "message": "All applications" + "message": "所有應用程式" }, "criticalApplications": { "message": "重要應用程式" }, + "noCriticalAppsAtRisk": { + "message": "No critical applications at risk" + }, "accessIntelligence": { "message": "存取資訊" }, @@ -108,11 +111,44 @@ "message": "全部密碼" }, "searchApps": { - "message": "Search applications" + "message": "搜尋應用程式" }, "atRiskMembers": { "message": "具有風險的成員" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "具有風險的應用程式 ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -122,6 +158,12 @@ "totalApplications": { "message": "Total applications" }, + "unmarkAsCriticalApp": { + "message": "Unmark as critical app" + }, + "criticalApplicationSuccessfullyUnmarked": { + "message": "Critical application successfully unmarked" + }, "whatTypeOfItem": { "message": "這是什麼類型的項目?" }, @@ -159,6 +201,9 @@ "notes": { "message": "備註" }, + "privateNote": { + "message": "Private note" + }, "note": { "message": "備註" }, @@ -267,7 +312,7 @@ "message": "安全碼 (CVV)" }, "securityCodeSlashCVV": { - "message": "Security code / CVV" + "message": "安全碼 / CVV" }, "identityName": { "message": "身分名稱" @@ -383,6 +428,9 @@ "dragToSort": { "message": "透過拖曳來排序" }, + "dragToReorder": { + "message": "Drag to reorder" + }, "cfTypeText": { "message": "文字型" }, @@ -425,6 +473,31 @@ "editFolder": { "message": "編輯資料夾" }, + "editWithName": { + "message": "Edit $ITEM$: $NAME$", + "placeholders": { + "item": { + "content": "$1", + "example": "login" + }, + "name": { + "content": "$2", + "example": "Social" + } + } + }, + "newFolder": { + "message": "New folder" + }, + "folderName": { + "message": "Folder name" + }, + "folderHintText": { + "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + }, + "deleteFolderPermanently": { + "message": "Are you sure you want to permanently delete this folder?" + }, "baseDomain": { "message": "基底網域", "description": "Domain name. Example: website.com" @@ -572,7 +645,7 @@ "message": "安全筆記" }, "typeSshKey": { - "message": "SSH key" + "message": "SSH 金鑰" }, "typeLoginPlural": { "message": "登入資料" @@ -605,7 +678,7 @@ "message": "全名" }, "address": { - "message": "Address" + "message": "地址" }, "address1": { "message": "地址 1" @@ -689,15 +762,6 @@ "itemName": { "message": "項目名" }, - "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", - "placeholders": { - "collections": { - "content": "$1", - "example": "Work, Personal" - } - } - }, "ex": { "message": "例如", "description": "Short abbreviation for 'example'." @@ -722,7 +786,7 @@ } }, "copySuccessful": { - "message": "Copy Successful" + "message": "複製成功" }, "copyValue": { "message": "複製值", @@ -737,7 +801,7 @@ "description": "Copy passphrase to clipboard" }, "passwordCopied": { - "message": "Password copied" + "message": "已複製密碼" }, "copyUsername": { "message": "複製使用者名稱", @@ -765,28 +829,28 @@ } }, "copyWebsite": { - "message": "Copy website" + "message": "複製網站" }, "copyNotes": { - "message": "Copy notes" + "message": "複製備註" }, "copyAddress": { - "message": "Copy address" + "message": "複製地址" }, "copyPhone": { - "message": "Copy phone" + "message": "複製電話" }, "copyEmail": { "message": "複製電子郵件地址" }, "copyCompany": { - "message": "Copy company" + "message": "複製公司名稱" }, "copySSN": { - "message": "Copy Social Security number" + "message": "複製社會安全號碼" }, "copyPassportNumber": { - "message": "Copy passport number" + "message": "複製護照號碼" }, "copyLicenseNumber": { "message": "Copy license number" @@ -815,9 +879,6 @@ "filter": { "message": "篩選" }, - "moveSelectedToOrg": { - "message": "移動所選至組織" - }, "deleteSelected": { "message": "刪除所選" }, @@ -873,15 +934,6 @@ } } }, - "movedItemsToOrg": { - "message": "將已選取項目移動至 $ORGNAME$", - "placeholders": { - "orgname": { - "content": "$1", - "example": "Company Name" - } - } - }, "itemsMovedToOrg": { "message": "Items moved to $ORGNAME$", "placeholders": { @@ -984,6 +1036,9 @@ "no": { "message": "否" }, + "location": { + "message": "Location" + }, "loginOrCreateNewAccount": { "message": "登入或建立帳戶以存取您的安全密碼庫。" }, @@ -1099,13 +1154,13 @@ "message": "建立帳戶" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "第一次使用 Bitwarden?" }, "setAStrongPassword": { "message": "設定一個強密碼" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "Finish creating your account by setting a password" + "message": "設定密碼以完成建立您的帳號。" }, "newAroundHere": { "message": "第一次使用?" @@ -1117,20 +1172,44 @@ "message": "登入" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "登入 Bitwarden" + }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "驗證逾時" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "此驗證工作階段已逾時。請重試登入。" }, - "verifyIdentity": { - "message": "核實你的身份" + "verifyYourIdentity": { + "message": "Verify your Identity" + }, + "weDontRecognizeThisDevice": { + "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + }, + "continueLoggingIn": { + "message": "Continue logging in" + }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." }, "logInInitiated": { "message": "登入已發起" }, + "logInRequestSent": { + "message": "Request sent" + }, "submit": { "message": "送出" }, @@ -1168,7 +1247,7 @@ "message": "主密碼提示" }, "masterPassHintText": { - "message": "If you forget your password, the password hint can be sent to your email. $CURRENT$/$MAXIMUM$ character maximum.", + "message": "如果您忘記了密碼,可以傳送密碼提示到您的電子信箱。$CURRENT$ / 最多 $MAXIMUM$ 個字元", "placeholders": { "current": { "content": "$1", @@ -1187,20 +1266,14 @@ "message": "Account email" }, "requestHint": { - "message": "Request hint" + "message": "請求提示" }, "requestPasswordHint": { - "message": "Request password hint" + "message": "請求密碼提示" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" }, - "passwordHint": { - "message": "密碼提示" - }, - "enterEmailToGetHint": { - "message": "請輸入您的帳户電子郵件地址以接收主密碼提示。" - }, "getMasterPasswordHint": { "message": "取得主密碼提示" }, @@ -1233,10 +1306,10 @@ "message": "帳戶已建立!現在可以登入了。" }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "您已成功建立新帳號!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "您已經登入!" }, "trialAccountCreated": { "message": "帳戶建立成功。" @@ -1254,10 +1327,10 @@ "message": "電子郵件地址" }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "您的密碼庫已被鎖定" }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "您的帳號已被鎖定。" }, "uuid": { "message": "UUID" @@ -1294,7 +1367,7 @@ "message": "您沒有檢視此集合中所有項目的權限。" }, "youDoNotHavePermissions": { - "message": "You do not have permissions to this collection" + "message": "您沒有存取此集合的權限" }, "noCollectionsInList": { "message": "沒有可列出的集合。" @@ -1320,11 +1393,38 @@ "notificationSentDevice": { "message": "通知已傳送至您的裝置。" }, - "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "notificationSentDevicePart1": { + "message": "Unlock Bitwarden on your device or on the " }, - "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "areYouTryingToAccessYourAccount": { + "message": "Are you trying to access your account?" + }, + "accessAttemptBy": { + "message": "Access attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "confirmAccess": { + "message": "Confirm access" + }, + "denyAccess": { + "message": "Deny access" + }, + "notificationSentDeviceAnchor": { + "message": "web app" + }, + "notificationSentDevicePart2": { + "message": "Make sure the Fingerprint phrase matches the one below before approving." + }, + "notificationSentDeviceComplete": { + "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + }, + "aNotificationWasSentToYourDevice": { + "message": "已傳送通知至您的裝置" }, "versionNumber": { "message": "版本 $VERSION_NUMBER$", @@ -1359,12 +1459,22 @@ "rememberMe": { "message": "記住我" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "再次傳送​​包含驗證碼的電子郵件" }, "useAnotherTwoStepMethod": { "message": "使用另一種兩步驟登入方法" }, + "selectAnotherMethod": { + "message": "Select another method", + "description": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "將您的 YubiKey 插入電腦的 USB 連接埠,然後觸摸其按鈕。" }, @@ -1383,6 +1493,9 @@ "twoStepOptions": { "message": "兩步驟登入選項" }, + "selectTwoStepLoginMethod": { + "message": "Select two-step login method" + }, "recoveryCodeDesc": { "message": "無法使用任何兩步驟登入方式嗎?請使用您的復原碼停用所有兩步驟登入方式。" }, @@ -1397,7 +1510,7 @@ "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP security key" + "message": "YubiKey OTP 安全金鑰" }, "yubiKeyDesc": { "message": "使用 YubiKey 存取您的帳戶。支援 YubiKey 4 系列、5 系列以及 NEO 裝置。" @@ -1425,6 +1538,9 @@ "webAuthnMigrated": { "message": "(遷移自 FIDO)" }, + "openInNewTab": { + "message": "Open in new tab" + }, "emailTitle": { "message": "電子郵件" }, @@ -1443,9 +1559,6 @@ "moveToOrgDesc": { "message": "選擇您希望將這個項目移動至哪個組織。項目的擁有權將會轉移至該組織。轉移之後,您將不再是此項目的直接擁有者。" }, - "moveManyToOrgDesc": { - "message": "選擇您希望將這些項目移動至哪個組織。項目的擁有權將會轉移至該組織。轉移之後,您將不再是這些項目的直接擁有者。" - }, "collectionsDesc": { "message": "編輯與此項目共享的集合。只有具有這些集合存取權限的組織使用者才能夠看到此項目。" }, @@ -1479,23 +1592,6 @@ } } }, - "moveSelectedItemsCountDesc": { - "message": "你選擇了 $COUNT$ 個項目。當中有 $MOVEABLE_COUNT$ 個項目可移動到組織當中,而有 $NONMOVEABLE_COUNT$ 個無法移動。", - "placeholders": { - "count": { - "content": "$1", - "example": "10" - }, - "moveable_count": { - "content": "$2", - "example": "8" - }, - "nonmoveable_count": { - "content": "$3", - "example": "2" - } - } - }, "verificationCodeTotp": { "message": "驗證碼 (TOTP)" }, @@ -1613,9 +1709,6 @@ "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." }, - "regeneratePassword": { - "message": "重新產生密碼" - }, "length": { "message": "長度" }, @@ -1670,10 +1763,10 @@ "message": "沒有可列出的密碼。" }, "clearHistory": { - "message": "Clear history" + "message": "清除歷史紀錄" }, "nothingToShow": { - "message": "Nothing to show" + "message": "沒有可顯示的內容" }, "nothingGeneratedRecently": { "message": "You haven't generated anything recently" @@ -1715,6 +1808,12 @@ "logBackIn": { "message": "請重新登入。" }, + "currentSession": { + "message": "目前工作階段" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "請重新登入。若您還在使用其他 Bitwarden 應用程式,也請登出後再重新登入。" }, @@ -1782,12 +1881,6 @@ "dangerZone": { "message": "危險區域" }, - "dangerZoneDesc": { - "message": "小心,這些動作無法復原!" - }, - "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" - }, "deauthorizeSessions": { "message": "取消工作階段授權" }, @@ -1797,11 +1890,32 @@ "deauthorizeSessionsWarning": { "message": "接下來會登出目前的工作階段,並要求您重新登入。若您有設定兩步驟登入,也需重新驗證。其他裝置上的活動工作階段最多會保持一個小時。" }, + "newDeviceLoginProtection": { + "message": "New device login" + }, + "turnOffNewDeviceLoginProtection": { + "message": "Turn off new device login protection" + }, + "turnOnNewDeviceLoginProtection": { + "message": "Turn on new device login protection" + }, + "turnOffNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + }, + "turnOnNewDeviceLoginProtectionModalDesc": { + "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + }, + "turnOffNewDeviceLoginProtectionWarning": { + "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + }, + "accountNewDeviceLoginProtectionSaved": { + "message": "New device login protection changes saved" + }, "sessionsDeauthorized": { "message": "已取消所有工作階段授權" }, "accountIsOwnedMessage": { - "message": "This account is owned by $ORGANIZATIONNAME$", + "message": "此帳號由 $ORGANIZATIONNAME$ 擁有", "placeholders": { "organizationName": { "content": "$1", @@ -2060,6 +2174,9 @@ "twoStepLoginRecoveryWarning": { "message": "啟用兩步驟登入可能會將您永久鎖定在您的 Bitwarden 帳戶外。如果您無法正常使用兩步驟登入方式(例如,您遺失了裝置),則可以使用復原碼存取您的帳戶。 如果您失去帳戶的存取權限,Bitwarden 也無法幫助您。所以我們建議您記下或列印復原碼,並將其妥善保存。" }, + "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." + }, "viewRecoveryCode": { "message": "檢視復原碼" }, @@ -2098,8 +2215,20 @@ "manage": { "message": "管理" }, - "canManage": { - "message": "可以管理" + "manageCollection": { + "message": "Manage collection" + }, + "viewItems": { + "message": "View items" + }, + "viewItemsHidePass": { + "message": "View items, hidden passwords" + }, + "editItems": { + "message": "Edit items" + }, + "editItemsHidePass": { + "message": "Edit items, hidden passwords" }, "disable": { "message": "停用" @@ -2107,6 +2236,9 @@ "revokeAccess": { "message": "撤銷存取權限" }, + "revoke": { + "message": "Revoke" + }, "twoStepLoginProviderEnabled": { "message": "您的帳戶已啟用此兩步驟登入方式。" }, @@ -2114,19 +2246,19 @@ "message": "輸入您的主密碼以修改兩步驟登入設定。" }, "twoStepAuthenticatorInstructionPrefix": { - "message": "Download an authenticator app such as" + "message": "下載驗證應用程式,例如" }, "twoStepAuthenticatorInstructionInfix1": { - "message": "," + "message": "、" }, "twoStepAuthenticatorInstructionInfix2": { - "message": "or" + "message": "或" }, "twoStepAuthenticatorInstructionSuffix": { - "message": "." + "message": "。" }, "continueToExternalUrlTitle": { - "message": "Continue to $URL$?", + "message": "繼續前往 $URL$ 嗎?", "placeholders": { "url": { "content": "$1", @@ -2135,16 +2267,16 @@ } }, "continueToExternalUrlDesc": { - "message": "You are leaving Bitwarden and launching an external website in a new window." + "message": "您將離開 Bitwarden 並在新視窗開啟外部網站。" }, "twoStepContinueToBitwardenUrlTitle": { - "message": "Continue to bitwarden.com?" + "message": "繼續前往 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." }, "twoStepAuthenticatorScanCodeV2": { - "message": "Scan the QR code below with your authenticator app or enter the key." + "message": "使用驗證應用程式掃描下方的 QR 碼,或輸入金鑰。" }, "twoStepAuthenticatorQRCanvasError": { "message": "Could not load QR code. Try again or use the key below." @@ -2296,11 +2428,8 @@ "twoFactorU2fProblemReadingTryAgain": { "message": "讀取安全鑰匙時發生問題。請再試一次。" }, - "twoFactorWebAuthnWarning": { - "message": "由於平台限制,無法於所有 Bitwarden 應用程式中使用 WebAuthn。請設定另一套兩步驟登入方式,以確保在 WebAuthn 無法使用時還能存取您的帳戶。支援的平台有:" - }, - "twoFactorWebAuthnSupportWeb": { - "message": "桌上型電腦/筆記型電腦上支援 WebAuthn 功能的瀏覽器(啟用了 FIDO U2F 的 Chrome、Opera、Vivaldi 或 Firefox)上的網頁版密碼庫和瀏覽器擴充套件。" + "twoFactorWebAuthnWarning1": { + "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." }, "twoFactorRecoveryYourCode": { "message": "您的 Bitwarden 兩步驟登入復原碼" @@ -3266,6 +3395,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "此使用者正在使用兩步驟登入保護帳戶。" }, @@ -3717,6 +3852,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "已為使用者 $ID$ 取消連結 SSO。", "placeholders": { @@ -3765,6 +3903,76 @@ "device": { "message": "裝置" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "登入要求已逾期。" + }, + "justNow": { + "message": "剛剛" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "建立帳號於" }, @@ -3888,8 +4096,14 @@ "updateBrowserDesc": { "message": "未支援您使用的瀏覽器。網頁版密碼庫可能無法正常運作。" }, + "youHaveAPendingLoginRequest": { + "message": "You have a pending login request from another device." + }, + "reviewLoginRequest": { + "message": "Review login request" + }, "freeTrialEndPromptCount": { - "message": "Your free trial ends in $COUNT$ days.", + "message": "您的免費試用將於 $COUNT$ 天後結束。", "placeholders": { "count": { "content": "$1", @@ -3898,7 +4112,7 @@ } }, "freeTrialEndPromptMultipleDays": { - "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "message": "$ORGANIZATION$,您的免費試用將於 $COUNT$ 天後結束。", "placeholders": { "count": { "content": "$2", @@ -3911,7 +4125,7 @@ } }, "freeTrialEndPromptTomorrow": { - "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "message": "$ORGANIZATION$,您的免費試用明天就結束了。", "placeholders": { "organization": { "content": "$1", @@ -3920,10 +4134,10 @@ } }, "freeTrialEndPromptTomorrowNoOrgName": { - "message": "Your free trial ends tomorrow." + "message": "您的免費試用明天就結束了。" }, "freeTrialEndPromptToday": { - "message": "$ORGANIZATION$, your free trial ends today.", + "message": "$ORGANIZATION$,您的免費試用今天就結束了。", "placeholders": { "organization": { "content": "$1", @@ -3932,16 +4146,16 @@ } }, "freeTrialEndingTodayWithoutOrgName": { - "message": "Your free trial ends today." + "message": "您的免費試用今天就結束了。" }, "clickHereToAddPaymentMethod": { - "message": "Click here to add a payment method." + "message": "按一下此處來新增付款方式。" }, "joinOrganization": { "message": "加入組織" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "加入 $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -3982,6 +4196,9 @@ "recoverAccountTwoStepDesc": { "message": "若您無法透過一般的兩步驟登入方式存取您的帳戶,您可以使用兩步驟登入復原碼以停用帳戶上的所有兩步驟登入方式。" }, + "logInBelowUsingYourSingleUseRecoveryCode": { + "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + }, "recoverAccountTwoStep": { "message": "復原帳戶的兩步驟登入" }, @@ -4001,7 +4218,7 @@ "message": "您已要求刪除您的 Bitwarden 帳戶。請點選下方的按鈕確認。" }, "deleteRecoverOrgConfirmDesc": { - "message": "You have requested to delete your Bitwarden organization." + "message": "您已要求刪除您的 Bitwarden 組織。" }, "myOrganization": { "message": "我的組織" @@ -4273,6 +4490,58 @@ "encryptionKeyUpdateCannotProceed": { "message": "Encryption key update cannot proceed" }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, "keyUpdateFoldersFailed": { "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." }, @@ -4421,7 +4690,7 @@ "message": "已選擇" }, "recommended": { - "message": "Recommended" + "message": "建議" }, "ownership": { "message": "擁有權" @@ -4544,6 +4813,15 @@ "masterPassPolicyDesc": { "message": "設定主密碼強度要求。" }, + "passwordStrengthScore": { + "message": "Password strength score $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, "twoStepLoginPolicyTitle": { "message": "要求兩步驟登入" }, @@ -4559,9 +4837,6 @@ "passwordGeneratorPolicyDesc": { "message": "設定密碼產生器要求。" }, - "passwordGeneratorPolicyInEffect": { - "message": "一個或多個組織原則正影響密碼產生器設定。" - }, "masterPasswordPolicyInEffect": { "message": "一個或多個組織原則要求您的主密碼須符合下列條件:" }, @@ -4740,10 +5015,10 @@ "message": "你已成功登入" }, "thisWindowWillCloseIn5Seconds": { - "message": "This window will automatically close in 5 seconds" + "message": "此視窗將在 5 秒後自動關閉。" }, "youMayCloseThisWindow": { - "message": "You may close this window" + "message": "您可以關閉此視窗" }, "includeAllTeamsFeatures": { "message": "包含所有團隊版功能" @@ -4827,12 +5102,40 @@ "requireSsoExemption": { "message": "組織擁有者與管理員不受此原則的執行影響。" }, + "limitSendViews": { + "message": "Limit views" + }, + "limitSendViewsHint": { + "message": "No one can view this Send after the limit is reached.", + "description": "Displayed under the limit views field on Send" + }, + "limitSendViewsCount": { + "message": "$ACCESSCOUNT$ views left", + "description": "Displayed under the limit views field on Send", + "placeholders": { + "accessCount": { + "content": "$1", + "example": "2" + } + } + }, + "sendDetails": { + "message": "Send details", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeTextToShare": { + "message": "Text to share" + }, "sendTypeFile": { "message": "檔案" }, "sendTypeText": { "message": "文字" }, + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "createSend": { "message": "建立新 Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4857,19 +5160,15 @@ "message": "刪除 Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "deleteSendConfirmation": { - "message": "您確定要刪除此 Send 嗎?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "whatTypeOfSend": { - "message": "這是什麽類型的 Send?", + "deleteSendPermanentConfirmation": { + "message": "Are you sure you want to permanently delete this Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "刪除日期" }, - "deletionDateDesc": { - "message": "此 Send 將在指定的日期和時間後被永久刪除。", + "deletionDateDescV2": { + "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -4882,21 +5181,6 @@ "maxAccessCount": { "message": "最大存取次數" }, - "maxAccessCountDesc": { - "message": "如果設定此選項,當達到最大存取次數時,使用者將無法再次存取此 Send。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "目前存取次數" - }, - "sendPasswordDesc": { - "message": "選用。使用者需提供密碼才能存取此 Send。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNotesDesc": { - "message": "關於此 Send 的私人備註。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "disabled": { "message": "已停用" }, @@ -4908,7 +5192,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "copyLink": { - "message": "Copy link" + "message": "複製連結" }, "copySendLink": { "message": "複製 Send 連結", @@ -4923,13 +5207,6 @@ "removePasswordConfirmation": { "message": "您確定要移除此密碼嗎?" }, - "hideEmail": { - "message": "對收件人隱藏我的電子郵件地址。" - }, - "disableThisSend": { - "message": "停用此 Send 以阻止任何人存取。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "allSends": { "message": "所有 Send" }, @@ -4940,6 +5217,9 @@ "pendingDeletion": { "message": "等待刪除" }, + "hideTextByDefault": { + "message": "Hide text by default" + }, "expired": { "message": "已逾期" }, @@ -5161,13 +5441,6 @@ "message": "建立或編輯 Send 時,始終對收件人顯示成員的電子郵件地址。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendOptionsPolicyInEffect": { - "message": "以下組織原則目前作用中:" - }, - "sendDisableHideEmailInEffect": { - "message": "使用者在建立或編輯 Send 時不允許隱藏他們的電子郵件地址。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "modifiedPolicyId": { "message": "原則 $ID$ 已修改。", "placeholders": { @@ -5267,27 +5540,6 @@ "personalOwnershipCheckboxDesc": { "message": "停用組織使用者的個人擁有權" }, - "textHiddenByDefault": { - "message": "存取此 Send 時,將預設隱藏文字", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "用於描述此 Send 的易記名稱。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "您想要傳送的文字。" - }, - "sendFileDesc": { - "message": "您想要傳送的檔案。" - }, - "copySendLinkOnSave": { - "message": "儲存時複製連結至剪貼簿以便共用此 Send。" - }, - "sendLinkLabel": { - "message": "Send 連結", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5335,19 +5587,19 @@ "message": "Protect secrets with end-to-end encryption. No more hard coding secrets or sharing through .env files." }, "enhanceDeveloperProductivity": { - "message": "Enhance developer productivity." + "message": "提升開發者生產力。" }, "enhanceDeveloperProductivityDescription": { "message": "Programmatically retrieve and deploy secrets at runtime so developers can focus on what matters most, like improving code quality." }, "strengthenBusinessSecurity": { - "message": "Strengthen business security." + "message": "強化企業安全。" }, "strengthenBusinessSecurityDescription": { "message": "Maintain tight control over machine and human access to secrets with SSO integrations, event logs, and access rotation." }, "tryItNow": { - "message": "Try it now" + "message": "立即體驗" }, "sendRequest": { "message": "Send request" @@ -5359,7 +5611,7 @@ "message": "Bitwarden Secrets Manager" }, "moreProductsFromBitwarden": { - "message": "More products from Bitwarden" + "message": "更多來自 Bitwarden 的產品" }, "requestAccessToSecretsManager": { "message": "Request access to Secrets Manager" @@ -5436,12 +5688,21 @@ "dateParsingError": { "message": "儲存刪除日期和逾期日期時發生錯誤。" }, + "hideYourEmail": { + "message": "Hide your email address from viewers." + }, "webAuthnFallbackMsg": { "message": "要驗證您的 2FA,請點選下方的按鈕。" }, "webAuthnAuthenticate": { "message": "驗證 WebAuthn" }, + "readSecurityKey": { + "message": "Read security key" + }, + "awaitingSecurityKeyInteraction": { + "message": "Awaiting security key interaction..." + }, "webAuthnNotSupported": { "message": "此瀏覽器不支援 WebAuthn。" }, @@ -5655,6 +5916,20 @@ "error": { "message": "錯誤" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "管理使用者也必須被授予管理帳戶復原的權限" }, @@ -6516,15 +6791,6 @@ "message": "產生器", "description": "Short for 'credential generator'." }, - "whatWouldYouLikeToGenerate": { - "message": "您想要產生什麼?" - }, - "passwordType": { - "message": "密碼類型" - }, - "regenerateUsername": { - "message": "重新產生使用者名稱" - }, "generateUsername": { "message": "產生使用者名稱" }, @@ -6532,7 +6798,7 @@ "message": "Generate email" }, "spinboxBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$.", + "message": "值必須介於 $MIN$ 及 $MAX$。", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6565,9 +6831,6 @@ } } }, - "usernameType": { - "message": "使用者名稱類型" - }, "plusAddressedEmail": { "message": "加號地址電子郵件", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -6581,6 +6844,9 @@ "catchallEmailDesc": { "message": "使用您的網域設定的 Catch-all 收件匣。" }, + "useThisEmail": { + "message": "Use this email" + }, "random": { "message": "隨機", "description": "Generates domain-based username using random letters" @@ -6592,10 +6858,10 @@ "message": "Username generator" }, "useThisPassword": { - "message": "Use this password" + "message": "使用此密碼" }, "useThisUsername": { - "message": "Use this username" + "message": "使用此使用者名稱" }, "securePasswordGenerated": { "message": "Secure password generated! Don't forget to also update your password on the website." @@ -6671,7 +6937,7 @@ "message": "使用外部轉寄服務產生一個電子郵件別名。" }, "forwarderDomainName": { - "message": "Email domain", + "message": "電子信箱網域", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { @@ -6730,6 +6996,30 @@ } } }, + "forwaderInvalidOperation": { + "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "description": "Displayed when the user is forbidden from using the API by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidOperationWithMessage": { + "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, "forwarderNoAccountId": { "message": "Unable to obtain $SERVICENAME$ masked email account ID.", "description": "Displayed when the forwarding service fails to return an account ID.", @@ -6784,9 +7074,6 @@ "message": "主機名稱", "description": "Part of a URL." }, - "apiAccessToken": { - "message": "API 存取權杖" - }, "deviceVerification": { "message": "裝置驗證" }, @@ -6957,11 +7244,17 @@ "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." }, "launchDuoAndFollowStepsToFinishLoggingIn": { - "message": "Launch Duo and follow the steps to finish logging in." + "message": "啟動 Duo 並依照步驟完成登入。" }, "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "啟動 Duo" }, @@ -6983,12 +7276,6 @@ "numberOfUsers": { "message": "使用者數量" }, - "loggingInAs": { - "message": "正登入為" - }, - "notYou": { - "message": "不是您嗎?" - }, "pickAnAvatarColor": { "message": "選擇頭像顏色" }, @@ -7498,18 +7785,6 @@ "noCollection": { "message": "沒有分類" }, - "canView": { - "message": "可以檢視" - }, - "canViewExceptPass": { - "message": "可以檢視(除了密碼)" - }, - "canEdit": { - "message": "可以編輯" - }, - "canEditExceptPass": { - "message": "可以編輯(除了密碼)" - }, "noCollectionsAdded": { "message": "未新增任何分類" }, @@ -7814,13 +8089,13 @@ "message": "或" }, "unlockWithBiometrics": { - "message": "Unlock with biometrics" + "message": "使用生物辨識解鎖" }, "unlockWithPin": { - "message": "Unlock with PIN" + "message": "使用 PIN 碼解鎖" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "使用主密碼解鎖" }, "licenseAndBillingManagement": { "message": "授權和計費管理" @@ -8124,42 +8399,39 @@ "requestAdminApproval": { "message": "要求管理員核准" }, - "approveWithMasterPassword": { - "message": "使用主密碼核准" - }, "trustedDeviceEncryption": { "message": "可信任的裝置加密" }, "trustedDevices": { "message": "可信任的裝置" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "通過驗證以後,成員將使用儲存在裝置的金鑰來解密密碼庫資料。使用此選項後,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "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", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "單一組織", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "single organization", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": "原則,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "policy,", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "要求 SSO", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO required", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "原則以及具有自動注冊的", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "policy, and", + "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.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "帳戶復原管理", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "account recovery administration", + "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.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "原則將被開啓。", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "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": "您的組織權限已更新,因此您需要設定主密碼。", @@ -8236,6 +8508,18 @@ "approveRequest": { "message": "核准要求" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "沒有裝置要求" }, @@ -8329,9 +8613,6 @@ "adminApprovalRequestSentToAdmins": { "message": "您的要求已傳送給您的管理員" }, - "youWillBeNotifiedOnceApproved": { - "message": "核准後將通知您。" - }, "troubleLoggingIn": { "message": "登入時遇到困難?" }, @@ -8445,6 +8726,9 @@ "limitCollectionDeletionDesc": { "message": "Limit collection deletion to owners and admins" }, + "limitItemDeletionDesc": { + "message": "Limit item deletion to members with the Can manage permission" + }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "擁有人與管理員可以管理所有分類與項目" }, @@ -8494,9 +8778,6 @@ "message": "Self-host server URL", "description": "Label for field requesting a self-hosted integration service URL" }, - "aliasDomain": { - "message": "別名網域" - }, "alreadyHaveAccount": { "message": "已經有一個帳戶了嗎?" }, @@ -8558,11 +8839,11 @@ "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." }, - "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "grantManageCollectionWarningTitle": { + "message": "Missing Manage Collection Permissions" }, - "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "grantManageCollectionWarning": { + "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." }, "grantCollectionAccess": { "message": "Grant groups or members access to this collection." @@ -8700,10 +8981,10 @@ } }, "addField": { - "message": "Add field" + "message": "新增欄位" }, "editField": { - "message": "Edit field" + "message": "編輯欄位" }, "items": { "message": "項目" @@ -9057,6 +9338,21 @@ "deviceManagementDesc": { "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, + "deviceIdMissing": { + "message": "Device ID is missing" + }, + "deviceTypeMissing": { + "message": "Device type is missing" + }, + "deviceCreationDateMissing": { + "message": "Device creation date is missing" + }, + "desktopRequired": { + "message": "Desktop required" + }, + "reopenLinkOnDesktop": { + "message": "Reopen this link from your email on a desktop." + }, "integrationCardTooltip": { "message": "Launch $INTEGRATION$ implementation guide.", "placeholders": { @@ -9067,7 +9363,7 @@ } }, "smIntegrationTooltip": { - "message": "Set up $INTEGRATION$.", + "message": "設定 $INTEGRATION$。", "placeholders": { "integration": { "content": "$1", @@ -9076,7 +9372,7 @@ } }, "smSdkTooltip": { - "message": "View $SDK$ repository", + "message": "檢視 $SDK$ 儲存庫", "placeholders": { "sdk": { "content": "$1", @@ -9094,7 +9390,7 @@ } }, "smSdkAriaLabel": { - "message": "view $SDK$ repository in a new tab.", + "message": "在新分頁中檢視 $SDK$ 儲存庫。", "placeholders": { "sdk": { "content": "$1", @@ -9123,6 +9419,9 @@ "monthPerMember": { "message": "month per member" }, + "monthPerMemberBilledAnnually": { + "message": "month per member billed annually" + }, "seats": { "message": "席位" }, @@ -9151,13 +9450,13 @@ "message": "從提供者入口網站管理計費" }, "continueSettingUpFreeTrial": { - "message": "Continue setting up your free trial of Bitwarden" + "message": "繼續設定您的 Bitwarden 免費試用" }, "continueSettingUpFreeTrialPasswordManager": { - "message": "Continue setting up your free trial of Bitwarden Password Manager" + "message": "繼續設定您的 Bitwarden 密碼管理器免費試用" }, "continueSettingUpFreeTrialSecretsManager": { - "message": "Continue setting up your free trial of Bitwarden Secrets Manager" + "message": "繼續設定您的 Bitwarden 機密管理免費試用" }, "enterTeamsOrgInfo": { "message": "Enter your Teams organization information" @@ -9411,7 +9710,7 @@ "message": "Client details" }, "downloadCSV": { - "message": "Download CSV" + "message": "下載 CSV" }, "monthlySubscriptionUserSeatsMessage": { "message": "Adjustments to your subscription will result in prorated charges to your billing totals on your next billing period. " @@ -9442,10 +9741,10 @@ "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": "新增到資料夾" }, "selectFolder": { - "message": "Select folder" + "message": "選擇資料夾" }, "personalItemTransferWarningSingular": { "message": "1 item will be permanently transferred to the selected organization. You will no longer own this item." @@ -9542,14 +9841,20 @@ "message": "Learn more about member roles and permissions" }, "whatIsACvvNumber": { - "message": "What is a CVV number?" + "message": "CVV 號碼是什麼?" }, "learnMoreAboutApi": { - "message": "Learn more about Bitwarden's API" + "message": "瞭解更多關於 Bitwarden API 的資訊" + }, + "fileSend": { + "message": "File Send" }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, @@ -9643,14 +9948,23 @@ "sshKeyAlgorithm": { "message": "Key algorithm" }, + "sshPrivateKey": { + "message": "Private key" + }, + "sshPublicKey": { + "message": "Public key" + }, + "sshFingerprint": { + "message": "Fingerprint" + }, "sshKeyFingerprint": { "message": "Fingerprint" }, "sshKeyPrivateKey": { - "message": "Private key" + "message": "私密金鑰" }, "sshKeyPublicKey": { - "message": "Public key" + "message": "公開金鑰" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -9747,7 +10061,7 @@ "message": "Enter the the field's html id, name, aria-label, or placeholder." }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "包含大寫字元", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -9755,7 +10069,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "包含小寫字元", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -9763,7 +10077,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "包含數字", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -9771,18 +10085,14 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "包含特殊字元", "description": "Full description for the password generator special characters checkbox" }, - "specialCharactersLabel": { - "message": "!@#$%^&*", - "description": "Label for the password generator special characters checkbox" - }, "addAttachment": { - "message": "Add attachment" + "message": "新增附件" }, "maxFileSizeSansPunctuation": { - "message": "Maximum file size is 500 MB" + "message": "最大檔案大小為 500MB" }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" @@ -9804,7 +10114,7 @@ "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." }, "deleteOrganizationUser": { - "message": "Delete $NAME$", + "message": "刪除 $NAME$", "placeholders": { "name": { "content": "$1", @@ -9828,7 +10138,7 @@ "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { - "message": "Deleted $NAME$", + "message": "已刪除 $NAME$", "placeholders": { "name": { "content": "$1", @@ -9879,7 +10189,7 @@ "message": "This action is not applicable to any of the selected members." }, "deletedSuccessfully": { - "message": "Deleted successfully" + "message": "刪除成功" }, "freeFamiliesSponsorship": { "message": "Remove Free Bitwarden Families sponsorship" @@ -9896,6 +10206,15 @@ "descriptorCode": { "message": "Descriptor code" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "importantNotice": { "message": "Important notice" }, @@ -9909,7 +10228,7 @@ "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." }, "remindMeLater": { - "message": "Remind me later" + "message": "稍後再提醒我" }, "newDeviceVerificationNoticePageOneFormContent": { "message": "Do you have reliable access to your email, $EMAIL$?", @@ -9927,14 +10246,23 @@ "message": "Yes, I can reliably access my email" }, "turnOnTwoStepLogin": { - "message": "Turn on two-step login" + "message": "啟動兩階段登入" }, "changeAcctEmail": { - "message": "Change account email" + "message": "更改帳號電子郵件位址" }, "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10020,8 +10348,70 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "sshKeyWrongPassword": { + "message": "The password you entered is incorrect." + }, + "importSshKey": { + "message": "Import" + }, + "confirmSshKeyPassword": { + "message": "Confirm password" + }, + "enterSshKeyPasswordDesc": { + "message": "Enter the password for the SSH key." + }, + "enterSshKeyPassword": { + "message": "Enter password" + }, + "invalidSshKey": { + "message": "The SSH key is invalid" + }, + "sshKeyTypeUnsupported": { + "message": "The SSH key type is not supported" + }, + "importSshKeyFromClipboard": { + "message": "Import key from clipboard" + }, + "sshKeyImported": { + "message": "SSH key imported successfully" + }, + "copySSHPrivateKey": { + "message": "Copy private key" + }, + "openingExtension": { + "message": "Opening the Bitwarden browser extension" + }, + "somethingWentWrong": { + "message": "Something went wrong..." + }, + "openingExtensionError": { + "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + }, + "openExtension": { + "message": "Open extension" + }, + "doNotHaveExtension": { + "message": "Don't have the Bitwarden browser extension?" + }, + "installExtension": { + "message": "Install extension" + }, + "openedExtension": { + "message": "Opened the browser extension" + }, + "openedExtensionViewAtRiskPasswords": { + "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + }, + "openExtensionManuallyPart1": { + "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "openExtensionManuallyPart2": { + "message": "from the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" + }, + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10033,8 +10423,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10050,8 +10440,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10062,5 +10452,99 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "聯繫 $PROVIDER$ 以取得協助。", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } + }, + "accountDeprovisioningNotification": { + "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + }, + "deleteManagedUserWarningDesc": { + "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + }, + "deleteManagedUserWarning": { + "message": "Delete is a new action!" + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } + }, + "existingOrganization": { + "message": "Existing organization" + }, + "selectOrganizationProviderPortal": { + "message": "Select an organization to add to your Provider Portal." + }, + "noOrganizations": { + "message": "There are no organizations to list" + }, + "yourProviderSubscriptionCredit": { + "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + }, + "doYouWantToAddThisOrg": { + "message": "Do you want to add this organization to $PROVIDER$?", + "placeholders": { + "provider": { + "content": "$1", + "example": "Cool MSP" + } + } + }, + "addedExistingOrganization": { + "message": "Added existing organization" + }, + "assignedExceedsAvailable": { + "message": "Assigned seats exceed available seats." + }, + "changeAtRiskPassword": { + "message": "Change at-risk password" + }, + "removeUnlockWithPinPolicyTitle": { + "message": "Remove Unlock with PIN" + }, + "removeUnlockWithPinPolicyDesc": { + "message": "Do not allow members to unlock their account with a PIN." + }, + "limitedEventLogs": { + "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", + "placeholders": { + "product_type": { + "content": "$1", + "example": "Teams" + } + } + }, + "upgradeForFullEvents": { + "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." + }, + "upgradeEventLogTitle": { + "message": "Upgrade for real event log data" + }, + "upgradeEventLogMessage": { + "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + }, + "cannotCreateCollection": { + "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." } } diff --git a/apps/web/src/main.ts b/apps/web/src/main.ts index 1d1519c8b50..b202a170d26 100644 --- a/apps/web/src/main.ts +++ b/apps/web/src/main.ts @@ -11,6 +11,4 @@ if (process.env.NODE_ENV === "production") { enableProdMode(); } -// 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 -platformBrowserDynamic().bootstrapModule(AppModule, { preserveWhitespaces: true }); +void platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/apps/web/src/polyfills.ts b/apps/web/src/polyfills.ts index 33af553f786..3971ed3207f 100644 --- a/apps/web/src/polyfills.ts +++ b/apps/web/src/polyfills.ts @@ -1,4 +1,5 @@ import "core-js/stable"; +import "core-js/proposals/explicit-resource-management"; import "zone.js"; if (process.env.NODE_ENV === "production") { diff --git a/apps/web/src/scss/buttons.scss b/apps/web/src/scss/buttons.scss index 881f853e76f..f026aa898e7 100644 --- a/apps/web/src/scss/buttons.scss +++ b/apps/web/src/scss/buttons.scss @@ -184,7 +184,8 @@ } } -button.no-btn { +button.no-btn, +a.no-btn { background: transparent; border: none; padding: 0; diff --git a/apps/web/src/scss/pages.scss b/apps/web/src/scss/pages.scss index 684d45a1a66..1e5c233e326 100644 --- a/apps/web/src/scss/pages.scss +++ b/apps/web/src/scss/pages.scss @@ -1,37 +1,3 @@ -app-generator { - #lengthRange { - width: 100%; - } - - .card-generated { - .card-body { - @include themify($themes) { - background: themed("foregroundColor"); - } - align-items: center; - display: flex; - flex-wrap: wrap; - font-family: $font-family-monospace; - font-size: $font-size-lg; - justify-content: center; - text-align: center; - } - } -} - -app-password-generator-history { - .list-group-item { - line-height: 1; - @include themify($themes) { - background: themed("backgroundColor"); - } - - .password { - font-family: $font-family-monospace; - } - } -} - tools-import { textarea { height: 150px; diff --git a/apps/web/src/scss/plugins.scss b/apps/web/src/scss/plugins.scss index 5fbd32ac4ee..ad996b0efaf 100644 --- a/apps/web/src/scss/plugins.scss +++ b/apps/web/src/scss/plugins.scss @@ -12,7 +12,7 @@ } #web-authn-frame { - height: 315px; + height: 40px; @include themify($themes) { background: themed("imgLoading") 0 0 no-repeat; } diff --git a/apps/web/src/scss/tailwind.css b/apps/web/src/scss/tailwind.css index 1ac7b154011..0ae3c291b56 100644 --- a/apps/web/src/scss/tailwind.css +++ b/apps/web/src/scss/tailwind.css @@ -1,9 +1,8 @@ +@import "../../../../libs/components/src/tw-theme.css"; @tailwind base; @tailwind components; @tailwind utilities; -@import "../../../../libs/components/src/tw-theme.css"; - /* * Web specific global styling. * diff --git a/apps/web/src/scss/vault-filters.scss b/apps/web/src/scss/vault-filters.scss index 27b4b8164f9..7133c469284 100644 --- a/apps/web/src/scss/vault-filters.scss +++ b/apps/web/src/scss/vault-filters.scss @@ -147,62 +147,37 @@ } } -.groupings { - .card { - #search { - margin-bottom: 1rem; +.filter { + ul:last-child { + margin-bottom: 0; + } + + a { + @include themify($themes) { + color: themed("textHeadingColor"); + } + } + .show-active { + display: none; + } + li.active { + > .show-active, + > div .show-active { + display: inline; + } + } + li.active { + > button:first-of-type, + > div button:first-of-type { @include themify($themes) { - background-color: themed("inputBackgroundColor"); - border-color: themed("inputBorderColor"); - color: themed("inputTextColor"); - } - - &::placeholder { - @include themify($themes) { - color: themed("inputPlaceholderColor"); - } + color: themed("linkColor"); } } - h3 { - font-weight: normal; + > .bwi, + > div > .bwi { @include themify($themes) { - color: themed("textMuted"); - } - } - - ul:last-child { - margin-bottom: 0; - } - - .card-body a { - @include themify($themes) { - color: themed("textHeadingColor"); - font-weight: themed("linkWeight"); - } - } - .show-active { - display: none; - } - li.active { - > .show-active, - > div .show-active { - display: inline; - } - } - li.active { - > button:first-of-type, - > div button:first-of-type { - @include themify($themes) { - color: themed("linkColor"); - } - } - - > .bwi, - > div > .bwi { - @include themify($themes) { - color: themed("linkColor"); - } + color: themed("linkColor"); } } } diff --git a/apps/web/src/utils/flags.ts b/apps/web/src/utils/flags.ts index dc0103e2436..a762053da35 100644 --- a/apps/web/src/utils/flags.ts +++ b/apps/web/src/utils/flags.ts @@ -7,11 +7,9 @@ import { } from "@bitwarden/common/platform/misc/flags"; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type Flags = {} & SharedFlags; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type DevFlags = {} & SharedDevFlags; export function flagEnabled(flag: keyof Flags): boolean { diff --git a/apps/web/tailwind.config.js b/apps/web/tailwind.config.js index 2c0108ca3e2..4a3f6cfef1b 100644 --- a/apps/web/tailwind.config.js +++ b/apps/web/tailwind.config.js @@ -5,7 +5,7 @@ config.content = [ "./src/**/*.{html,ts}", "../../libs/components/src/**/*.{html,ts}", "../../libs/auth/src/**/*.{html,ts}", - "../../libs/key-management/src/**/*.{html,ts}", + "../../libs/key-management-ui/src/**/*.{html,ts}", "../../libs/vault/src/**/*.{html,ts}", "../../libs/angular/src/**/*.{html,ts}", "../../bitwarden_license/bit-web/src/**/*.{html,ts}", diff --git a/apps/web/test.setup.ts b/apps/web/test.setup.ts index 224262ec83e..5c248668a6d 100644 --- a/apps/web/test.setup.ts +++ b/apps/web/test.setup.ts @@ -1,4 +1,4 @@ -import "jest-preset-angular/setup-jest"; +import "@bitwarden/ui-common/setup-jest"; Object.defineProperty(window, "CSS", { value: null }); Object.defineProperty(window, "getComputedStyle", { diff --git a/apps/web/tsconfig.build.json b/apps/web/tsconfig.build.json index b10c4f9d899..39ab37efbb8 100644 --- a/apps/web/tsconfig.build.json +++ b/apps/web/tsconfig.build.json @@ -1,5 +1,8 @@ { "extends": "./tsconfig.json", "files": ["src/polyfills.ts", "src/main.ts", "src/theme.ts"], - "include": ["src/connectors/*.ts", "../../libs/common/src/platform/services/**/*.worker.ts"] + "include": [ + "src/connectors/*.ts", + "../../libs/common/src/key-management/crypto/services/encrypt.worker.ts" + ] } diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 678db7c4af5..3d62a30bc01 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -7,8 +7,8 @@ "paths": { "@bitwarden/admin-console/common": ["../../libs/admin-console/src/common"], "@bitwarden/angular/*": ["../../libs/angular/src/*"], - "@bitwarden/auth/common": ["../../libs/auth/src/common"], "@bitwarden/auth/angular": ["../../libs/auth/src/angular"], + "@bitwarden/auth/common": ["../../libs/auth/src/common"], "@bitwarden/billing": ["../../libs/billing/src"], "@bitwarden/bit-common/*": ["../../bitwarden_license/bit-common/src/*"], "@bitwarden/common/*": ["../../libs/common/src/*"], @@ -18,30 +18,31 @@ "@bitwarden/generator-history": ["../../libs/tools/generator/extensions/history/src"], "@bitwarden/generator-legacy": ["../../libs/tools/generator/extensions/legacy/src"], "@bitwarden/generator-navigation": ["../../libs/tools/generator/extensions/navigation/src"], + "@bitwarden/importer-core": ["../../libs/importer/src"], + "@bitwarden/importer-ui": ["../../libs/importer/src/components"], + "@bitwarden/key-management": ["../../libs/key-management/src"], + "@bitwarden/key-management-ui": ["../../libs/key-management-ui/src"], + "@bitwarden/platform": ["../../libs/platform/src"], + "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], + "@bitwarden/tools-card": ["../../libs/tools/card/src"], + "@bitwarden/ui-common": ["../../libs/ui/common/src"], + "@bitwarden/ui-common/setup-jest": ["../../libs/ui/common/src/setup-jest"], "@bitwarden/vault-export-core": [ "../../libs/tools/export/vault-export/vault-export-core/src" ], "@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-ui/src"], - "@bitwarden/importer/core": ["../../libs/importer/src"], - "@bitwarden/importer/ui": ["../../libs/importer/src/components"], - "@bitwarden/key-management": ["../../libs/key-management/src"], - "@bitwarden/key-management/angular": ["../../libs/key-management/src/angular"], - "@bitwarden/platform": ["../../libs/platform/src"], - "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], - "@bitwarden/tools-card": ["../../libs/tools/card/src"], "@bitwarden/vault": ["../../libs/vault/src"], "@bitwarden/web-vault/*": ["src/*"] } }, "angularCompilerOptions": { - "strictTemplates": true, - "preserveWhitespaces": true + "strictTemplates": true }, "files": ["src/polyfills.ts", "src/main.ts", "src/theme.ts"], "include": [ "src/connectors/*.ts", "src/**/*.stories.ts", "src/**/*.spec.ts", - "../../libs/common/src/platform/services/**/*.worker.ts" + "../../libs/common/src/key-management/crypto/services/encrypt.worker.ts" ] } diff --git a/apps/web/webpack.config.js b/apps/web/webpack.config.js index 9373308c112..d172ea95c71 100644 --- a/apps/web/webpack.config.js +++ b/apps/web/webpack.config.js @@ -49,7 +49,13 @@ const moduleRules = [ loader: MiniCssExtractPlugin.loader, }, "css-loader", - "sass-loader", + "resolve-url-loader", + { + loader: "sass-loader", + options: { + sourceMap: true, + }, + }, ], }, { @@ -59,7 +65,13 @@ const moduleRules = [ loader: MiniCssExtractPlugin.loader, }, "css-loader", - "postcss-loader", + "resolve-url-loader", + { + loader: "postcss-loader", + options: { + sourceMap: true, + }, + }, ], }, { @@ -69,6 +81,7 @@ const moduleRules = [ loader: "babel-loader", options: { configFile: "../../babel.config.json", + cacheDirectory: NODE_ENV !== "production", }, }, ], @@ -94,7 +107,7 @@ const plugins = [ new HtmlWebpackPlugin({ template: "./src/connectors/webauthn.html", filename: "webauthn-connector.html", - chunks: ["connectors/webauthn"], + chunks: ["connectors/webauthn", "styles"], }), new HtmlWebpackPlugin({ template: "./src/connectors/webauthn-mobile.html", @@ -104,7 +117,7 @@ const plugins = [ new HtmlWebpackPlugin({ template: "./src/connectors/webauthn-fallback.html", filename: "webauthn-fallback-connector.html", - chunks: ["connectors/webauthn-fallback"], + chunks: ["connectors/webauthn-fallback", "styles"], }), new HtmlWebpackPlugin({ template: "./src/connectors/sso.html", @@ -337,6 +350,20 @@ const webpackConfig = { styles: ["./src/scss/styles.scss", "./src/scss/tailwind.css"], theme_head: "./src/theme.ts", }, + cache: + NODE_ENV === "production" + ? false + : { + type: "filesystem", + allowCollectingMemory: true, + cacheDirectory: path.resolve(__dirname, "../../node_modules/.cache/webpack"), + buildDependencies: { + config: [__filename], + }, + }, + snapshot: { + unmanagedPaths: [path.resolve(__dirname, "../../node_modules/@bitwarden/")], + }, optimization: { splitChunks: { cacheGroups: { @@ -349,6 +376,7 @@ const webpackConfig = { }, }, }, + minimize: NODE_ENV === "production", minimizer: [ new TerserPlugin({ terserOptions: { diff --git a/bitwarden_license/bit-cli/.eslintrc.json b/bitwarden_license/bit-cli/.eslintrc.json deleted file mode 100644 index 10d22388378..00000000000 --- a/bitwarden_license/bit-cli/.eslintrc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "env": { - "node": true - } -} diff --git a/bitwarden_license/bit-cli/src/admin-console/.eslintrc.json b/bitwarden_license/bit-cli/src/admin-console/.eslintrc.json deleted file mode 100644 index 38467187294..00000000000 --- a/bitwarden_license/bit-cli/src/admin-console/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../../../libs/admin-console/.eslintrc.json" -} diff --git a/bitwarden_license/bit-cli/src/admin-console/device-approval/approve-all.command.ts b/bitwarden_license/bit-cli/src/admin-console/device-approval/approve-all.command.ts index db63a3c1c68..dc78ebf7cd0 100644 --- a/bitwarden_license/bit-cli/src/admin-console/device-approval/approve-all.command.ts +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/approve-all.command.ts @@ -1,11 +1,13 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { OrganizationAuthRequestService } from "@bitwarden/bit-common/admin-console/auth-requests"; import { Response } from "@bitwarden/cli/models/response"; import { MessageResponse } from "@bitwarden/cli/models/response/message.response"; -import { OrganizationService } from "@bitwarden/common/admin-console/services/organization/organization.service"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { ServiceContainer } from "../../service-container"; @@ -14,6 +16,7 @@ export class ApproveAllCommand { constructor( private organizationAuthRequestService: OrganizationAuthRequestService, private organizationService: OrganizationService, + private accountService: AccountService, ) {} async run(organizationId: string): Promise { @@ -25,7 +28,13 @@ export class ApproveAllCommand { return Response.badRequest("`" + organizationId + "` is not a GUID."); } - const organization = await firstValueFrom(this.organizationService.get$(organizationId)); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + + const organization = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(map((organizations) => organizations.find((o) => o.id === organizationId))), + ); if (!organization?.canManageUsersPassword) { return Response.error( "You do not have permission to approve pending device authorization requests.", @@ -58,6 +67,7 @@ export class ApproveAllCommand { return new ApproveAllCommand( serviceContainer.organizationAuthRequestService, serviceContainer.organizationService, + serviceContainer.accountService, ); } } diff --git a/bitwarden_license/bit-cli/src/admin-console/device-approval/approve.command.ts b/bitwarden_license/bit-cli/src/admin-console/device-approval/approve.command.ts index dc6a6d8e906..27597fd1a85 100644 --- a/bitwarden_license/bit-cli/src/admin-console/device-approval/approve.command.ts +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/approve.command.ts @@ -1,16 +1,19 @@ -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; +import { OrganizationAuthRequestService } from "@bitwarden/bit-common/admin-console/auth-requests"; import { Response } from "@bitwarden/cli/models/response"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { DefaultOrganizationService } from "@bitwarden/common/admin-console/services/organization/default-organization.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { OrganizationAuthRequestService } from "../../../../bit-common/src/admin-console/auth-requests"; import { ServiceContainer } from "../../service-container"; export class ApproveCommand { constructor( - private organizationService: OrganizationService, + private organizationService: DefaultOrganizationService, private organizationAuthRequestService: OrganizationAuthRequestService, + private accountService: AccountService, ) {} async run(organizationId: string, id: string): Promise { @@ -30,7 +33,17 @@ export class ApproveCommand { return Response.badRequest("`" + id + "` is not a GUID."); } - const organization = await firstValueFrom(this.organizationService.get$(organizationId)); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + + if (!userId) { + return Response.badRequest("No user found."); + } + + const organization = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(map((organizations) => organizations?.find((o) => o.id === organizationId))), + ); if (!organization?.canManageUsersPassword) { return Response.error( "You do not have permission to approve pending device authorization requests.", @@ -57,6 +70,7 @@ export class ApproveCommand { return new ApproveCommand( serviceContainer.organizationService, serviceContainer.organizationAuthRequestService, + serviceContainer.accountService, ); } } diff --git a/bitwarden_license/bit-cli/src/admin-console/device-approval/deny-all.command.ts b/bitwarden_license/bit-cli/src/admin-console/device-approval/deny-all.command.ts index f5dff801f27..923545f15ef 100644 --- a/bitwarden_license/bit-cli/src/admin-console/device-approval/deny-all.command.ts +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/deny-all.command.ts @@ -1,19 +1,22 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; +import { OrganizationAuthRequestService } from "@bitwarden/bit-common/admin-console/auth-requests"; import { Response } from "@bitwarden/cli/models/response"; import { MessageResponse } from "@bitwarden/cli/models/response/message.response"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { OrganizationAuthRequestService } from "../../../../bit-common/src/admin-console/auth-requests"; import { ServiceContainer } from "../../service-container"; export class DenyAllCommand { constructor( private organizationService: OrganizationService, private organizationAuthRequestService: OrganizationAuthRequestService, + private accountService: AccountService, ) {} async run(organizationId: string): Promise { @@ -25,7 +28,17 @@ export class DenyAllCommand { return Response.badRequest("`" + organizationId + "` is not a GUID."); } - const organization = await firstValueFrom(this.organizationService.get$(organizationId)); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + + if (!userId) { + return Response.badRequest("No user found."); + } + + const organization = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(map((organizations) => organizations.find((o) => o.id === organizationId))), + ); if (!organization?.canManageUsersPassword) { return Response.error( "You do not have permission to approve pending device authorization requests.", @@ -54,6 +67,7 @@ export class DenyAllCommand { return new DenyAllCommand( serviceContainer.organizationService, serviceContainer.organizationAuthRequestService, + serviceContainer.accountService, ); } } diff --git a/bitwarden_license/bit-cli/src/admin-console/device-approval/deny.command.ts b/bitwarden_license/bit-cli/src/admin-console/device-approval/deny.command.ts index 58c1c576433..ac5c285f8d7 100644 --- a/bitwarden_license/bit-cli/src/admin-console/device-approval/deny.command.ts +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/deny.command.ts @@ -1,16 +1,18 @@ -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; +import { OrganizationAuthRequestService } from "@bitwarden/bit-common/admin-console/auth-requests"; import { Response } from "@bitwarden/cli/models/response"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { OrganizationAuthRequestService } from "../../../../bit-common/src/admin-console/auth-requests"; import { ServiceContainer } from "../../service-container"; export class DenyCommand { constructor( private organizationService: OrganizationService, private organizationAuthRequestService: OrganizationAuthRequestService, + private accountServcie: AccountService, ) {} async run(organizationId: string, id: string): Promise { @@ -30,7 +32,17 @@ export class DenyCommand { return Response.badRequest("`" + id + "` is not a GUID."); } - const organization = await firstValueFrom(this.organizationService.get$(organizationId)); + const userId = await firstValueFrom(this.accountServcie.activeAccount$.pipe(map((a) => a?.id))); + + if (!userId) { + return Response.badRequest("No user found."); + } + + const organization = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(map((organizations) => organizations.find((o) => o.id === organizationId))), + ); if (!organization?.canManageUsersPassword) { return Response.error( "You do not have permission to approve pending device authorization requests.", @@ -57,6 +69,7 @@ export class DenyCommand { return new DenyCommand( serviceContainer.organizationService, serviceContainer.organizationAuthRequestService, + serviceContainer.accountService, ); } } diff --git a/bitwarden_license/bit-cli/src/admin-console/device-approval/list.command.ts b/bitwarden_license/bit-cli/src/admin-console/device-approval/list.command.ts index 972be460df7..31a0e748175 100644 --- a/bitwarden_license/bit-cli/src/admin-console/device-approval/list.command.ts +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/list.command.ts @@ -1,9 +1,11 @@ -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { OrganizationAuthRequestService } from "@bitwarden/bit-common/admin-console/auth-requests"; import { Response } from "@bitwarden/cli/models/response"; import { ListResponse } from "@bitwarden/cli/models/response/list.response"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { ServiceContainer } from "../../service-container"; @@ -14,6 +16,7 @@ export class ListCommand { constructor( private organizationAuthRequestService: OrganizationAuthRequestService, private organizationService: OrganizationService, + private accountService: AccountService, ) {} async run(organizationId: string): Promise { @@ -25,7 +28,17 @@ export class ListCommand { return Response.badRequest("`" + organizationId + "` is not a GUID."); } - const organization = await firstValueFrom(this.organizationService.get$(organizationId)); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + + if (!userId) { + return Response.badRequest("No user found."); + } + + const organization = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(map((organizations) => organizations.find((o) => o.id === organizationId))), + ); if (!organization?.canManageUsersPassword) { return Response.error( "You do not have permission to approve pending device authorization requests.", @@ -46,6 +59,7 @@ export class ListCommand { return new ListCommand( serviceContainer.organizationAuthRequestService, serviceContainer.organizationService, + serviceContainer.accountService, ); } } diff --git a/bitwarden_license/bit-cli/tsconfig.json b/bitwarden_license/bit-cli/tsconfig.json index 92a206f44db..4a972b540a7 100644 --- a/bitwarden_license/bit-cli/tsconfig.json +++ b/bitwarden_license/bit-cli/tsconfig.json @@ -18,13 +18,12 @@ "@bitwarden/auth/common": ["../../libs/auth/src/common"], "@bitwarden/auth/angular": ["../../libs/auth/src/angular"], "@bitwarden/common/*": ["../../libs/common/src/*"], - "@bitwarden/importer/core": ["../../libs/importer/src"], + "@bitwarden/importer-core": ["../../libs/importer/src"], "@bitwarden/generator-core": ["../../libs/tools/generator/core/src"], "@bitwarden/generator-legacy": ["../../libs/tools/generator/extensions/legacy/src"], "@bitwarden/generator-history": ["../../libs/tools/generator/extensions/history/src"], "@bitwarden/generator-navigation": ["../../libs/tools/generator/extensions/navigation/src"], "@bitwarden/key-management": ["../../libs/key-management/src"], - "@bitwarden/key-management/angular": ["../../libs/key-management/src/angular"], "@bitwarden/vault-export-core": [ "../../libs/tools/export/vault-export/vault-export-core/src" ], diff --git a/bitwarden_license/bit-common/jest.config.js b/bitwarden_license/bit-common/jest.config.js index a0441b01883..ab31a4c26ca 100644 --- a/bitwarden_license/bit-common/jest.config.js +++ b/bitwarden_license/bit-common/jest.config.js @@ -7,9 +7,17 @@ module.exports = { ...sharedConfig, displayName: "bit-common tests", testEnvironment: "jsdom", - moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { - prefix: "/", - }), + moduleNameMapper: pathsToModuleNameMapper( + { + "@bitwarden/common/spec": ["../../libs/common/spec"], + "@bitwarden/common": ["../../libs/common/src/*"], + "@bitwarden/admin-console/common": ["/libs/admin-console/src/common"], + ...(compilerOptions?.paths ?? {}), + }, + { + prefix: "/", + }, + ), setupFilesAfterEnv: ["/test.setup.ts"], transformIgnorePatterns: ["node_modules/(?!(.*\\.mjs$|@angular|rxjs|@bitwarden))"], moduleFileExtensions: ["ts", "js", "html", "mjs"], diff --git a/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.spec.ts b/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.spec.ts index e893b2dfe8c..448399a8bb0 100644 --- a/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.spec.ts +++ b/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.spec.ts @@ -4,8 +4,8 @@ import { OrganizationUserApiService, OrganizationUserResetPasswordDetailsResponse, } from "@bitwarden/admin-console/common"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { KeyService } from "@bitwarden/key-management"; diff --git a/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.ts b/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.ts index 4c4507e5cb8..025b021f83d 100644 --- a/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.ts +++ b/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.ts @@ -4,7 +4,7 @@ import { OrganizationUserApiService, OrganizationUserResetPasswordDetailsResponse, } from "@bitwarden/admin-console/common"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; diff --git a/bitwarden_license/bit-common/src/admin-console/auth-requests/pending-auth-request.view.ts b/bitwarden_license/bit-common/src/admin-console/auth-requests/pending-auth-request.view.ts index d32d6fcfbc7..b0f65cd3f76 100644 --- a/bitwarden_license/bit-common/src/admin-console/auth-requests/pending-auth-request.view.ts +++ b/bitwarden_license/bit-common/src/admin-console/auth-requests/pending-auth-request.view.ts @@ -13,6 +13,7 @@ export class PendingAuthRequestView implements View { requestDeviceIdentifier: string; requestDeviceType: string; requestIpAddress: string; + requestCountryName: string; creationDate: Date; static fromResponse(response: PendingOrganizationAuthRequestResponse): PendingAuthRequestView { diff --git a/bitwarden_license/bit-common/src/admin-console/auth-requests/pending-organization-auth-request.response.ts b/bitwarden_license/bit-common/src/admin-console/auth-requests/pending-organization-auth-request.response.ts index b4854eea4aa..0f686d17edd 100644 --- a/bitwarden_license/bit-common/src/admin-console/auth-requests/pending-organization-auth-request.response.ts +++ b/bitwarden_license/bit-common/src/admin-console/auth-requests/pending-organization-auth-request.response.ts @@ -9,6 +9,7 @@ export class PendingOrganizationAuthRequestResponse extends BaseResponse { requestDeviceIdentifier: string; requestDeviceType: string; requestIpAddress: string; + requestCountryName: string; creationDate: string; constructor(response: any) { @@ -21,6 +22,7 @@ export class PendingOrganizationAuthRequestResponse extends BaseResponse { this.requestDeviceIdentifier = this.getResponseProperty("RequestDeviceIdentifier"); this.requestDeviceType = this.getResponseProperty("RequestDeviceType"); this.requestIpAddress = this.getResponseProperty("RequestIpAddress"); + this.requestCountryName = this.getResponseProperty("RequestCountryName"); this.creationDate = this.getResponseProperty("CreationDate"); } } diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts index b8d5852088a..723d737d5bd 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts @@ -1,6 +1,9 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { Opaque } from "type-fest"; + +import { OrganizationId } from "@bitwarden/common/types/guid"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { BadgeVariant } from "@bitwarden/components"; @@ -30,6 +33,10 @@ export type ApplicationHealthReportDetail = { atRiskMemberDetails: MemberDetailsFlat[]; }; +export type ApplicationHealthReportDetailWithCriticalFlag = ApplicationHealthReportDetail & { + isMarkedAsCritical: boolean; +}; + /** * Breaks the cipher health info out by uri and passes * along the password health and member info @@ -78,6 +85,7 @@ export type WeakPasswordScore = { * How many times a password has been exposed */ export type ExposedPasswordDetail = { + cipherId: string; exposedXTimes: number; } | null; @@ -90,3 +98,62 @@ export type MemberDetailsFlat = { email: string; cipherId: string; }; + +/** + * Member email with the number of at risk passwords + * At risk member detail that contains the email + * and the count of at risk ciphers + */ +export type AtRiskMemberDetail = { + email: string; + atRiskPasswordCount: number; +}; + +/* + * A list of applications and the count of + * at risk passwords for each application + */ +export type AtRiskApplicationDetail = { + applicationName: string; + atRiskPasswordCount: number; +}; + +export type AppAtRiskMembersDialogParams = { + members: MemberDetailsFlat[]; + applicationName: string; +}; + +/** + * Request to drop a password health report application + * Model is expected by the API endpoint + */ +export interface PasswordHealthReportApplicationDropRequest { + organizationId: OrganizationId; + passwordHealthReportApplicationIds: string[]; +} + +/** + * Response from the API after marking an app as critical + */ +export interface PasswordHealthReportApplicationsResponse { + id: PasswordHealthReportApplicationId; + organizationId: OrganizationId; + uri: string; +} +/* + * Request to save a password health report application + * Model is expected by the API endpoint + */ +export interface PasswordHealthReportApplicationsRequest { + organizationId: OrganizationId; + url: string; +} + +export enum DrawerType { + None = 0, + AppAtRiskMembers = 1, + OrgAtRiskMembers = 2, + OrgAtRiskApps = 3, +} + +export type PasswordHealthReportApplicationId = Opaque; diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps-api.service.spec.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps-api.service.spec.ts new file mode 100644 index 00000000000..5ed88c4cac9 --- /dev/null +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps-api.service.spec.ts @@ -0,0 +1,101 @@ +import { mock } from "jest-mock-extended"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationId } from "@bitwarden/common/types/guid"; + +import { + PasswordHealthReportApplicationDropRequest, + PasswordHealthReportApplicationId, + PasswordHealthReportApplicationsRequest, + PasswordHealthReportApplicationsResponse, +} from "../models/password-health"; + +import { CriticalAppsApiService } from "./critical-apps-api.service"; + +describe("CriticalAppsApiService", () => { + let service: CriticalAppsApiService; + const apiService = mock(); + + beforeEach(() => { + service = new CriticalAppsApiService(apiService); + }); + + it("should be created", () => { + expect(service).toBeTruthy(); + }); + + it("should call apiService.send with correct parameters for SaveCriticalApps", (done) => { + const requests: PasswordHealthReportApplicationsRequest[] = [ + { organizationId: "org1" as OrganizationId, url: "test one" }, + { organizationId: "org1" as OrganizationId, url: "test two" }, + ]; + const response: PasswordHealthReportApplicationsResponse[] = [ + { + id: "1" as PasswordHealthReportApplicationId, + organizationId: "org1" as OrganizationId, + uri: "test one", + }, + { + id: "2" as PasswordHealthReportApplicationId, + organizationId: "org1" as OrganizationId, + uri: "test two", + }, + ]; + + apiService.send.mockReturnValue(Promise.resolve(response)); + + service.saveCriticalApps(requests).subscribe((result) => { + expect(result).toEqual(response); + expect(apiService.send).toHaveBeenCalledWith( + "POST", + "/reports/password-health-report-applications/", + requests, + true, + true, + ); + done(); + }); + }); + + it("should call apiService.send with correct parameters for GetCriticalApps", (done) => { + const orgId: OrganizationId = "org1" as OrganizationId; + const response: PasswordHealthReportApplicationsResponse[] = [ + { id: "1" as PasswordHealthReportApplicationId, organizationId: orgId, uri: "test one" }, + { id: "2" as PasswordHealthReportApplicationId, organizationId: orgId, uri: "test two" }, + ]; + + apiService.send.mockReturnValue(Promise.resolve(response)); + + service.getCriticalApps(orgId).subscribe((result) => { + expect(result).toEqual(response); + expect(apiService.send).toHaveBeenCalledWith( + "GET", + `/reports/password-health-report-applications/${orgId.toString()}`, + null, + true, + true, + ); + done(); + }); + }); + + it("should call apiService.send with correct parameters for DropCriticalApp", (done) => { + const request: PasswordHealthReportApplicationDropRequest = { + organizationId: "org1" as OrganizationId, + passwordHealthReportApplicationIds: ["123"], + }; + + apiService.send.mockReturnValue(Promise.resolve()); + + service.dropCriticalApp(request).subscribe(() => { + expect(apiService.send).toHaveBeenCalledWith( + "DELETE", + "/reports/password-health-report-application/", + request, + true, + true, + ); + done(); + }); + }); +}); diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps-api.service.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps-api.service.ts new file mode 100644 index 00000000000..c02a3686dfd --- /dev/null +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps-api.service.ts @@ -0,0 +1,52 @@ +import { from, Observable } from "rxjs"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationId } from "@bitwarden/common/types/guid"; + +import { + PasswordHealthReportApplicationDropRequest, + PasswordHealthReportApplicationsRequest, + PasswordHealthReportApplicationsResponse, +} from "../models/password-health"; + +export class CriticalAppsApiService { + constructor(private apiService: ApiService) {} + + saveCriticalApps( + requests: PasswordHealthReportApplicationsRequest[], + ): Observable { + const dbResponse = this.apiService.send( + "POST", + "/reports/password-health-report-applications/", + requests, + true, + true, + ); + + return from(dbResponse as Promise); + } + + getCriticalApps(orgId: OrganizationId): Observable { + const dbResponse = this.apiService.send( + "GET", + `/reports/password-health-report-applications/${orgId.toString()}`, + null, + true, + true, + ); + + return from(dbResponse as Promise); + } + + dropCriticalApp(request: PasswordHealthReportApplicationDropRequest): Observable { + const dbResponse = this.apiService.send( + "DELETE", + "/reports/password-health-report-application/", + request, + true, + true, + ); + + return from(dbResponse as Promise); + } +} diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps.service.spec.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps.service.spec.ts new file mode 100644 index 00000000000..d2d48edf869 --- /dev/null +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps.service.spec.ts @@ -0,0 +1,197 @@ +import { randomUUID } from "crypto"; + +import { fakeAsync, flush } from "@angular/core/testing"; +import { mock } from "jest-mock-extended"; +import { of } from "rxjs"; + +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { CsprngArray } from "@bitwarden/common/types/csprng"; +import { OrganizationId } from "@bitwarden/common/types/guid"; +import { OrgKey } from "@bitwarden/common/types/key"; +import { KeyService } from "@bitwarden/key-management"; + +import { + PasswordHealthReportApplicationId, + PasswordHealthReportApplicationsRequest, + PasswordHealthReportApplicationsResponse, +} from "../models/password-health"; + +import { CriticalAppsApiService } from "./critical-apps-api.service"; +import { CriticalAppsService } from "./critical-apps.service"; + +describe("CriticalAppsService", () => { + let service: CriticalAppsService; + const keyService = mock(); + const encryptService = mock(); + const criticalAppsApiService = mock({ + saveCriticalApps: jest.fn(), + getCriticalApps: jest.fn(), + }); + + beforeEach(() => { + service = new CriticalAppsService(keyService, encryptService, criticalAppsApiService); + + // reset mocks + jest.resetAllMocks(); + + const mockRandomBytes = new Uint8Array(64) as CsprngArray; + const mockOrgKey = new SymmetricCryptoKey(mockRandomBytes) as OrgKey; + keyService.getOrgKey.mockResolvedValue(mockOrgKey); + }); + + it("should be created", () => { + expect(service).toBeTruthy(); + }); + + it("should set critical apps", async () => { + // arrange + const criticalApps = ["https://example.com", "https://example.org"]; + + const request = [ + { organizationId: "org1", url: "encryptedUrlName" }, + { organizationId: "org1", url: "encryptedUrlName" }, + ] as PasswordHealthReportApplicationsRequest[]; + + const response = [ + { id: "id1", organizationId: "org1", uri: "https://example.com" }, + { id: "id2", organizationId: "org1", uri: "https://example.org" }, + ] as PasswordHealthReportApplicationsResponse[]; + + encryptService.encrypt.mockResolvedValue(new EncString("encryptedUrlName")); + criticalAppsApiService.saveCriticalApps.mockReturnValue(of(response)); + + // act + await service.setCriticalApps("org1", criticalApps); + + // expectations + expect(keyService.getOrgKey).toHaveBeenCalledWith("org1"); + expect(encryptService.encrypt).toHaveBeenCalledTimes(2); + expect(criticalAppsApiService.saveCriticalApps).toHaveBeenCalledWith(request); + }); + + it("should exclude records that already exist", async () => { + // arrange + // one record already exists + service.setAppsInListForOrg([ + { + id: randomUUID() as PasswordHealthReportApplicationId, + organizationId: "org1" as OrganizationId, + uri: "https://example.com", + }, + ]); + + // two records are selected - one already in the database + const selectedUrls = ["https://example.com", "https://example.org"]; + + // expect only one record to be sent to the server + const request = [ + { organizationId: "org1", url: "encryptedUrlName" }, + ] as PasswordHealthReportApplicationsRequest[]; + + // mocked response + const response = [ + { id: "id1", organizationId: "org1", uri: "test" }, + ] as PasswordHealthReportApplicationsResponse[]; + + encryptService.encrypt.mockResolvedValue(new EncString("encryptedUrlName")); + criticalAppsApiService.saveCriticalApps.mockReturnValue(of(response)); + + // act + await service.setCriticalApps("org1", selectedUrls); + + // expectations + expect(keyService.getOrgKey).toHaveBeenCalledWith("org1"); + expect(encryptService.encrypt).toHaveBeenCalledTimes(1); + expect(criticalAppsApiService.saveCriticalApps).toHaveBeenCalledWith(request); + }); + + it("should get critical apps", fakeAsync(() => { + const orgId = "org1" as OrganizationId; + const response = [ + { id: "id1", organizationId: "org1", uri: "https://example.com" }, + { id: "id2", organizationId: "org1", uri: "https://example.org" }, + ] as PasswordHealthReportApplicationsResponse[]; + + encryptService.decryptToUtf8.mockResolvedValue("https://example.com"); + criticalAppsApiService.getCriticalApps.mockReturnValue(of(response)); + + const mockRandomBytes = new Uint8Array(64) as CsprngArray; + const mockOrgKey = new SymmetricCryptoKey(mockRandomBytes) as OrgKey; + keyService.getOrgKey.mockResolvedValue(mockOrgKey); + + service.setOrganizationId(orgId as OrganizationId); + flush(); + + expect(keyService.getOrgKey).toHaveBeenCalledWith(orgId.toString()); + expect(encryptService.decryptToUtf8).toHaveBeenCalledTimes(2); + expect(criticalAppsApiService.getCriticalApps).toHaveBeenCalledWith(orgId); + })); + + it("should get by org id", () => { + const orgId = "org1" as OrganizationId; + const response = [ + { id: "id1", organizationId: "org1", uri: "https://example.com" }, + { id: "id2", organizationId: "org1", uri: "https://example.org" }, + { id: "id3", organizationId: "org2", uri: "https://example.org" }, + { id: "id4", organizationId: "org2", uri: "https://example.org" }, + ] as PasswordHealthReportApplicationsResponse[]; + + service.setAppsInListForOrg(response); + + service.getAppsListForOrg(orgId as OrganizationId).subscribe((res) => { + expect(res).toHaveLength(2); + }); + }); + + it("should drop a critical app", async () => { + // arrange + const orgId = "org1" as OrganizationId; + const selectedUrl = "https://example.com"; + + const initialList = [ + { id: "id1", organizationId: "org1", uri: "https://example.com" }, + { id: "id2", organizationId: "org1", uri: "https://example.org" }, + ] as PasswordHealthReportApplicationsResponse[]; + + service.setAppsInListForOrg(initialList); + + // act + await service.dropCriticalApp(orgId, selectedUrl); + + // expectations + expect(criticalAppsApiService.dropCriticalApp).toHaveBeenCalledWith({ + organizationId: orgId, + passwordHealthReportApplicationIds: ["id1"], + }); + expect(service.getAppsListForOrg(orgId)).toBeTruthy(); + service.getAppsListForOrg(orgId).subscribe((res) => { + expect(res).toHaveLength(1); + expect(res[0].uri).toBe("https://example.org"); + }); + }); + + it("should not drop a critical app if it does not exist", async () => { + // arrange + const orgId = "org1" as OrganizationId; + const selectedUrl = "https://nonexistent.com"; + + const initialList = [ + { id: "id1", organizationId: "org1", uri: "https://example.com" }, + { id: "id2", organizationId: "org1", uri: "https://example.org" }, + ] as PasswordHealthReportApplicationsResponse[]; + + service.setAppsInListForOrg(initialList); + + // act + await service.dropCriticalApp(orgId, selectedUrl); + + // expectations + expect(criticalAppsApiService.dropCriticalApp).not.toHaveBeenCalled(); + expect(service.getAppsListForOrg(orgId)).toBeTruthy(); + service.getAppsListForOrg(orgId).subscribe((res) => { + expect(res).toHaveLength(2); + }); + }); +}); diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps.service.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps.service.ts new file mode 100644 index 00000000000..bc8edc17360 --- /dev/null +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps.service.ts @@ -0,0 +1,176 @@ +import { + BehaviorSubject, + first, + firstValueFrom, + forkJoin, + from, + map, + Observable, + of, + Subject, + switchMap, + takeUntil, + zip, +} from "rxjs"; + +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { OrganizationId } from "@bitwarden/common/types/guid"; +import { OrgKey } from "@bitwarden/common/types/key"; +import { KeyService } from "@bitwarden/key-management"; + +import { + PasswordHealthReportApplicationsRequest, + PasswordHealthReportApplicationsResponse, +} from "../models/password-health"; + +import { CriticalAppsApiService } from "./critical-apps-api.service"; + +/* Retrieves and decrypts critical apps for a given organization + * Encrypts and saves data for a given organization + */ +export class CriticalAppsService { + private orgId = new BehaviorSubject(null); + private criticalAppsList = new BehaviorSubject([]); + private teardown = new Subject(); + + private fetchOrg$ = this.orgId + .pipe( + switchMap((orgId) => this.retrieveCriticalApps(orgId)), + takeUntil(this.teardown), + ) + .subscribe((apps) => this.criticalAppsList.next(apps)); + + constructor( + private keyService: KeyService, + private encryptService: EncryptService, + private criticalAppsApiService: CriticalAppsApiService, + ) {} + + // Get a list of critical apps for a given organization + getAppsListForOrg(orgId: string): Observable { + return this.criticalAppsList + .asObservable() + .pipe(map((apps) => apps.filter((app) => app.organizationId === orgId))); + } + + // Reset the critical apps list + setAppsInListForOrg(apps: PasswordHealthReportApplicationsResponse[]) { + this.criticalAppsList.next(apps); + } + + // Save the selected critical apps for a given organization + async setCriticalApps(orgId: string, selectedUrls: string[]) { + const key = await this.keyService.getOrgKey(orgId); + if (key == null) { + throw new Error("Organization key not found"); + } + + // only save records that are not already in the database + const newEntries = await this.filterNewEntries(orgId as OrganizationId, selectedUrls); + const criticalAppsRequests = await this.encryptNewEntries( + orgId as OrganizationId, + key, + newEntries, + ); + + const dbResponse = await firstValueFrom( + this.criticalAppsApiService.saveCriticalApps(criticalAppsRequests), + ); + + // add the new entries to the criticalAppsList + const updatedList = [...this.criticalAppsList.value]; + for (const responseItem of dbResponse) { + const decryptedUrl = await this.encryptService.decryptToUtf8( + new EncString(responseItem.uri), + key, + ); + if (!updatedList.some((f) => f.uri === decryptedUrl)) { + updatedList.push({ + id: responseItem.id, + organizationId: responseItem.organizationId, + uri: decryptedUrl, + } as PasswordHealthReportApplicationsResponse); + } + } + this.criticalAppsList.next(updatedList); + } + + // Get the critical apps for a given organization + setOrganizationId(orgId: OrganizationId) { + this.orgId.next(orgId); + } + + // Drop a critical app for a given organization + // Only one app may be dropped at a time + async dropCriticalApp(orgId: OrganizationId, selectedUrl: string) { + const app = this.criticalAppsList.value.find( + (f) => f.organizationId === orgId && f.uri === selectedUrl, + ); + + if (!app) { + return; + } + + await this.criticalAppsApiService.dropCriticalApp({ + organizationId: app.organizationId, + passwordHealthReportApplicationIds: [app.id], + }); + + this.criticalAppsList.next(this.criticalAppsList.value.filter((f) => f.uri !== selectedUrl)); + } + + private retrieveCriticalApps( + orgId: OrganizationId | null, + ): Observable { + if (orgId === null) { + return of([]); + } + + const result$ = zip( + this.criticalAppsApiService.getCriticalApps(orgId), + from(this.keyService.getOrgKey(orgId)), + ).pipe( + switchMap(([response, key]) => { + if (key == null) { + throw new Error("Organization key not found"); + } + + const results = response.map(async (r: PasswordHealthReportApplicationsResponse) => { + const encrypted = new EncString(r.uri); + const uri = await this.encryptService.decryptToUtf8(encrypted, key); + return { id: r.id, organizationId: r.organizationId, uri: uri }; + }); + return forkJoin(results); + }), + first(), + ); + + return result$ as Observable; + } + + private async filterNewEntries(orgId: OrganizationId, selectedUrls: string[]): Promise { + return await firstValueFrom(this.criticalAppsList).then((criticalApps) => { + const criticalAppsUri = criticalApps + .filter((f) => f.organizationId === orgId) + .map((f) => f.uri); + return selectedUrls.filter((url) => !criticalAppsUri.includes(url)); + }); + } + + private async encryptNewEntries( + orgId: OrganizationId, + key: OrgKey, + newEntries: string[], + ): Promise { + const criticalAppsPromises = newEntries.map(async (url) => { + const encryptedUrlName = await this.encryptService.encrypt(url, key); + return { + organizationId: orgId, + url: encryptedUrlName?.encryptedString?.toString() ?? "", + } as PasswordHealthReportApplicationsRequest; + }); + + return await Promise.all(criticalAppsPromises); + } +} diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/index.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/index.ts index a8e62437b9d..f547df31f41 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/index.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/index.ts @@ -1,4 +1,6 @@ export * from "./member-cipher-details-api.service"; export * from "./password-health.service"; +export * from "./critical-apps.service"; +export * from "./critical-apps-api.service"; export * from "./risk-insights-report.service"; export * from "./risk-insights-data.service"; diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-data.service.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-data.service.ts index 42bab69fca4..386c6fd6865 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-data.service.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-data.service.ts @@ -1,10 +1,15 @@ import { BehaviorSubject } from "rxjs"; import { finalize } from "rxjs/operators"; -import { ApplicationHealthReportDetail } from "../models/password-health"; +import { + AppAtRiskMembersDialogParams, + ApplicationHealthReportDetail, + AtRiskApplicationDetail, + AtRiskMemberDetail, + DrawerType, +} from "../models/password-health"; import { RiskInsightsReportService } from "./risk-insights-report.service"; - export class RiskInsightsDataService { private applicationsSubject = new BehaviorSubject(null); @@ -22,6 +27,13 @@ export class RiskInsightsDataService { private dataLastUpdatedSubject = new BehaviorSubject(null); dataLastUpdated$ = this.dataLastUpdatedSubject.asObservable(); + openDrawer = false; + drawerInvokerId: string = ""; + activeDrawerType: DrawerType = DrawerType.None; + atRiskMemberDetails: AtRiskMemberDetail[] = []; + appAtRiskMembers: AppAtRiskMembersDialogParams | null = null; + atRiskAppDetails: AtRiskApplicationDetail[] | null = null; + constructor(private reportService: RiskInsightsReportService) {} /** @@ -57,4 +69,57 @@ export class RiskInsightsDataService { refreshApplicationsReport(organizationId: string): void { this.fetchApplicationsReport(organizationId, true); } + + isActiveDrawerType = (drawerType: DrawerType): boolean => { + return this.activeDrawerType === drawerType; + }; + + setDrawerForOrgAtRiskMembers = ( + atRiskMemberDetails: AtRiskMemberDetail[], + invokerId: string = "", + ): void => { + this.resetDrawer(DrawerType.OrgAtRiskMembers); + this.activeDrawerType = DrawerType.OrgAtRiskMembers; + this.drawerInvokerId = invokerId; + this.atRiskMemberDetails = atRiskMemberDetails; + this.openDrawer = !this.openDrawer; + }; + + setDrawerForAppAtRiskMembers = ( + atRiskMembersDialogParams: AppAtRiskMembersDialogParams, + invokerId: string = "", + ): void => { + this.resetDrawer(DrawerType.None); + this.activeDrawerType = DrawerType.AppAtRiskMembers; + this.drawerInvokerId = invokerId; + this.appAtRiskMembers = atRiskMembersDialogParams; + this.openDrawer = !this.openDrawer; + }; + + setDrawerForOrgAtRiskApps = ( + atRiskApps: AtRiskApplicationDetail[], + invokerId: string = "", + ): void => { + this.resetDrawer(DrawerType.OrgAtRiskApps); + this.activeDrawerType = DrawerType.OrgAtRiskApps; + this.drawerInvokerId = invokerId; + this.atRiskAppDetails = atRiskApps; + this.openDrawer = !this.openDrawer; + }; + + closeDrawer = (): void => { + this.resetDrawer(DrawerType.None); + }; + + private resetDrawer = (drawerType: DrawerType): void => { + if (this.activeDrawerType !== drawerType) { + this.openDrawer = false; + } + + this.activeDrawerType = DrawerType.None; + this.atRiskMemberDetails = []; + this.appAtRiskMembers = null; + this.atRiskAppDetails = null; + this.drawerInvokerId = ""; + }; } diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.ts index d0530dfd821..027760f678c 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.ts @@ -12,6 +12,8 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { ApplicationHealthReportDetail, ApplicationHealthReportSummary, + AtRiskMemberDetail, + AtRiskApplicationDetail, CipherHealthReportDetail, CipherHealthReportUriDetail, ExposedPasswordDetail, @@ -89,6 +91,54 @@ export class RiskInsightsReportService { return results$; } + /** + * Generates a list of members with at-risk passwords along with the number of at-risk passwords. + */ + generateAtRiskMemberList( + cipherHealthReportDetails: ApplicationHealthReportDetail[], + ): AtRiskMemberDetail[] { + const memberRiskMap = new Map(); + + cipherHealthReportDetails.forEach((app) => { + app.atRiskMemberDetails.forEach((member) => { + if (memberRiskMap.has(member.email)) { + memberRiskMap.set(member.email, memberRiskMap.get(member.email) + 1); + } else { + memberRiskMap.set(member.email, 1); + } + }); + }); + + return Array.from(memberRiskMap.entries()).map(([email, atRiskPasswordCount]) => ({ + email, + atRiskPasswordCount, + })); + } + + generateAtRiskApplicationList( + cipherHealthReportDetails: ApplicationHealthReportDetail[], + ): AtRiskApplicationDetail[] { + const appsRiskMap = new Map(); + + cipherHealthReportDetails + .filter((app) => app.atRiskPasswordCount > 0) + .forEach((app) => { + if (appsRiskMap.has(app.applicationName)) { + appsRiskMap.set( + app.applicationName, + appsRiskMap.get(app.applicationName) + app.atRiskPasswordCount, + ); + } else { + appsRiskMap.set(app.applicationName, app.atRiskPasswordCount); + } + }); + + return Array.from(appsRiskMap.entries()).map(([applicationName, atRiskPasswordCount]) => ({ + applicationName, + atRiskPasswordCount, + })); + } + /** * Gets the summary from the application health report. Returns total members and applications as well * as the total at risk members and at risk applications @@ -125,6 +175,7 @@ export class RiskInsightsReportService { ): Promise { const cipherHealthReports: CipherHealthReportDetail[] = []; const passwordUseMap = new Map(); + const exposedDetails = await this.findExposedPasswords(ciphers); for (const cipher of ciphers) { if (this.validateCipher(cipher)) { const weakPassword = this.findWeakPassword(cipher); @@ -139,7 +190,7 @@ export class RiskInsightsReportService { passwordUseMap.set(cipher.login.password, 1); } - const exposedPassword = await this.findExposedPassword(cipher); + const exposedPassword = exposedDetails.find((x) => x.cipherId === cipher.id); // Get the cipher members const cipherMembers = memberDetails.filter((x) => x.cipherId === cipher.id); @@ -205,13 +256,29 @@ export class RiskInsightsReportService { return appReports; } - private async findExposedPassword(cipher: CipherView): Promise { - const exposedCount = await this.auditService.passwordLeaked(cipher.login.password); - if (exposedCount > 0) { - const exposedDetail = { exposedXTimes: exposedCount } as ExposedPasswordDetail; - return exposedDetail; - } - return null; + private async findExposedPasswords(ciphers: CipherView[]): Promise { + const exposedDetails: ExposedPasswordDetail[] = []; + const promises: Promise[] = []; + + ciphers.forEach((ciph) => { + if (this.validateCipher(ciph)) { + const promise = this.auditService + .passwordLeaked(ciph.login.password) + .then((exposedCount) => { + if (exposedCount > 0) { + const detail = { + exposedXTimes: exposedCount, + cipherId: ciph.id, + } as ExposedPasswordDetail; + exposedDetails.push(detail); + } + }); + promises.push(promise); + } + }); + await Promise.all(promises); + + return exposedDetails; } private findWeakPassword(cipher: CipherView): WeakPasswordDetail { diff --git a/bitwarden_license/bit-common/test.setup.ts b/bitwarden_license/bit-common/test.setup.ts index a702c633967..3f9ef28ad00 100644 --- a/bitwarden_license/bit-common/test.setup.ts +++ b/bitwarden_license/bit-common/test.setup.ts @@ -1 +1 @@ -import "jest-preset-angular/setup-jest"; +import "@bitwarden/ui-common/setup-jest"; diff --git a/bitwarden_license/bit-common/tsconfig.json b/bitwarden_license/bit-common/tsconfig.json index 7791840950b..641b0ac6aa9 100644 --- a/bitwarden_license/bit-common/tsconfig.json +++ b/bitwarden_license/bit-common/tsconfig.json @@ -9,6 +9,7 @@ "@bitwarden/angular/*": ["../../libs/angular/src/*"], "@bitwarden/auth": ["../../libs/auth/src"], "@bitwarden/billing": ["../../libs/billing/src"], + "@bitwarden/bit-common/*": ["../bit-common/src/*"], "@bitwarden/common/*": ["../../libs/common/src/*"], "@bitwarden/components": ["../../libs/components/src"], "@bitwarden/generator-components": ["../../libs/tools/generator/components/src"], @@ -16,18 +17,18 @@ "@bitwarden/generator-history": ["../../libs/tools/generator/extensions/history/src"], "@bitwarden/generator-legacy": ["../../libs/tools/generator/extensions/legacy/src"], "@bitwarden/generator-navigation": ["../../libs/tools/generator/extensions/navigation/src"], + "@bitwarden/key-management": ["../../libs/key-management/src"], + "@bitwarden/platform": ["../../libs/platform/src"], + "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], + "@bitwarden/tools-card": ["../../libs/tools/card/src"], + "@bitwarden/ui-common": ["../../libs/ui/common/src"], + "@bitwarden/ui-common/setup-jest": ["../../libs/ui/common/src/setup-jest"], "@bitwarden/vault-export-core": [ "../../libs/tools/export/vault-export/vault-export-core/src" ], "@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-core/src"], - "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], - "@bitwarden/tools-card": ["../../libs/tools/card/src"], - "@bitwarden/key-management": ["../../libs/key-management/src"], - "@bitwarden/key-management/angular": ["../../libs/key-management/src/angular"], - "@bitwarden/platform": ["../../libs/platform/src"], "@bitwarden/vault": ["../../libs/vault/src"], - "@bitwarden/web-vault/*": ["../../apps/web/src/*"], - "@bitwarden/bit-common/*": ["../bit-common/src/*"] + "@bitwarden/web-vault/*": ["../../apps/web/src/*"] } } } diff --git a/bitwarden_license/bit-web/jest.config.js b/bitwarden_license/bit-web/jest.config.js index 17b7139049a..9c9c61b2402 100644 --- a/bitwarden_license/bit-web/jest.config.js +++ b/bitwarden_license/bit-web/jest.config.js @@ -9,7 +9,15 @@ module.exports = { ...sharedConfig, preset: "jest-preset-angular", setupFilesAfterEnv: ["../../apps/web/test.setup.ts"], - moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { - prefix: "/", - }), + moduleNameMapper: pathsToModuleNameMapper( + { + "@bitwarden/common/spec": ["../../libs/common/spec"], + "@bitwarden/common": ["../../libs/common/src/*"], + "@bitwarden/admin-console/common": ["/libs/admin-console/src/common"], + ...(compilerOptions?.paths ?? {}), + }, + { + prefix: "/", + }, + ), }; diff --git a/bitwarden_license/bit-web/src/app/admin-console/.eslintrc.json b/bitwarden_license/bit-web/src/app/admin-console/.eslintrc.json deleted file mode 100644 index d55df3899e7..00000000000 --- a/bitwarden_license/bit-web/src/app/admin-console/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../../../../libs/admin-console/.eslintrc.json" -} diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.html b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.html index 7723324781b..cafd0744a8f 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.html @@ -1,7 +1,7 @@ diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts index 760877ff8bc..744cf2c4674 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts @@ -10,8 +10,8 @@ import { OrganizationAuthRequestApiService } from "@bitwarden/bit-common/admin-c import { OrganizationAuthRequestService } from "@bitwarden/bit-common/admin-console/auth-requests/organization-auth-request.service"; import { PendingAuthRequestView } from "@bitwarden/bit-common/admin-console/auth-requests/pending-auth-request.view"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -98,6 +98,8 @@ export class DeviceApprovalsComponent implements OnInit, OnDestroy { title: null, message: this.i18nService.t("loginRequestApproved"), }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { this.toastService.showToast({ variant: "error", diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.html b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.html index 7226c957598..7df5953c56a 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.html @@ -7,25 +7,27 @@ {{ "newDomain" | i18n }} + {{ ((accountDeprovisioningEnabled$ | async) ? "claimDomain" : "verifyDomain") | i18n }} + + + + {{ data.orgDomain.domainName }} + + + {{ - ((accountDeprovisioningEnabled$ | async) ? "claimDomain" : "verifyDomain") | i18n - }} - - {{ - data.orgDomain.domainName - }} - - {{ - ((accountDeprovisioningEnabled$ | async) - ? "domainStatusUnderVerification" - : "domainStatusUnverified" - ) | i18n - }} - {{ - ((accountDeprovisioningEnabled$ | async) ? "domainStatusClaimed" : "domainStatusVerified") - | i18n - }} + ((accountDeprovisioningEnabled$ | async) + ? "domainStatusUnderVerification" + : "domainStatusUnverified" + ) | i18n + }} + + + {{ + ((accountDeprovisioningEnabled$ | async) ? "domainStatusClaimed" : "domainStatusVerified") + | i18n + }} +
    diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-verification.component.html b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-verification.component.html index ac83491538e..adf9fcd2dcf 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-verification.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-verification.component.html @@ -4,7 +4,11 @@ -

    +

    {{ "claimedDomainsDesc" | i18n }} @@ -70,7 +74,7 @@ {{ orgDomain.lastCheckedDate | date: "medium" }} - +

    + + +
    diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.ts index 80ed3025306..f830b149db4 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.ts @@ -1,26 +1,37 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, OnDestroy, OnInit } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; +import { CommonModule } from "@angular/common"; +import { Component } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { FormControl } from "@angular/forms"; +import { ActivatedRoute, Router, RouterModule } from "@angular/router"; import { firstValueFrom, from, map } from "rxjs"; -import { switchMap, takeUntil } from "rxjs/operators"; +import { debounceTime, first, switchMap } from "rxjs/operators"; +import { JslibModule } from "@bitwarden/angular/jslib.module"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { ProviderStatusType, ProviderUserType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { PlanType } from "@bitwarden/common/billing/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; -import { DialogService, ToastService } from "@bitwarden/components"; +import { + AvatarModule, + DialogService, + TableDataSource, + TableModule, + ToastService, +} from "@bitwarden/components"; +import { SharedOrganizationModule } from "@bitwarden/web-vault/app/admin-console/organizations/shared"; +import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module"; import { WebProviderService } from "../services/web-provider.service"; import { AddOrganizationComponent } from "./add-organization.component"; -import { BaseClientsComponent } from "./base-clients.component"; const DisallowedPlanTypes = [ PlanType.Free, @@ -32,13 +43,26 @@ const DisallowedPlanTypes = [ @Component({ templateUrl: "clients.component.html", + standalone: true, + imports: [ + SharedOrganizationModule, + HeaderModule, + CommonModule, + JslibModule, + AvatarModule, + RouterModule, + TableModule, + ], }) -export class ClientsComponent extends BaseClientsComponent implements OnInit, OnDestroy { - providerId: string; - addableOrganizations: Organization[]; +export class ClientsComponent { + providerId: string = ""; + addableOrganizations: Organization[] = []; loading = true; manageOrganizations = false; showAddExisting = false; + dataSource: TableDataSource = + new TableDataSource(); + protected searchControl = new FormControl("", { nonNullable: true }); constructor( private router: Router, @@ -46,28 +70,20 @@ export class ClientsComponent extends BaseClientsComponent implements OnInit, On private apiService: ApiService, private organizationService: OrganizationService, private organizationApiService: OrganizationApiServiceAbstraction, - activatedRoute: ActivatedRoute, - dialogService: DialogService, - i18nService: I18nService, - searchService: SearchService, - toastService: ToastService, - validationService: ValidationService, - webProviderService: WebProviderService, + private accountService: AccountService, + private activatedRoute: ActivatedRoute, + private dialogService: DialogService, + private i18nService: I18nService, + private toastService: ToastService, + private validationService: ValidationService, + private webProviderService: WebProviderService, ) { - super( - activatedRoute, - dialogService, - i18nService, - searchService, - toastService, - validationService, - webProviderService, - ); - } + this.activatedRoute.queryParams.pipe(first(), takeUntilDestroyed()).subscribe((queryParams) => { + this.searchControl.setValue(queryParams.search); + }); - ngOnInit() { - this.activatedRoute.parent.params - .pipe( + this.activatedRoute.parent?.params + ?.pipe( switchMap((params) => { this.providerId = params.providerId; return this.providerService.get$(this.providerId).pipe( @@ -85,23 +101,52 @@ export class ClientsComponent extends BaseClientsComponent implements OnInit, On }), ); }), - takeUntil(this.destroy$), + takeUntilDestroyed(), ) .subscribe(); + + this.searchControl.valueChanges + .pipe(debounceTime(200), takeUntilDestroyed()) + .subscribe((searchText) => { + this.dataSource.filter = (data) => + data.organizationName.toLowerCase().indexOf(searchText.toLowerCase()) > -1; + }); } - ngOnDestroy() { - super.ngOnDestroy(); + async remove(organization: ProviderOrganizationOrganizationDetailsResponse) { + const confirmed = await this.dialogService.openSimpleDialog({ + title: organization.organizationName, + content: { key: "detachOrganizationConfirmation" }, + type: "warning", + }); + + if (!confirmed) { + return; + } + + try { + await this.webProviderService.detachOrganization(this.providerId, organization.id); + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("detachedOrganization", organization.organizationName), + }); + await this.load(); + } catch (e) { + this.validationService.showError(e); + } } async load() { const response = await this.apiService.getProviderClients(this.providerId); - this.clients = response.data != null && response.data.length > 0 ? response.data : []; + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + const clients = response.data != null && response.data.length > 0 ? response.data : []; + this.dataSource.data = clients; this.manageOrganizations = (await this.providerService.get(this.providerId)).type === ProviderUserType.ProviderAdmin; - const candidateOrgs = (await this.organizationService.getAll()).filter( - (o) => o.isOwner && o.providerId == null, - ); + const candidateOrgs = ( + await firstValueFrom(this.organizationService.organizations$(userId)) + ).filter((o) => o.isOwner && o.providerId == null); const allowedOrgsIds = await Promise.all( candidateOrgs.map((o) => this.organizationApiService.get(o.id)), ).then((orgs) => diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/create-organization.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/create-organization.component.ts index ec352748064..d22665b432f 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/create-organization.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/create-organization.component.ts @@ -9,7 +9,6 @@ import { OrganizationPlansComponent } from "@bitwarden/web-vault/app/billing"; selector: "app-create-organization", templateUrl: "create-organization.component.html", }) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil export class CreateOrganizationComponent implements OnInit { @ViewChild(OrganizationPlansComponent, { static: true }) orgPlansComponent: OrganizationPlansComponent; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/vnext-clients.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/vnext-clients.component.html deleted file mode 100644 index e6b5ae122f3..00000000000 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/vnext-clients.component.html +++ /dev/null @@ -1,82 +0,0 @@ - - - - - {{ "newClient" | i18n }} - - -
    - - - - {{ "loading" | i18n }} - - - -

    {{ "noClientsInList" | i18n }}

    - - - - {{ "name" | i18n }} - {{ "numberOfUsers" | i18n }} - {{ "billingPlan" | i18n }} - - - - - - - - {{ row.organizationName }} - - - {{ row.userCount }} - / {{ row.seats }} - - - {{ row.plan }} - - - - - - - -
    diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/vnext-clients.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/vnext-clients.component.ts deleted file mode 100644 index 2be38477d4c..00000000000 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/vnext-clients.component.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { Component } from "@angular/core"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { FormControl } from "@angular/forms"; -import { ActivatedRoute, Router, RouterModule } from "@angular/router"; -import { firstValueFrom, from, map } from "rxjs"; -import { debounceTime, first, switchMap } from "rxjs/operators"; - -import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; -import { ProviderStatusType, ProviderUserType } from "@bitwarden/common/admin-console/enums"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; -import { PlanType } from "@bitwarden/common/billing/enums"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; -import { - AvatarModule, - DialogService, - TableDataSource, - TableModule, - ToastService, -} from "@bitwarden/components"; -import { SharedOrganizationModule } from "@bitwarden/web-vault/app/admin-console/organizations/shared"; -import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module"; - -import { WebProviderService } from "../services/web-provider.service"; - -import { AddOrganizationComponent } from "./add-organization.component"; - -const DisallowedPlanTypes = [ - PlanType.Free, - PlanType.FamiliesAnnually2019, - PlanType.FamiliesAnnually, - PlanType.TeamsStarter2023, - PlanType.TeamsStarter, -]; - -@Component({ - templateUrl: "vnext-clients.component.html", - standalone: true, - imports: [ - SharedOrganizationModule, - HeaderModule, - CommonModule, - JslibModule, - AvatarModule, - RouterModule, - TableModule, - ], -}) -export class vNextClientsComponent { - providerId: string = ""; - addableOrganizations: Organization[] = []; - loading = true; - manageOrganizations = false; - showAddExisting = false; - dataSource: TableDataSource = - new TableDataSource(); - protected searchControl = new FormControl("", { nonNullable: true }); - - constructor( - private router: Router, - private providerService: ProviderService, - private apiService: ApiService, - private organizationService: OrganizationService, - private organizationApiService: OrganizationApiServiceAbstraction, - private activatedRoute: ActivatedRoute, - private dialogService: DialogService, - private i18nService: I18nService, - private toastService: ToastService, - private validationService: ValidationService, - private webProviderService: WebProviderService, - ) { - this.activatedRoute.queryParams.pipe(first(), takeUntilDestroyed()).subscribe((queryParams) => { - this.searchControl.setValue(queryParams.search); - }); - - this.activatedRoute.parent?.params - ?.pipe( - switchMap((params) => { - this.providerId = params.providerId; - return this.providerService.get$(this.providerId).pipe( - map((provider) => provider?.providerStatus === ProviderStatusType.Billable), - map((isBillable) => { - if (isBillable) { - return from( - this.router.navigate(["../manage-client-organizations"], { - relativeTo: this.activatedRoute, - }), - ); - } else { - return from(this.load()); - } - }), - ); - }), - takeUntilDestroyed(), - ) - .subscribe(); - - this.searchControl.valueChanges - .pipe(debounceTime(200), takeUntilDestroyed()) - .subscribe((searchText) => { - this.dataSource.filter = (data) => - data.organizationName.toLowerCase().indexOf(searchText.toLowerCase()) > -1; - }); - } - - async remove(organization: ProviderOrganizationOrganizationDetailsResponse) { - const confirmed = await this.dialogService.openSimpleDialog({ - title: organization.organizationName, - content: { key: "detachOrganizationConfirmation" }, - type: "warning", - }); - - if (!confirmed) { - return; - } - - try { - await this.webProviderService.detachOrganization(this.providerId, organization.id); - this.toastService.showToast({ - variant: "success", - title: "", - message: this.i18nService.t("detachedOrganization", organization.organizationName), - }); - await this.load(); - } catch (e) { - this.validationService.showError(e); - } - } - - async load() { - const response = await this.apiService.getProviderClients(this.providerId); - const clients = response.data != null && response.data.length > 0 ? response.data : []; - this.dataSource.data = clients; - this.manageOrganizations = - (await this.providerService.get(this.providerId)).type === ProviderUserType.ProviderAdmin; - const candidateOrgs = (await this.organizationService.getAll()).filter( - (o) => o.isOwner && o.providerId == null, - ); - const allowedOrgsIds = await Promise.all( - candidateOrgs.map((o) => this.organizationApiService.get(o.id)), - ).then((orgs) => - orgs.filter((o) => !DisallowedPlanTypes.includes(o.planType)).map((o) => o.id), - ); - this.addableOrganizations = candidateOrgs.filter((o) => allowedOrgsIds.includes(o.id)); - - this.showAddExisting = this.addableOrganizations.length !== 0; - this.loading = false; - } - - async addExistingOrganization() { - const dialogRef = AddOrganizationComponent.open(this.dialogService, { - providerId: this.providerId, - organizations: this.addableOrganizations, - }); - - if (await firstValueFrom(dialogRef.closed)) { - await this.load(); - } - } -} diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/accept-provider.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/accept-provider.component.html index 37d5001f776..30cd7fec8f2 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 @@ -1,6 +1,6 @@
    - +

    - - {{ "providerClientVaultPrivacyNotification" | i18n }} - - {{ "contactBitwardenSupport" | i18n }} . - diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.ts index 3f1a7ff3989..7e47da95e2b 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.ts @@ -10,27 +10,15 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { ProviderStatusType } from "@bitwarden/common/admin-console/enums"; import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { BannerModule, IconModule, LinkModule } from "@bitwarden/components"; +import { IconModule } from "@bitwarden/components"; import { ProviderPortalLogo } from "@bitwarden/web-vault/app/admin-console/icons/provider-portal-logo"; import { WebLayoutModule } from "@bitwarden/web-vault/app/layouts/web-layout.module"; -import { ProviderClientVaultPrivacyBannerService } from "./services/provider-client-vault-privacy-banner.service"; - @Component({ selector: "providers-layout", templateUrl: "providers-layout.component.html", standalone: true, - imports: [ - CommonModule, - RouterModule, - JslibModule, - WebLayoutModule, - IconModule, - LinkModule, - BannerModule, - ], + imports: [CommonModule, RouterModule, JslibModule, WebLayoutModule, IconModule], }) export class ProvidersLayoutComponent implements OnInit, OnDestroy { protected readonly logo = ProviderPortalLogo; @@ -41,15 +29,9 @@ export class ProvidersLayoutComponent implements OnInit, OnDestroy { protected isBillable: Observable; protected canAccessBilling$: Observable; - protected showProviderClientVaultPrivacyWarningBanner$ = this.configService.getFeatureFlag$( - FeatureFlag.ProviderClientVaultPrivacyBanner, - ); - constructor( private route: ActivatedRoute, private providerService: ProviderService, - private configService: ConfigService, - protected providerClientVaultPrivacyBannerService: ProviderClientVaultPrivacyBannerService, ) {} ngOnInit() { diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts index 09276263332..00c944e69bb 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts @@ -2,10 +2,8 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; import { authGuard } from "@bitwarden/angular/auth/guards"; -import { featureFlaggedRoute } from "@bitwarden/angular/platform/utils/feature-flagged-route"; import { AnonLayoutWrapperComponent } from "@bitwarden/auth/angular"; import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { FrontendLayoutComponent } from "@bitwarden/web-vault/app/layouts/frontend-layout.component"; import { UserLayoutComponent } from "@bitwarden/web-vault/app/layouts/user-layout.component"; @@ -14,12 +12,10 @@ import { ProviderSubscriptionComponent, hasConsolidatedBilling, ProviderBillingHistoryComponent, - vNextManageClientsComponent, } from "../../billing/providers"; import { ClientsComponent } from "./clients/clients.component"; import { CreateOrganizationComponent } from "./clients/create-organization.component"; -import { vNextClientsComponent } from "./clients/vnext-clients.component"; import { providerPermissionsGuard } from "./guards/provider-permissions.guard"; import { AcceptProviderComponent } from "./manage/accept-provider.component"; import { EventsComponent } from "./manage/events.component"; @@ -86,25 +82,13 @@ const routes: Routes = [ children: [ { path: "", pathMatch: "full", redirectTo: "clients" }, { path: "clients/create", component: CreateOrganizationComponent }, - ...featureFlaggedRoute({ - defaultComponent: ClientsComponent, - flaggedComponent: vNextClientsComponent, - featureFlag: FeatureFlag.PM12443RemovePagingLogic, - routeOptions: { - path: "clients", - data: { titleId: "clients" }, - }, - }), - ...featureFlaggedRoute({ - defaultComponent: ManageClientsComponent, - flaggedComponent: vNextManageClientsComponent, - featureFlag: FeatureFlag.PM12443RemovePagingLogic, - routeOptions: { - path: "manage-client-organizations", - data: { titleId: "clients" }, - canActivate: [hasConsolidatedBilling], - }, - }), + { path: "clients", component: ClientsComponent, data: { titleId: "clients" } }, + { + path: "manage-client-organizations", + canActivate: [hasConsolidatedBilling], + component: ManageClientsComponent, + data: { titleId: "clients" }, + }, { path: "manage", children: [ diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts index 80108e66eda..9b5559af946 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts @@ -6,22 +6,21 @@ import { FormsModule } from "@angular/forms"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { SearchModule } from "@bitwarden/components"; import { DangerZoneComponent } from "@bitwarden/web-vault/app/auth/settings/account/danger-zone.component"; -import { OrganizationPlansComponent, TaxInfoComponent } from "@bitwarden/web-vault/app/billing"; +import { OrganizationPlansComponent } from "@bitwarden/web-vault/app/billing"; +import { VerifyBankAccountComponent } from "@bitwarden/web-vault/app/billing/shared/verify-bank-account/verify-bank-account.component"; import { OssModule } from "@bitwarden/web-vault/app/oss.module"; import { CreateClientDialogComponent, - NoClientsComponent, ManageClientNameDialogComponent, - ManageClientsComponent, ManageClientSubscriptionDialogComponent, ProviderBillingHistoryComponent, ProviderSubscriptionComponent, ProviderSubscriptionStatusComponent, } from "../../billing/providers"; +import { AddExistingOrganizationDialogComponent } from "../../billing/providers/clients/add-existing-organization-dialog.component"; import { AddOrganizationComponent } from "./clients/add-organization.component"; -import { ClientsComponent } from "./clients/clients.component"; import { CreateOrganizationComponent } from "./clients/create-organization.component"; import { AcceptProviderComponent } from "./manage/accept-provider.component"; import { AddEditMemberDialogComponent } from "./manage/dialogs/add-edit-member-dialog.component"; @@ -49,9 +48,9 @@ import { VerifyRecoverDeleteProviderComponent } from "./verify-recover-delete-pr OrganizationPlansComponent, SearchModule, ProvidersLayoutComponent, - TaxInfoComponent, DangerZoneComponent, ScrollingModule, + VerifyBankAccountComponent, ], declarations: [ AcceptProviderComponent, @@ -59,7 +58,6 @@ import { VerifyRecoverDeleteProviderComponent } from "./verify-recover-delete-pr AddOrganizationComponent, BulkConfirmDialogComponent, BulkRemoveDialogComponent, - ClientsComponent, CreateOrganizationComponent, EventsComponent, MembersComponent, @@ -67,9 +65,8 @@ import { VerifyRecoverDeleteProviderComponent } from "./verify-recover-delete-pr SetupProviderComponent, UserAddEditComponent, AddEditMemberDialogComponent, + AddExistingOrganizationDialogComponent, CreateClientDialogComponent, - NoClientsComponent, - ManageClientsComponent, ManageClientNameDialogComponent, ManageClientSubscriptionDialogComponent, ProviderBillingHistoryComponent, diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/services/provider-client-vault-privacy-banner.service.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/services/provider-client-vault-privacy-banner.service.ts deleted file mode 100644 index c347f5c2aae..00000000000 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/services/provider-client-vault-privacy-banner.service.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Injectable } from "@angular/core"; - -import { - StateProvider, - AC_BANNERS_DISMISSED_DISK, - UserKeyDefinition, -} from "@bitwarden/common/platform/state"; - -export const SHOW_BANNER_KEY = new UserKeyDefinition( - AC_BANNERS_DISMISSED_DISK, - "showProviderClientVaultPrivacyBanner", - { - deserializer: (b) => b, - clearOn: [], - }, -); - -/** Displays a banner warning provider users that client organization vaults - * will soon become inaccessible directly. */ -@Injectable({ providedIn: "root" }) -export class ProviderClientVaultPrivacyBannerService { - private _showBanner = this.stateProvider.getActive(SHOW_BANNER_KEY); - - showBanner$ = this._showBanner.state$; - - constructor(private stateProvider: StateProvider) {} - - async hideBanner() { - await this._showBanner.update(() => false); - } -} diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts index 264b43aee9d..d3482ea67a5 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts @@ -1,15 +1,20 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Injectable } from "@angular/core"; +import { firstValueFrom, map } from "rxjs"; +import { switchMap } from "rxjs/operators"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction"; import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request"; import { ProviderAddOrganizationRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-add-organization.request"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; import { PlanType } from "@bitwarden/common/billing/enums"; import { CreateClientOrganizationRequest } from "@bitwarden/common/billing/models/request/create-client-organization.request"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { StateProvider } from "@bitwarden/common/platform/state"; +import { OrganizationId } from "@bitwarden/common/types/guid"; import { OrgKey } from "@bitwarden/common/types/key"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { KeyService } from "@bitwarden/key-management"; @@ -23,6 +28,8 @@ export class WebProviderService { private i18nService: I18nService, private encryptService: EncryptService, private billingApiService: BillingApiServiceAbstraction, + private stateProvider: StateProvider, + private providerApiService: ProviderApiServiceAbstraction, ) {} async addOrganizationToProvider(providerId: string, organizationId: string) { @@ -40,6 +47,22 @@ export class WebProviderService { return response; } + async addOrganizationToProviderVNext(providerId: string, organizationId: string): Promise { + const orgKey = await firstValueFrom( + this.stateProvider.activeUserId$.pipe( + switchMap((userId) => this.keyService.orgKeys$(userId)), + map((organizationKeysById) => organizationKeysById[organizationId as OrganizationId]), + ), + ); + const providerKey = await this.keyService.getProviderKey(providerId); + const encryptedOrgKey = await this.encryptService.encrypt(orgKey.key, providerKey); + await this.providerApiService.addOrganizationToProvider(providerId, { + key: encryptedOrgKey.encryptedString, + organizationId, + }); + await this.syncService.fullSync(true); + } + async createClientOrganization( providerId: string, name: string, diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.ts index e0a4eaedce1..e72e1c7c326 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.ts @@ -21,7 +21,6 @@ import { DialogService, ToastService } from "@bitwarden/components"; selector: "provider-account", templateUrl: "account.component.html", }) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil export class AccountComponent implements OnDestroy, OnInit { selfHosted = false; loading = true; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts index f773db6c11c..ecf649b8f31 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts @@ -23,8 +23,7 @@ import { KeyService } from "@bitwarden/key-management"; templateUrl: "setup.component.html", }) export class SetupComponent implements OnInit, OnDestroy { - @ViewChild(ManageTaxInformationComponent) - manageTaxInformationComponent: ManageTaxInformationComponent; + @ViewChild(ManageTaxInformationComponent) taxInformationComponent: ManageTaxInformationComponent; loading = true; providerId: string; @@ -111,7 +110,7 @@ export class SetupComponent implements OnInit, OnDestroy { try { this.formGroup.markAllAsTouched(); - if (!this.manageTaxInformationComponent.validate() || !this.formGroup.valid) { + if (!this.taxInformationComponent.validate() || !this.formGroup.valid) { return; } @@ -125,7 +124,7 @@ export class SetupComponent implements OnInit, OnDestroy { request.key = key; request.taxInfo = new ExpandedTaxInfoUpdateRequest(); - const taxInformation = this.manageTaxInformationComponent.getTaxInformation(); + const taxInformation = this.taxInformationComponent.getTaxInformation(); request.taxInfo.country = taxInformation.country; request.taxInfo.postalCode = taxInformation.postalCode; @@ -147,6 +146,7 @@ export class SetupComponent implements OnInit, OnDestroy { await this.router.navigate(["/providers", provider.id]); } catch (e) { + e.message = this.i18nService.translate(e.message) || e.message; this.validationService.showError(e); } }; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/verify-recover-delete-provider.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/verify-recover-delete-provider.component.html index 116e1660d7a..e1f99122b22 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/verify-recover-delete-provider.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/verify-recover-delete-provider.component.html @@ -1,4 +1,4 @@ -

    {{ "deleteProvider" | i18n }}

    +

    {{ "deleteProvider" | i18n }}

    {{ "deleteProviderWarning" | i18n }}

    {{ name }} diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/verify-recover-delete-provider.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/verify-recover-delete-provider.component.ts index 83a87d8bc6c..b27a7ddd0f4 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/verify-recover-delete-provider.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/verify-recover-delete-provider.component.ts @@ -14,7 +14,6 @@ import { ToastService } from "@bitwarden/components"; selector: "app-verify-recover-delete-provider", templateUrl: "verify-recover-delete-provider.component.html", }) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil export class VerifyRecoverDeleteProviderComponent implements OnInit { name: string; diff --git a/bitwarden_license/bit-web/src/app/app.component.ts b/bitwarden_license/bit-web/src/app/app.component.ts index 1e0f60e2cd2..dd814f5c0d2 100644 --- a/bitwarden_license/bit-web/src/app/app.component.ts +++ b/bitwarden_license/bit-web/src/app/app.component.ts @@ -20,17 +20,10 @@ export class AppComponent extends BaseAppComponent implements OnInit { this.policyListService.addPolicies([ new MaximumVaultTimeoutPolicy(), new DisablePersonalVaultExportPolicy(), + new FreeFamiliesSponsorshipPolicy(), + new ActivateAutofillPolicy(), ]); - this.configService - .getFeatureFlag(FeatureFlag.DisableFreeFamiliesSponsorship) - .then((isFreeFamilyEnabled) => { - if (isFreeFamilyEnabled) { - this.policyListService.addPolicies([new FreeFamiliesSponsorshipPolicy()]); - } - this.policyListService.addPolicies([new ActivateAutofillPolicy()]); - }); - this.configService.getFeatureFlag(FeatureFlag.IdpAutoSubmitLogin).then((enabled) => { if ( enabled && diff --git a/bitwarden_license/bit-web/src/app/app.module.ts b/bitwarden_license/bit-web/src/app/app.module.ts index 3a78ae0ed01..833a67e1515 100644 --- a/bitwarden_license/bit-web/src/app/app.module.ts +++ b/bitwarden_license/bit-web/src/app/app.module.ts @@ -4,7 +4,6 @@ import { NgModule } from "@angular/core"; import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { RouterModule } from "@angular/router"; -import { InfiniteScrollDirective } from "ngx-infinite-scroll"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { CoreModule } from "@bitwarden/web-vault/app/core"; @@ -37,7 +36,6 @@ import { AccessIntelligenceModule } from "./tools/access-intelligence/access-int FormsModule, ReactiveFormsModule, CoreModule, - InfiniteScrollDirective, DragDropModule, AppRoutingModule, OssRoutingModule, diff --git a/bitwarden_license/bit-web/src/app/auth/sso/sso.component.html b/bitwarden_license/bit-web/src/app/auth/sso/sso.component.html index 0731820e413..9cead9d21f3 100644 --- a/bitwarden_license/bit-web/src/app/auth/sso/sso.component.html +++ b/bitwarden_license/bit-web/src/app/auth/sso/sso.component.html @@ -3,7 +3,7 @@ @@ -38,8 +38,6 @@ -


    - {{ "memberDecryptionOption" | i18n }} @@ -89,19 +87,19 @@ {{ "trustedDevices" | i18n }} - {{ "memberDecryptionOptionTdeDescriptionPartOne" | i18n }} - {{ - "memberDecryptionOptionTdeDescriptionLinkOne" | i18n - }} - {{ "memberDecryptionOptionTdeDescriptionPartTwo" | i18n }} - {{ - "memberDecryptionOptionTdeDescriptionLinkTwo" | i18n - }} - {{ "memberDecryptionOptionTdeDescriptionPartThree" | i18n }} - {{ - "memberDecryptionOptionTdeDescriptionLinkThree" | i18n - }} - {{ "memberDecryptionOptionTdeDescriptionPartFour" | i18n }} + {{ "memberDecryptionOptionTdeDescPart1" | i18n }} + + {{ "memberDecryptionOptionTdeDescLink1" | i18n }} + + {{ "memberDecryptionOptionTdeDescPart2" | i18n }} + + {{ "memberDecryptionOptionTdeDescLink2" | i18n }} + + {{ "memberDecryptionOptionTdeDescPart3" | i18n }} + + {{ "memberDecryptionOptionTdeDescLink3" | i18n }} + + {{ "memberDecryptionOptionTdeDescPart4" | i18n }} @@ -148,7 +146,7 @@ aria-live="polite" *ngIf="haveTestedKeyConnector && !keyConnectorUrl.hasError('invalidUrl')" > - + {{ "keyConnectorTestSuccess" | i18n }} @@ -156,7 +154,7 @@ -
    +
    {{ "type" | i18n }} @@ -173,8 +171,10 @@ *ngIf="ssoConfigForm.get('configType').value === ssoType.OpenIdConnect" [formGroup]="openIdForm" > -
    -

    {{ "openIdConnectConfig" | i18n }}

    +
    +

    + {{ "openIdConnectConfig" | i18n }} +

    {{ "callbackPath" | i18n }} @@ -246,14 +246,15 @@
    -

    +

    {{ "openIdOptionalCustomizations" | i18n }}

    + + + + +

    + {{ "noOrganizations" | i18n }} +

    + + +

    {{ "yourProviderSubscriptionCredit" | i18n }}

    +

    {{ "doYouWantToAddThisOrg" | i18n: dialogParams.provider.name }}

    +
    +
    {{ "organization" | i18n }}: {{ selectedOrganization.name }}
    +
    {{ "billingPlan" | i18n }}: {{ selectedOrganization.plan }}
    +
    {{ "assignedSeats" | i18n }}: {{ selectedOrganization.seats }}
    +
    +
    + + + + + + diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/add-existing-organization-dialog.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/add-existing-organization-dialog.component.ts new file mode 100644 index 00000000000..3df0693d091 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/add-existing-organization-dialog.component.ts @@ -0,0 +1,82 @@ +import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; +import { Component, Inject, OnInit } from "@angular/core"; + +import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction"; +import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; +import { AddableOrganizationResponse } from "@bitwarden/common/admin-console/models/response/addable-organization.response"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { DialogService, ToastService } from "@bitwarden/components"; + +import { WebProviderService } from "../../../admin-console/providers/services/web-provider.service"; + +export type AddExistingOrganizationDialogParams = { + provider: Provider; +}; + +export enum AddExistingOrganizationDialogResultType { + Closed = "closed", + Submitted = "submitted", +} + +@Component({ + templateUrl: "./add-existing-organization-dialog.component.html", +}) +export class AddExistingOrganizationDialogComponent implements OnInit { + protected loading: boolean = true; + + addableOrganizations: AddableOrganizationResponse[] = []; + selectedOrganization?: AddableOrganizationResponse; + + protected readonly ResultType = AddExistingOrganizationDialogResultType; + + constructor( + @Inject(DIALOG_DATA) protected dialogParams: AddExistingOrganizationDialogParams, + private dialogRef: DialogRef, + private i18nService: I18nService, + private providerApiService: ProviderApiServiceAbstraction, + private toastService: ToastService, + private webProviderService: WebProviderService, + ) {} + + async ngOnInit() { + this.addableOrganizations = await this.providerApiService.getProviderAddableOrganizations( + this.dialogParams.provider.id, + ); + this.loading = false; + } + + addExistingOrganization = async (): Promise => { + if (this.selectedOrganization) { + await this.webProviderService.addOrganizationToProviderVNext( + this.dialogParams.provider.id, + this.selectedOrganization.id, + ); + + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("addedExistingOrganization"), + }); + + this.dialogRef.close(this.ResultType.Submitted); + } + }; + + selectOrganization(organizationId: string) { + this.selectedOrganization = this.addableOrganizations.find( + (organization) => organization.id === organizationId, + ); + } + + static open = ( + dialogService: DialogService, + dialogConfig: DialogConfig< + AddExistingOrganizationDialogParams, + DialogRef + >, + ) => + dialogService.open< + AddExistingOrganizationDialogResultType, + AddExistingOrganizationDialogParams + >(AddExistingOrganizationDialogComponent, dialogConfig); +} diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.html b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.html index 78f2cb41bef..c11b23db9fb 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.html +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.html @@ -15,7 +15,7 @@
    @@ -29,9 +29,11 @@

    {{ planCard.name }}

    {{ - planCard.cost | currency: "$" + planCard.getMonthlyCost() | currency: "$" }} - / {{ "monthPerMember" | i18n }} + / {{ planCard.getTimePerMemberLabel() | i18n }}
    @@ -45,8 +47,8 @@ {{ "organizationNameMaxLength" | i18n }} diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts index 2a27b1b32f3..38eaed83937 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts @@ -1,6 +1,5 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; +import { BasePortalOutlet } from "@angular/cdk/portal"; import { Component, Inject, OnInit } from "@angular/core"; import { FormControl, FormGroup, Validators } from "@angular/forms"; @@ -25,48 +24,42 @@ export enum CreateClientDialogResultType { export const openCreateClientDialog = ( dialogService: DialogService, - dialogConfig: DialogConfig, + dialogConfig: DialogConfig< + CreateClientDialogParams, + DialogRef, + BasePortalOutlet + >, ) => dialogService.open( CreateClientDialogComponent, dialogConfig, ); -type PlanCard = { - name: string; - cost: number; - type: PlanType; - plan: PlanResponse; +export class PlanCard { + readonly name: string; + private readonly cost: number; + readonly type: PlanType; + readonly plan: PlanResponse; selected: boolean; -}; -@Component({ - templateUrl: "./create-client-dialog.component.html", -}) -export class CreateClientDialogComponent implements OnInit { - protected discountPercentage: number; - protected formGroup = new FormGroup({ - clientOwnerEmail: new FormControl("", [Validators.required, Validators.email]), - organizationName: new FormControl("", [Validators.required, Validators.maxLength(50)]), - seats: new FormControl(null, [Validators.required, Validators.min(1)]), - }); - protected loading = true; - protected planCards: PlanCard[]; - protected ResultType = CreateClientDialogResultType; + constructor(name: string, cost: number, type: PlanType, plan: PlanResponse, selected: boolean) { + this.name = name; + this.cost = cost; + this.type = type; + this.plan = plan; + this.selected = selected; + } - private providerPlans: ProviderPlanResponse[]; + getMonthlyCost(): number { + return this.plan.isAnnual ? this.cost / 12 : this.cost; + } - constructor( - private billingApiService: BillingApiServiceAbstraction, - @Inject(DIALOG_DATA) private dialogParams: CreateClientDialogParams, - private dialogRef: DialogRef, - private i18nService: I18nService, - private toastService: ToastService, - private webProviderService: WebProviderService, - ) {} + getTimePerMemberLabel(): string { + return this.plan.isAnnual ? "monthPerMemberBilledAnnually" : "monthPerMember"; + } - protected getPlanCardContainerClasses(selected: boolean) { - switch (selected) { + getContainerClasses() { + switch (this.selected) { case true: { return [ "tw-group/plan-card-container", @@ -97,6 +90,41 @@ export class CreateClientDialogComponent implements OnInit { } } } +} + +@Component({ + templateUrl: "./create-client-dialog.component.html", +}) +export class CreateClientDialogComponent implements OnInit { + protected discountPercentage: number | null | undefined; + protected formGroup = new FormGroup({ + clientOwnerEmail: new FormControl("", { + nonNullable: true, + validators: [Validators.required, Validators.email], + }), + organizationName: new FormControl("", { + nonNullable: true, + validators: [Validators.required, Validators.maxLength(50)], + }), + seats: new FormControl(1, { + nonNullable: true, + validators: [Validators.required, Validators.min(1)], + }), + }); + protected loading = true; + protected planCards: PlanCard[] = []; + protected ResultType = CreateClientDialogResultType; + + private providerPlans: ProviderPlanResponse[] = []; + + constructor( + private billingApiService: BillingApiServiceAbstraction, + @Inject(DIALOG_DATA) private dialogParams: CreateClientDialogParams, + private dialogRef: DialogRef, + private i18nService: I18nService, + private toastService: ToastService, + private webProviderService: WebProviderService, + ) {} async ngOnInit(): Promise { const response = await this.billingApiService.getProviderSubscription( @@ -114,6 +142,10 @@ export class CreateClientDialogComponent implements OnInit { const providerPlan = this.providerPlans[i]; const plan = this.dialogParams.plans.find((plan) => plan.type === providerPlan.type); + if (!plan) { + continue; + } + let planName: string; switch (plan.productTier) { case ProductTierType.Teams: { @@ -124,23 +156,28 @@ export class CreateClientDialogComponent implements OnInit { planName = this.i18nService.t("planNameEnterprise"); break; } + default: + continue; } - this.planCards.push({ - name: planName, - cost: plan.PasswordManager.providerPortalSeatPrice * discountFactor, - type: plan.type, - plan: plan, - selected: i === 0, - }); + this.planCards.push( + new PlanCard( + planName, + plan.PasswordManager.providerPortalSeatPrice * discountFactor, + plan.type, + plan, + i === 0, + ), + ); } this.loading = false; } protected selectPlan(name: string) { - this.planCards.find((planCard) => planCard.name === name).selected = true; - this.planCards.find((planCard) => planCard.name !== name).selected = false; + this.planCards.forEach((planCard) => { + planCard.selected = planCard.name === name; + }); } submit = async () => { @@ -152,17 +189,21 @@ export class CreateClientDialogComponent implements OnInit { const selectedPlanCard = this.planCards.find((planCard) => planCard.selected); + if (!selectedPlanCard) { + return; + } + await this.webProviderService.createClientOrganization( this.dialogParams.providerId, - this.formGroup.value.organizationName, - this.formGroup.value.clientOwnerEmail, + this.formGroup.controls.organizationName.value, + this.formGroup.controls.clientOwnerEmail.value, selectedPlanCard.type, - this.formGroup.value.seats, + this.formGroup.controls.seats.value, ); this.toastService.showToast({ variant: "success", - title: null, + title: "", message: this.i18nService.t("createdNewClient"), }); @@ -178,7 +219,7 @@ export class CreateClientDialogComponent implements OnInit { const openSeats = selectedProviderPlan.seatMinimum - selectedProviderPlan.assignedSeats; - const unassignedSeats = openSeats - this.formGroup.value.seats; + const unassignedSeats = openSeats - this.formGroup.controls.seats.value; return unassignedSeats > 0 ? unassignedSeats : 0; } @@ -191,22 +232,22 @@ export class CreateClientDialogComponent implements OnInit { } if (selectedProviderPlan.purchasedSeats > 0) { - return this.formGroup.value.seats; + return this.formGroup.controls.seats.value; } const additionalSeatsPurchased = - this.formGroup.value.seats + + this.formGroup.controls.seats.value + selectedProviderPlan.assignedSeats - selectedProviderPlan.seatMinimum; return additionalSeatsPurchased > 0 ? additionalSeatsPurchased : 0; } - private getSelectedProviderPlan(): ProviderPlanResponse { + private getSelectedProviderPlan(): ProviderPlanResponse | null { if (this.loading || !this.planCards) { return null; } - const selectedPlan = this.planCards.find((planCard) => planCard.selected).plan; - return this.providerPlans.find((providerPlan) => providerPlan.planName === selectedPlan.name); + const selectedPlan = this.planCards.find((planCard) => planCard.selected)!.plan; + return this.providerPlans.find((providerPlan) => providerPlan.planName === selectedPlan.name)!; } } diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/index.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/index.ts index 05887fc198e..898d51e0baf 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/index.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/index.ts @@ -3,5 +3,4 @@ export * from "./manage-clients.component"; export * from "./manage-client-name-dialog.component"; export * from "./manage-client-subscription-dialog.component"; export * from "./no-clients.component"; -export * from "./vnext-manage-clients.component"; export * from "./replace.pipe"; diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.ts index ee0e5c07c68..e5da22a861e 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.ts @@ -11,7 +11,8 @@ import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstract import { UpdateClientOrganizationRequest } from "@bitwarden/common/billing/models/request/update-client-organization.request"; import { ProviderPlanResponse } from "@bitwarden/common/billing/models/response/provider-subscription-response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { DialogService, ToastService } from "@bitwarden/components"; +import { DialogService } from "@bitwarden/components"; +import { BillingNotificationService } from "@bitwarden/web-vault/app/billing/services/billing-notification.service"; type ManageClientSubscriptionDialogParams = { organization: ProviderOrganizationOrganizationDetailsResponse; @@ -56,30 +57,34 @@ export class ManageClientSubscriptionDialogComponent implements OnInit { @Inject(DIALOG_DATA) protected dialogParams: ManageClientSubscriptionDialogParams, private dialogRef: DialogRef, private i18nService: I18nService, - private toastService: ToastService, + private billingNotificationService: BillingNotificationService, ) {} async ngOnInit(): Promise { - const response = await this.billingApiService.getProviderSubscription( - this.dialogParams.provider.id, - ); + try { + const response = await this.billingApiService.getProviderSubscription( + this.dialogParams.provider.id, + ); - this.providerPlan = response.plans.find( - (plan) => plan.planName === this.dialogParams.organization.plan, - ); + this.providerPlan = response.plans.find( + (plan) => plan.planName === this.dialogParams.organization.plan, + ); - this.assignedSeats = this.providerPlan.assignedSeats; - this.openSeats = this.providerPlan.seatMinimum - this.providerPlan.assignedSeats; - this.purchasedSeats = this.providerPlan.purchasedSeats; - this.seatMinimum = this.providerPlan.seatMinimum; + this.assignedSeats = this.providerPlan.assignedSeats; + this.openSeats = this.providerPlan.seatMinimum - this.providerPlan.assignedSeats; + this.purchasedSeats = this.providerPlan.purchasedSeats; + this.seatMinimum = this.providerPlan.seatMinimum; - this.formGroup.controls.assignedSeats.addValidators( - this.isServiceUserWithPurchasedSeats - ? this.createPurchasedSeatsValidator() - : this.createUnassignedSeatsValidator(), - ); - - this.loading = false; + this.formGroup.controls.assignedSeats.addValidators( + this.isServiceUserWithPurchasedSeats + ? this.createPurchasedSeatsValidator() + : this.createUnassignedSeatsValidator(), + ); + } catch (error) { + this.billingNotificationService.handleError(error); + } finally { + this.loading = false; + } } submit = async () => { @@ -91,24 +96,25 @@ export class ManageClientSubscriptionDialogComponent implements OnInit { return; } - const request = new UpdateClientOrganizationRequest(); - request.assignedSeats = this.formGroup.value.assignedSeats; - request.name = this.dialogParams.organization.organizationName; + try { + const request = new UpdateClientOrganizationRequest(); + request.assignedSeats = this.formGroup.value.assignedSeats; + request.name = this.dialogParams.organization.organizationName; - await this.billingApiService.updateProviderClientOrganization( - this.dialogParams.provider.id, - this.dialogParams.organization.id, - request, - ); + await this.billingApiService.updateProviderClientOrganization( + this.dialogParams.provider.id, + this.dialogParams.organization.id, + request, + ); - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("subscriptionUpdated"), - }); + this.billingNotificationService.showSuccess(this.i18nService.t("subscriptionUpdated")); - this.loading = false; - this.dialogRef.close(this.ResultType.Submitted); + this.dialogRef.close(this.ResultType.Submitted); + } catch (error) { + this.billingNotificationService.handleError(error); + } finally { + this.loading = false; + } }; createPurchasedSeatsValidator = diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.html b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.html index 49a9208107f..077aeb6c124 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.html +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.html @@ -1,9 +1,39 @@ - - - - {{ "addNewOrganization" | i18n }} - + + + + + + + + + + + + {{ "addNewOrganization" | i18n }} + + @@ -16,78 +46,67 @@ - + - - {{ "client" | i18n }} - {{ "assigned" | i18n }} - {{ "used" | i18n }} - {{ "remaining" | i18n }} - {{ "billingPlan" | i18n }} - - + {{ "client" | i18n }} + {{ "assigned" | i18n }} + {{ "used" | i18n }} + {{ "remaining" | i18n }} + {{ "billingPlan" | i18n }} + - - - - - - - - - - {{ client.seats }} - - - {{ client.occupiedSeats }} - - - {{ client.remainingSeats }} - - - {{ removeMonthly(client.plan) }} - - - - - - - - - - + + + + + + + + + {{ row.seats }} + + + {{ row.occupiedSeats }} + + + {{ row.remainingSeats }} + + + {{ row.plan | replace: " (Monthly)" : "" }} + + + + + + + + + - -
    + +
    diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.ts index d413551b743..c0b4ba72499 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.ts @@ -1,25 +1,37 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Component } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { FormControl } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; import { firstValueFrom, from, lastValueFrom, map } from "rxjs"; -import { switchMap } from "rxjs/operators"; +import { debounceTime, first, switchMap } from "rxjs/operators"; -import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { ProviderStatusType, ProviderUserType } from "@bitwarden/common/admin-console/enums"; import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; -import { BillingApiServiceAbstraction as BillingApiService } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; +import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; -import { DialogService, ToastService } from "@bitwarden/components"; +import { + AvatarModule, + DialogService, + TableDataSource, + TableModule, + ToastService, +} from "@bitwarden/components"; +import { SharedOrganizationModule } from "@bitwarden/web-vault/app/admin-console/organizations/shared"; +import { BillingNotificationService } from "@bitwarden/web-vault/app/billing/services/billing-notification.service"; +import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module"; -import { BaseClientsComponent } from "../../../admin-console/providers/clients/base-clients.component"; import { WebProviderService } from "../../../admin-console/providers/services/web-provider.service"; +import { + AddExistingOrganizationDialogComponent, + AddExistingOrganizationDialogResultType, +} from "./add-existing-organization-dialog.component"; import { CreateClientDialogResultType, openCreateClientDialog, @@ -32,47 +44,58 @@ import { ManageClientSubscriptionDialogResultType, openManageClientSubscriptionDialog, } from "./manage-client-subscription-dialog.component"; +import { NoClientsComponent } from "./no-clients.component"; +import { ReplacePipe } from "./replace.pipe"; @Component({ templateUrl: "manage-clients.component.html", + standalone: true, + imports: [ + AvatarModule, + TableModule, + HeaderModule, + SharedOrganizationModule, + NoClientsComponent, + ReplacePipe, + ], }) -export class ManageClientsComponent extends BaseClientsComponent { - providerId: string; - provider: Provider; - +export class ManageClientsComponent { + providerId: string = ""; + provider: Provider | undefined; loading = true; isProviderAdmin = false; + dataSource: TableDataSource = + new TableDataSource(); - protected plans: PlanResponse[]; + protected searchControl = new FormControl("", { nonNullable: true }); + protected plans: PlanResponse[] = []; + protected addExistingOrgsFromProviderPortal$ = this.configService.getFeatureFlag$( + FeatureFlag.PM15179_AddExistingOrgsFromProviderPortal, + ); constructor( - private billingApiService: BillingApiService, + private billingApiService: BillingApiServiceAbstraction, private providerService: ProviderService, private router: Router, - activatedRoute: ActivatedRoute, - dialogService: DialogService, - i18nService: I18nService, - searchService: SearchService, - toastService: ToastService, - validationService: ValidationService, - webProviderService: WebProviderService, + private activatedRoute: ActivatedRoute, + private dialogService: DialogService, + private i18nService: I18nService, + private toastService: ToastService, + private validationService: ValidationService, + private webProviderService: WebProviderService, + private configService: ConfigService, + private billingNotificationService: BillingNotificationService, ) { - super( - activatedRoute, - dialogService, - i18nService, - searchService, - toastService, - validationService, - webProviderService, - ); + this.activatedRoute.queryParams.pipe(first(), takeUntilDestroyed()).subscribe((queryParams) => { + this.searchControl.setValue(queryParams.search); + }); - this.activatedRoute.parent.params - .pipe( + this.activatedRoute.parent?.params + ?.pipe( switchMap((params) => { this.providerId = params.providerId; return this.providerService.get$(this.providerId).pipe( - map((provider) => provider?.providerStatus === ProviderStatusType.Billable), + map((provider: Provider) => provider?.providerStatus === ProviderStatusType.Billable), map((isBillable) => { if (!isBillable) { return from( @@ -89,26 +112,45 @@ export class ManageClientsComponent extends BaseClientsComponent { takeUntilDestroyed(), ) .subscribe(); - } - removeMonthly = (plan: string) => plan.replace(" (Monthly)", ""); + this.searchControl.valueChanges + .pipe(debounceTime(200), takeUntilDestroyed()) + .subscribe((searchText) => { + this.dataSource.filter = (data) => + data.organizationName.toLowerCase().indexOf(searchText.toLowerCase()) > -1; + }); + } async load() { - this.provider = await firstValueFrom(this.providerService.get$(this.providerId)); - - this.isProviderAdmin = this.provider.type === ProviderUserType.ProviderAdmin; - - this.clients = ( - await this.billingApiService.getProviderClientOrganizations(this.providerId) - ).data; - - this.dataSource.data = this.clients; - - this.plans = (await this.billingApiService.getPlans()).data; - - this.loading = false; + try { + this.provider = await firstValueFrom(this.providerService.get$(this.providerId)); + this.isProviderAdmin = this.provider?.type === ProviderUserType.ProviderAdmin; + this.dataSource.data = ( + await this.billingApiService.getProviderClientOrganizations(this.providerId) + ).data; + this.plans = (await this.billingApiService.getPlans()).data; + this.loading = false; + } catch (error) { + this.billingNotificationService.handleError(error); + } } + addExistingOrganization = async () => { + if (this.provider) { + const reference = AddExistingOrganizationDialogComponent.open(this.dialogService, { + data: { + provider: this.provider, + }, + }); + + const result = await lastValueFrom(reference.closed); + + if (result === AddExistingOrganizationDialogResultType.Submitted) { + await this.load(); + } + } + }; + createClient = async () => { const reference = openCreateClientDialog(this.dialogService, { data: { @@ -131,7 +173,7 @@ export class ManageClientsComponent extends BaseClientsComponent { organization: { id: organization.id, name: organization.organizationName, - seats: organization.seats, + seats: organization.seats ? organization.seats : 0, }, }, }); @@ -149,7 +191,7 @@ export class ManageClientsComponent extends BaseClientsComponent { const dialogRef = openManageClientSubscriptionDialog(this.dialogService, { data: { organization, - provider: this.provider, + provider: this.provider!, }, }); @@ -159,4 +201,28 @@ export class ManageClientsComponent extends BaseClientsComponent { await this.load(); } }; + + async remove(organization: ProviderOrganizationOrganizationDetailsResponse) { + const confirmed = await this.dialogService.openSimpleDialog({ + title: organization.organizationName, + content: { key: "detachOrganizationConfirmation" }, + type: "warning", + }); + + if (!confirmed) { + return; + } + + try { + await this.webProviderService.detachOrganization(this.providerId, organization.id); + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("detachedOrganization", organization.organizationName), + }); + await this.load(); + } catch (e) { + this.validationService.showError(e); + } + } } diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/no-clients.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/no-clients.component.ts index 30ab444fe8b..7e4323d7603 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/no-clients.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/no-clients.component.ts @@ -1,6 +1,7 @@ import { Component, EventEmitter, Input, Output } from "@angular/core"; import { svgIcon } from "@bitwarden/components"; +import { SharedOrganizationModule } from "@bitwarden/web-vault/app/admin-console/organizations/shared"; const gearIcon = svgIcon` @@ -23,6 +24,8 @@ const gearIcon = svgIcon` @Component({ selector: "app-no-clients", + standalone: true, + imports: [SharedOrganizationModule], template: `

    {{ "noClients" | i18n }}

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

    {{ "noClients" | i18n }}

    - - - {{ "addNewOrganization" | i18n }} - -
    `, -}) -export class vNextNoClientsComponent { - icon = gearIcon; - @Input() showAddOrganizationButton = true; - @Output() addNewOrganizationClicked = new EventEmitter(); - - addNewOrganization = () => this.addNewOrganizationClicked.emit(); -} diff --git a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.html b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.html index 50db2cb6a36..f2f72fa5bb4 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.html +++ b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.html @@ -1,7 +1,7 @@ - + {{ "loading" | i18n }} @@ -11,7 +11,7 @@
    {{ "details" | i18n }}   {{ getFormattedPlanName(i.planName) }} {{ "orgSeats" | i18n }} ({{ - i.cadence.toLowerCase() + getFormattedPlanNameCadence(i.cadence) | i18n }}) {{ "×" }}{{ getFormattedSeatCount(i.seatMinimum, i.purchasedSeats) }} @ {{ @@ -38,12 +38,11 @@ }} - {{ ((100 - subscription.discountPercentage) / 100) * i.cost | currency: "$" }} /{{ - "month" | i18n - }} + {{ ((100 - subscription.discountPercentage) / 100) * i.cost | currency: "$" }} / + {{ getBillingCadenceLabel(i) | i18n }}
    - {{ i.cost | currency: "$" }} /{{ "month" | i18n }} + {{ i.cost | currency: "$" }} / {{ getBillingCadenceLabel(i) | i18n }}
    @@ -52,8 +51,9 @@ - Total: {{ totalCost | currency: "$" }} /{{ - "month" | i18n + Total: {{ totalCost | currency: "$" }} / + {{ + getBillingCadenceLabel(activePlans.length > 0 ? activePlans[0] : null) | i18n }} @@ -63,15 +63,40 @@
    - +

    {{ "accountCredit" | i18n }}

    {{ subscription.accountCredit | currency: "$" }}

    {{ "creditAppliedDesc" | i18n }}

    -
    + + + +

    {{ "paymentMethod" | i18n }}

    +

    + {{ "noPaymentMethod" | i18n }} +

    + + + +

    + + {{ subscription.paymentSource.description }} + - {{ "unverified" | i18n }} +

    +
    + +
    - +

    {{ "taxInformation" | i18n }}

    {{ "taxInformationDesc" | i18n }}

    -
    +
    diff --git a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts index a6b27b9f3dd..3d9388877fd 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts @@ -2,39 +2,54 @@ // @ts-strict-ignore import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; -import { Subject, concatMap, takeUntil } from "rxjs"; +import { concatMap, lastValueFrom, Subject, takeUntil } from "rxjs"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; +import { PaymentMethodType } from "@bitwarden/common/billing/enums"; import { TaxInformation } from "@bitwarden/common/billing/models/domain"; import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; +import { VerifyBankAccountRequest } from "@bitwarden/common/billing/models/request/verify-bank-account.request"; import { ProviderPlanResponse, ProviderSubscriptionResponse, } from "@bitwarden/common/billing/models/response/provider-subscription-response"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { ToastService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; +import { BillingNotificationService } from "@bitwarden/web-vault/app/billing/services/billing-notification.service"; +import { + AdjustPaymentDialogComponent, + AdjustPaymentDialogResultType, +} from "@bitwarden/web-vault/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component"; @Component({ selector: "app-provider-subscription", templateUrl: "./provider-subscription.component.html", }) export class ProviderSubscriptionComponent implements OnInit, OnDestroy { - providerId: string; - subscription: ProviderSubscriptionResponse; + private providerId: string; + protected subscription: ProviderSubscriptionResponse; - firstLoaded = false; - loading: boolean; + protected firstLoaded = false; + protected loading: boolean; private destroy$ = new Subject(); - totalCost: number; - currentDate = new Date(); + protected totalCost: number; protected readonly TaxInformation = TaxInformation; + protected readonly allowProviderPaymentMethod$ = this.configService.getFeatureFlag$( + FeatureFlag.PM18794_ProviderPaymentMethod, + ); + constructor( private billingApiService: BillingApiServiceAbstraction, private i18nService: I18nService, private route: ActivatedRoute, + private billingNotificationService: BillingNotificationService, + private dialogService: DialogService, private toastService: ToastService, + private configService: ConfigService, ) {} async ngOnInit() { @@ -50,32 +65,58 @@ export class ProviderSubscriptionComponent implements OnInit, OnDestroy { .subscribe(); } - get isExpired() { - return this.subscription.status !== "active"; - } - - async load() { + protected async load() { if (this.loading) { return; } this.loading = true; - this.subscription = await this.billingApiService.getProviderSubscription(this.providerId); - this.totalCost = - ((100 - this.subscription.discountPercentage) / 100) * this.sumCost(this.subscription.plans); - this.loading = false; + try { + this.subscription = await this.billingApiService.getProviderSubscription(this.providerId); + this.totalCost = + ((100 - this.subscription.discountPercentage) / 100) * + this.sumCost(this.subscription.plans); + } catch (error) { + this.billingNotificationService.handleError(error); + } finally { + this.loading = false; + } } - updateTaxInformation = async (taxInformation: TaxInformation) => { - const request = ExpandedTaxInfoUpdateRequest.From(taxInformation); - await this.billingApiService.updateProviderTaxInformation(this.providerId, request); + protected updatePaymentMethod = async (): Promise => { + const dialogRef = AdjustPaymentDialogComponent.open(this.dialogService, { + data: { + initialPaymentMethod: this.subscription.paymentSource?.type, + providerId: this.providerId, + }, + }); + + const result = await lastValueFrom(dialogRef.closed); + + if (result === AdjustPaymentDialogResultType.Submitted) { + await this.load(); + } + }; + + protected updateTaxInformation = async (taxInformation: TaxInformation) => { + try { + const request = ExpandedTaxInfoUpdateRequest.From(taxInformation); + await this.billingApiService.updateProviderTaxInformation(this.providerId, request); + this.billingNotificationService.showSuccess(this.i18nService.t("updatedTaxInformation")); + } catch (error) { + this.billingNotificationService.handleError(error); + } + }; + + protected verifyBankAccount = async (request: VerifyBankAccountRequest): Promise => { + await this.billingApiService.verifyProviderBankAccount(this.providerId, request); this.toastService.showToast({ variant: "success", title: null, - message: this.i18nService.t("updatedTaxInformation"), + message: this.i18nService.t("verifiedBankAccount"), }); }; - getFormattedCost( + protected getFormattedCost( cost: number, seatMinimum: number, purchasedSeats: number, @@ -85,17 +126,21 @@ export class ProviderSubscriptionComponent implements OnInit, OnDestroy { return costPerSeat - (costPerSeat * discountPercentage) / 100; } - getFormattedPlanName(planName: string): string { + protected getFormattedPlanName(planName: string): string { const spaceIndex = planName.indexOf(" "); return planName.substring(0, spaceIndex); } - getFormattedSeatCount(seatMinimum: number, purchasedSeats: number): string { + protected getFormattedSeatCount(seatMinimum: number, purchasedSeats: number): string { const totalSeats = seatMinimum + purchasedSeats; return totalSeats > 1 ? totalSeats.toString() : ""; } - sumCost(plans: ProviderPlanResponse[]): number { + protected getFormattedPlanNameCadence(cadence: string) { + return cadence === "Annual" ? "annually" : "monthly"; + } + + private sumCost(plans: ProviderPlanResponse[]): number { return plans.reduce((acc, plan) => acc + plan.cost, 0); } @@ -104,7 +149,7 @@ export class ProviderSubscriptionComponent implements OnInit, OnDestroy { this.destroy$.complete(); } - get activePlans(): ProviderPlanResponse[] { + protected get activePlans(): ProviderPlanResponse[] { return this.subscription.plans.filter((plan) => { if (plan.purchasedSeats === 0) { return plan.seatMinimum > 0; @@ -113,4 +158,43 @@ export class ProviderSubscriptionComponent implements OnInit, OnDestroy { } }); } + + protected getBillingCadenceLabel(providerPlanResponse: ProviderPlanResponse): string { + if (providerPlanResponse == null || providerPlanResponse == undefined) { + return "month"; + } + + switch (providerPlanResponse.cadence) { + case "Monthly": + return "month"; + case "Annual": + return "year"; + default: + return "month"; + } + } + + protected get paymentSourceClasses() { + if (this.subscription.paymentSource == null) { + return []; + } + switch (this.subscription.paymentSource.type) { + case PaymentMethodType.Card: + return ["bwi-credit-card"]; + case PaymentMethodType.BankAccount: + return ["bwi-bank"]; + case PaymentMethodType.Check: + return ["bwi-money"]; + case PaymentMethodType.PayPal: + return ["bwi-paypal text-primary"]; + default: + return []; + } + } + + protected get updatePaymentSourceButtonText(): string { + const key = + this.subscription.paymentSource == null ? "addPaymentMethod" : "changePaymentMethod"; + return this.i18nService.t(key); + } } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/guards/sm-org-enabled.guard.ts b/bitwarden_license/bit-web/src/app/secrets-manager/guards/sm-org-enabled.guard.ts index a1f7564156c..d042c4d904e 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/guards/sm-org-enabled.guard.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/guards/sm-org-enabled.guard.ts @@ -1,7 +1,13 @@ import { inject } from "@angular/core"; import { ActivatedRouteSnapshot, CanActivateFn, createUrlTreeFromSnapshot } from "@angular/router"; +import { firstValueFrom } from "rxjs"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; /** @@ -10,13 +16,17 @@ import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.serv export const organizationEnabledGuard: CanActivateFn = async (route: ActivatedRouteSnapshot) => { const syncService = inject(SyncService); const orgService = inject(OrganizationService); + const accountService = inject(AccountService); /** Workaround to avoid service initialization race condition. */ if ((await syncService.getLastSync()) == null) { await syncService.fullSync(false); } - const org = await orgService.get(route.params.organizationId); + const userId = await firstValueFrom(getUserId(accountService.activeAccount$)); + const org = await firstValueFrom( + orgService.organizations$(userId).pipe(getOrganizationById(route.params.organizationId)), + ); if (org == null || !org.canAccessSecretsManager) { return createUrlTreeFromSnapshot(route, ["/"]); } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/guards/sm.guard.ts b/bitwarden_license/bit-web/src/app/secrets-manager/guards/sm.guard.ts index 2a36bf1cbbc..39576bc8dc6 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/guards/sm.guard.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/guards/sm.guard.ts @@ -5,8 +5,11 @@ import { createUrlTreeFromSnapshot, RouterStateSnapshot, } from "@angular/router"; +import { firstValueFrom } from "rxjs"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; /** @@ -18,13 +21,15 @@ export const canActivateSM: CanActivateFn = async ( ) => { const syncService = inject(SyncService); const orgService = inject(OrganizationService); + const accountService = inject(AccountService); /** Workaround to avoid service initialization race condition. */ if ((await syncService.getLastSync()) == null) { await syncService.fullSync(false); } - const orgs = await orgService.getAll(); + const userId = await firstValueFrom(getUserId(accountService.activeAccount$)); + const orgs = await firstValueFrom(orgService.organizations$(userId)); const smOrg = orgs.find((o) => o.canAccessSecretsManager); if (smOrg) { return createUrlTreeFromSnapshot(route, ["/sm", smOrg.id]); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.ts index adf01afd10c..6594b71a14c 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.ts @@ -15,8 +15,13 @@ import { takeUntil, } from "rxjs"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { SecretsManagerLogo } from "@bitwarden/web-vault/app/layouts/secrets-manager-logo"; import { OrganizationCounts } from "../models/view/counts.view"; @@ -41,6 +46,7 @@ export class NavigationComponent implements OnInit, OnDestroy { constructor( protected route: ActivatedRoute, private organizationService: OrganizationService, + private accountService: AccountService, private countService: CountService, private projectService: ProjectService, private secretService: SecretService, @@ -50,7 +56,15 @@ export class NavigationComponent implements OnInit, OnDestroy { ngOnInit() { const org$ = this.route.params.pipe( - concatMap((params) => this.organizationService.get(params.organizationId)), + concatMap((params) => + getUserId(this.accountService.activeAccount$).pipe( + switchMap((userId) => + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(params.organizationId)), + ), + ), + ), distinctUntilChanged(), takeUntil(this.destroy$), ); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts index a95192e0d91..698448c2d06 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts @@ -16,19 +16,27 @@ import { firstValueFrom, of, filter, + catchError, + from, } from "rxjs"; import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { DialogService } from "@bitwarden/components"; +import { BillingNotificationService } from "@bitwarden/web-vault/app/billing/services/billing-notification.service"; import { TrialFlowService } from "@bitwarden/web-vault/app/billing/services/trial-flow.service"; -import { FreeTrial } from "@bitwarden/web-vault/app/core/types/free-trial"; +import { FreeTrial } from "@bitwarden/web-vault/app/billing/types/free-trial"; import { OrganizationCounts } from "../models/view/counts.view"; import { ProjectListView } from "../models/view/project-list.view"; @@ -112,6 +120,7 @@ export class OverviewComponent implements OnInit, OnDestroy { private serviceAccountService: ServiceAccountService, private dialogService: DialogService, private organizationService: OrganizationService, + private accountService: AccountService, private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, private smOnboardingTasksService: SMOnboardingTasksService, @@ -120,6 +129,7 @@ export class OverviewComponent implements OnInit, OnDestroy { private organizationApiService: OrganizationApiServiceAbstraction, private trialFlowService: TrialFlowService, private organizationBillingService: OrganizationBillingServiceAbstraction, + private billingNotificationService: BillingNotificationService, ) {} ngOnInit() { @@ -130,7 +140,15 @@ export class OverviewComponent implements OnInit, OnDestroy { distinctUntilChanged(), ); - const org$ = orgId$.pipe(switchMap((orgId) => this.organizationService.get(orgId))); + const org$ = orgId$.pipe( + switchMap((orgId) => + getUserId(this.accountService.activeAccount$).pipe( + switchMap((userId) => + this.organizationService.organizations$(userId).pipe(getOrganizationById(orgId)), + ), + ), + ), + ); org$.pipe(takeUntil(this.destroy$)).subscribe((org) => { this.organizationId = org.id; @@ -147,12 +165,18 @@ export class OverviewComponent implements OnInit, OnDestroy { combineLatest([ of(org), this.organizationApiService.getSubscription(org.id), - this.organizationBillingService.getPaymentSource(org.id), + from(this.organizationBillingService.getPaymentSource(org.id)).pipe( + catchError((error: unknown) => { + this.billingNotificationService.handleError(error); + return of(null); + }), + ), ]), ), map(([org, sub, paymentSource]) => { return this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues(org, sub, paymentSource); }), + filter((result) => result !== null), takeUntil(this.destroy$), ); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.module.ts index b9c09a0d671..94d9ab2d8ee 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.module.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.module.ts @@ -1,8 +1,8 @@ import { NgModule } from "@angular/core"; import { BannerModule } from "@bitwarden/components"; +import { OnboardingModule } from "@bitwarden/web-vault/app/shared/components/onboarding/onboarding.module"; -import { OnboardingModule } from "../../../../../../apps/web/src/app/shared/components/onboarding/onboarding.module"; import { SecretsManagerSharedModule } from "../shared/sm-shared.module"; import { OverviewRoutingModule } from "./overview-routing.module"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts index 631f6409748..159b90d5432 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts @@ -3,13 +3,18 @@ import { TestBed } from "@angular/core/testing"; import { Router } from "@angular/router"; import { RouterTestingModule } from "@angular/router/testing"; import { MockProxy, mock } from "jest-mock-extended"; +import { of } from "rxjs"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { ToastService } from "@bitwarden/components"; +import { RouterService } from "@bitwarden/web-vault/app/core"; -import { RouterService } from "../../../../../../../apps/web/src/app/core/router.service"; import { ProjectView } from "../../models/view/project.view"; import { ProjectService } from "../project.service"; @@ -32,6 +37,8 @@ describe("Project Redirect Guard", () => { let i18nServiceMock: MockProxy; let toastService: MockProxy; let router: Router; + let accountService: FakeAccountService; + const userId = Utils.newGuid() as UserId; const smOrg1 = { id: "123", canAccessSecretsManager: true } as Organization; const projectView = { @@ -50,6 +57,7 @@ describe("Project Redirect Guard", () => { projectServiceMock = mock(); i18nServiceMock = mock(); toastService = mock(); + accountService = mockAccountServiceWith(userId); TestBed.configureTestingModule({ imports: [ @@ -71,6 +79,7 @@ describe("Project Redirect Guard", () => { ], providers: [ { provide: OrganizationService, useValue: organizationService }, + { provide: AccountService, useValue: accountService }, { provide: RouterService, useValue: routerService }, { provide: ProjectService, useValue: projectServiceMock }, { provide: I18nService, useValue: i18nServiceMock }, @@ -83,7 +92,7 @@ describe("Project Redirect Guard", () => { it("redirects to sm/{orgId}/projects/{projectId} if project exists", async () => { // Arrange - organizationService.getAll.mockResolvedValue([smOrg1]); + organizationService.organizations$.mockReturnValue(of([smOrg1])); projectServiceMock.getByProjectId.mockReturnValue(Promise.resolve(projectView)); // Act @@ -95,7 +104,7 @@ describe("Project Redirect Guard", () => { it("redirects to sm/projects if project does not exist", async () => { // Arrange - organizationService.getAll.mockResolvedValue([smOrg1]); + organizationService.organizations$.mockReturnValue(of([smOrg1])); // Act await router.navigateByUrl("sm/123/projects/124"); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project.service.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project.service.ts index ee2395b3f83..8c9f894f8f6 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project.service.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project.service.ts @@ -4,8 +4,8 @@ import { Injectable } from "@angular/core"; import { Subject } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { KeyService } from "@bitwarden/key-management"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.html index 1ab8b7e0196..d9919ef6bac 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.html @@ -2,7 +2,7 @@
    - - - - - {{ "application" | i18n }} - {{ "atRiskPasswords" | i18n }} - {{ "totalPasswords" | i18n }} - {{ "atRiskMembers" | i18n }} - {{ "totalMembers" | i18n }} - - - - - - - - - {{ r.applicationName }} - - - - {{ r.atRiskPasswordCount }} - - - - - {{ r.passwordCount }} - - - - - {{ r.atRiskMemberCount }} - - - - {{ r.memberCount }} - - - - + +
    diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts index f4d3656071d..ee51a134e31 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts @@ -1,19 +1,26 @@ -import { Component, DestroyRef, OnDestroy, OnInit, inject } from "@angular/core"; +import { Component, DestroyRef, inject, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormControl } from "@angular/forms"; import { ActivatedRoute } from "@angular/router"; -import { debounceTime, map, Observable, of, Subscription } from "rxjs"; +import { combineLatest, debounceTime, firstValueFrom, map, Observable, of, skipWhile } from "rxjs"; import { + CriticalAppsService, RiskInsightsDataService, RiskInsightsReportService, } from "@bitwarden/bit-common/tools/reports/risk-insights"; import { ApplicationHealthReportDetail, + ApplicationHealthReportDetailWithCriticalFlag, ApplicationHealthReportSummary, } from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -30,6 +37,7 @@ import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.mod import { SharedModule } from "@bitwarden/web-vault/app/shared"; import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module"; +import { AppTableRowScrollableComponent } from "./app-table-row-scrollable.component"; import { ApplicationsLoadingComponent } from "./risk-insights-loading.component"; @Component({ @@ -44,18 +52,23 @@ import { ApplicationsLoadingComponent } from "./risk-insights-loading.component" PipesModule, NoItemsModule, SharedModule, + AppTableRowScrollableComponent, ], }) -export class AllApplicationsComponent implements OnInit, OnDestroy { - protected dataSource = new TableDataSource(); - protected selectedIds: Set = new Set(); +export class AllApplicationsComponent implements OnInit { + protected dataSource = new TableDataSource(); + protected selectedUrls: Set = new Set(); protected searchControl = new FormControl("", { nonNullable: true }); protected loading = true; - protected organization = {} as Organization; + protected organization = new Organization(); noItemsIcon = Icons.Security; protected markingAsCritical = false; - protected applicationSummary = {} as ApplicationHealthReportSummary; - private subscription = new Subscription(); + protected applicationSummary: ApplicationHealthReportSummary = { + totalMemberCount: 0, + totalAtRiskMemberCount: 0, + totalApplicationCount: 0, + totalAtRiskApplicationCount: 0, + }; destroyRef = inject(DestroyRef); isLoading$: Observable = of(false); @@ -66,30 +79,45 @@ export class AllApplicationsComponent implements OnInit, OnDestroy { FeatureFlag.CriticalApps, ); - const organizationId = this.activatedRoute.snapshot.paramMap.get("organizationId"); + const organizationId = this.activatedRoute.snapshot.paramMap.get("organizationId") ?? ""; + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); if (organizationId) { - this.organization = await this.organizationService.get(organizationId); - this.subscription = this.dataService.applications$ + const organization$ = this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(organizationId)); + + combineLatest([ + this.dataService.applications$, + this.criticalAppsService.getAppsListForOrg(organizationId), + organization$, + ]) .pipe( - map((applications) => { - if (applications) { - this.dataSource.data = applications; - this.applicationSummary = - this.reportService.generateApplicationsSummary(applications); - } - }), takeUntilDestroyed(this.destroyRef), + skipWhile(([_, __, organization]) => !organization), + map(([applications, criticalApps, organization]) => { + const criticalUrls = criticalApps.map((ca) => ca.uri); + const data = applications?.map((app) => ({ + ...app, + isMarkedAsCritical: criticalUrls.includes(app.applicationName), + })) as ApplicationHealthReportDetailWithCriticalFlag[]; + return { data, organization }; + }), ) - .subscribe(); + .subscribe(({ data, organization }) => { + if (data) { + this.dataSource.data = data; + this.applicationSummary = this.reportService.generateApplicationsSummary(data); + } + if (organization) { + this.organization = organization; + } + }); + this.isLoading$ = this.dataService.isLoading$; } } - ngOnDestroy(): void { - this.subscription?.unsubscribe(); - } - constructor( protected cipherService: CipherService, protected i18nService: I18nService, @@ -99,6 +127,8 @@ export class AllApplicationsComponent implements OnInit, OnDestroy { protected dataService: RiskInsightsDataService, protected organizationService: OrganizationService, protected reportService: RiskInsightsReportService, + private accountService: AccountService, + protected criticalAppsService: CriticalAppsService, ) { this.searchControl.valueChanges .pipe(debounceTime(200), takeUntilDestroyed()) @@ -114,33 +144,66 @@ export class AllApplicationsComponent implements OnInit, OnDestroy { }); }; + isMarkedAsCriticalItem(applicationName: string) { + return this.selectedUrls.has(applicationName); + } + markAppsAsCritical = async () => { - // TODO: Send to API once implemented this.markingAsCritical = true; - return new Promise((resolve) => { - setTimeout(() => { - this.selectedIds.clear(); - this.toastService.showToast({ - variant: "success", - title: "", - message: this.i18nService.t("appsMarkedAsCritical"), - }); - resolve(true); - this.markingAsCritical = false; - }, 1000); - }); + + try { + await this.criticalAppsService.setCriticalApps( + this.organization.id, + Array.from(this.selectedUrls), + ); + + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("appsMarkedAsCritical"), + }); + } finally { + this.selectedUrls.clear(); + this.markingAsCritical = false; + } }; trackByFunction(_: number, item: ApplicationHealthReportDetail) { return item.applicationName; } - onCheckboxChange(id: number, event: Event) { + showAppAtRiskMembers = async (applicationName: string) => { + const info = { + members: + this.dataSource.data.find((app) => app.applicationName === applicationName) + ?.atRiskMemberDetails ?? [], + applicationName, + }; + this.dataService.setDrawerForAppAtRiskMembers(info, applicationName); + }; + + showOrgAtRiskMembers = async (invokerId: string) => { + const dialogData = this.reportService.generateAtRiskMemberList(this.dataSource.data); + this.dataService.setDrawerForOrgAtRiskMembers(dialogData, invokerId); + }; + + showOrgAtRiskApps = async (invokerId: string) => { + const data = this.reportService.generateAtRiskApplicationList(this.dataSource.data); + this.dataService.setDrawerForOrgAtRiskApps(data, invokerId); + }; + + onCheckboxChange = (applicationName: string, event: Event) => { const isChecked = (event.target as HTMLInputElement).checked; if (isChecked) { - this.selectedIds.add(id); + this.selectedUrls.add(applicationName); } else { - this.selectedIds.delete(id); + this.selectedUrls.delete(applicationName); } - } + }; + + getSelectedUrls = () => Array.from(this.selectedUrls); + + isDrawerOpenForTableRow = (applicationName: string): boolean => { + return this.dataService.drawerInvokerId === applicationName; + }; } diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-table-row-scrollable.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-table-row-scrollable.component.html new file mode 100644 index 00000000000..e89acf1ac66 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-table-row-scrollable.component.html @@ -0,0 +1,98 @@ + + + + + {{ "application" | i18n }} + {{ "atRiskPasswords" | i18n }} + {{ "totalPasswords" | i18n }} + {{ "atRiskMembers" | i18n }} + {{ "totalMembers" | i18n }} + + + + + + + + + + + {{ row.applicationName }} + + + + {{ row.atRiskPasswordCount }} + + + + + {{ row.passwordCount }} + + + + + {{ row.atRiskMemberCount }} + + + + {{ row.memberCount }} + + + + + + + + + + + diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-table-row-scrollable.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-table-row-scrollable.component.ts new file mode 100644 index 00000000000..b0e99580919 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-table-row-scrollable.component.ts @@ -0,0 +1,26 @@ +import { CommonModule } from "@angular/common"; +import { Component, Input } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { ApplicationHealthReportDetailWithCriticalFlag } from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health"; +import { MenuModule, TableDataSource, TableModule } from "@bitwarden/components"; +import { SharedModule } from "@bitwarden/web-vault/app/shared"; +import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module"; + +@Component({ + selector: "app-table-row-scrollable", + standalone: true, + imports: [CommonModule, JslibModule, TableModule, SharedModule, PipesModule, MenuModule], + templateUrl: "./app-table-row-scrollable.component.html", +}) +export class AppTableRowScrollableComponent { + @Input() dataSource!: TableDataSource; + @Input() showRowMenuForCriticalApps: boolean = false; + @Input() showRowCheckBox: boolean = false; + @Input() selectedUrls: Set = new Set(); + @Input() isCriticalAppsFeatureEnabled: boolean = false; + @Input() isDrawerIsOpenForThisRecord!: (applicationName: string) => boolean; + @Input() showAppAtRiskMembers!: (applicationName: string) => void; + @Input() unmarkAsCriticalApp!: (applicationName: string) => void; + @Input() checkboxChange!: (applicationName: string, $event: Event) => void; +} diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/application-table.mock.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/application-table.mock.ts deleted file mode 100644 index 4df363ab2c7..00000000000 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/application-table.mock.ts +++ /dev/null @@ -1,50 +0,0 @@ -export const applicationTableMockData = [ - { - id: 1, - name: "google.com", - atRiskPasswords: 4, - totalPasswords: 10, - atRiskMembers: 2, - totalMembers: 5, - }, - { - id: 2, - name: "facebook.com", - atRiskPasswords: 3, - totalPasswords: 8, - atRiskMembers: 1, - totalMembers: 3, - }, - { - id: 3, - name: "twitter.com", - atRiskPasswords: 2, - totalPasswords: 6, - atRiskMembers: 0, - totalMembers: 2, - }, - { - id: 4, - name: "linkedin.com", - atRiskPasswords: 1, - totalPasswords: 4, - atRiskMembers: 0, - totalMembers: 1, - }, - { - id: 5, - name: "instagram.com", - atRiskPasswords: 0, - totalPasswords: 2, - atRiskMembers: 0, - totalMembers: 0, - }, - { - id: 6, - name: "tiktok.com", - atRiskPasswords: 0, - totalPasswords: 1, - atRiskMembers: 0, - totalMembers: 0, - }, -]; diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/critical-applications.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/critical-applications.component.html index 1c503f3d786..d9ad4dce0ff 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/critical-applications.component.html +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/critical-applications.component.html @@ -9,7 +9,7 @@
    -

    +

    {{ "noCriticalAppsTitle" | i18n }}

    @@ -28,24 +28,45 @@

    {{ "criticalApplications" | i18n }}

    -
    @@ -56,44 +77,14 @@ [formControl]="searchControl" >
    - - - - - {{ "application" | i18n }} - {{ "atRiskPasswords" | i18n }} - {{ "totalPasswords" | i18n }} - {{ "atRiskMembers" | i18n }} - {{ "totalMembers" | i18n }} - - - - - - - - - {{ r.name }} - - - - {{ r.atRiskPasswords }} - - - - - {{ r.totalPasswords }} - - - - - {{ r.atRiskMembers }} - - - - {{ r.totalMembers }} - - - - + +
    diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/critical-applications.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/critical-applications.component.ts index 99f68aa9c72..af5c9a90015 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/critical-applications.component.ts +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/critical-applications.component.ts @@ -4,49 +4,94 @@ import { Component, DestroyRef, inject, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormControl } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; -import { debounceTime, map } from "rxjs"; +import { combineLatest, debounceTime, map } from "rxjs"; +import { + CriticalAppsService, + RiskInsightsDataService, + RiskInsightsReportService, +} from "@bitwarden/bit-common/tools/reports/risk-insights"; +import { + ApplicationHealthReportDetailWithCriticalFlag, + ApplicationHealthReportSummary, +} from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { SearchModule, TableDataSource, NoItemsModule, Icons } from "@bitwarden/components"; +import { CipherId, OrganizationId } from "@bitwarden/common/types/guid"; +import { + Icons, + NoItemsModule, + SearchModule, + TableDataSource, + ToastService, +} from "@bitwarden/components"; import { CardComponent } from "@bitwarden/tools-card"; +import { SecurityTaskType } from "@bitwarden/vault"; import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module"; import { SharedModule } from "@bitwarden/web-vault/app/shared"; import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module"; -import { applicationTableMockData } from "./application-table.mock"; +import { CreateTasksRequest } from "../../vault/services/abstractions/admin-task.abstraction"; +import { DefaultAdminTaskService } from "../../vault/services/default-admin-task.service"; + +import { AppTableRowScrollableComponent } from "./app-table-row-scrollable.component"; import { RiskInsightsTabType } from "./risk-insights.component"; @Component({ standalone: true, selector: "tools-critical-applications", templateUrl: "./critical-applications.component.html", - imports: [CardComponent, HeaderModule, SearchModule, NoItemsModule, PipesModule, SharedModule], + imports: [ + CardComponent, + HeaderModule, + SearchModule, + NoItemsModule, + PipesModule, + SharedModule, + AppTableRowScrollableComponent, + ], + providers: [DefaultAdminTaskService], }) export class CriticalApplicationsComponent implements OnInit { - protected dataSource = new TableDataSource(); + protected dataSource = new TableDataSource(); protected selectedIds: Set = new Set(); protected searchControl = new FormControl("", { nonNullable: true }); private destroyRef = inject(DestroyRef); protected loading = false; protected organizationId: string; + protected applicationSummary = {} as ApplicationHealthReportSummary; noItemsIcon = Icons.Security; - // MOCK DATA - protected mockData = applicationTableMockData; - protected mockAtRiskMembersCount = 0; - protected mockAtRiskAppsCount = 0; - protected mockTotalMembersCount = 0; - protected mockTotalAppsCount = 0; + isNotificationsFeatureEnabled: boolean = false; + enableRequestPasswordChange = false; - ngOnInit() { - this.activatedRoute.paramMap + async ngOnInit() { + this.isNotificationsFeatureEnabled = await this.configService.getFeatureFlag( + FeatureFlag.EnableRiskInsightsNotifications, + ); + this.organizationId = this.activatedRoute.snapshot.paramMap.get("organizationId") ?? ""; + combineLatest([ + this.dataService.applications$, + this.criticalAppsService.getAppsListForOrg(this.organizationId), + ]) .pipe( takeUntilDestroyed(this.destroyRef), - map(async (params) => { - this.organizationId = params.get("organizationId"); - // TODO: use organizationId to fetch data + map(([applications, criticalApps]) => { + const criticalUrls = criticalApps.map((ca) => ca.uri); + const data = applications?.map((app) => ({ + ...app, + isMarkedAsCritical: criticalUrls.includes(app.applicationName), + })) as ApplicationHealthReportDetailWithCriticalFlag[]; + return data?.filter((app) => app.isMarkedAsCritical); }), ) - .subscribe(); + .subscribe((applications) => { + if (applications) { + this.dataSource.data = applications; + this.applicationSummary = this.reportService.generateApplicationsSummary(applications); + this.enableRequestPasswordChange = this.applicationSummary.totalAtRiskMemberCount > 0; + } + }); } goToAllAppsTab = async () => { @@ -56,14 +101,96 @@ export class CriticalApplicationsComponent implements OnInit { }); }; + unmarkAsCriticalApp = async (hostname: string) => { + try { + await this.criticalAppsService.dropCriticalApp( + this.organizationId as OrganizationId, + hostname, + ); + } catch { + this.toastService.showToast({ + message: this.i18nService.t("unexpectedError"), + variant: "error", + title: this.i18nService.t("error"), + }); + return; + } + + this.toastService.showToast({ + message: this.i18nService.t("criticalApplicationSuccessfullyUnmarked"), + variant: "success", + title: this.i18nService.t("success"), + }); + this.dataSource.data = this.dataSource.data.filter((app) => app.applicationName !== hostname); + }; + + async requestPasswordChange() { + const apps = this.dataSource.data; + const cipherIds = apps + .filter((_) => _.atRiskPasswordCount > 0) + .flatMap((app) => app.atRiskMemberDetails.map((member) => member.cipherId)); + const distinctCipherIds = Array.from(new Set(cipherIds)); + const tasks: CreateTasksRequest[] = distinctCipherIds.map((cipherId) => ({ + cipherId: cipherId as CipherId, + type: SecurityTaskType.UpdateAtRiskCredential, + })); + + try { + await this.adminTaskService.bulkCreateTasks(this.organizationId as OrganizationId, tasks); + 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"), + }); + } + } + constructor( - protected i18nService: I18nService, protected activatedRoute: ActivatedRoute, protected router: Router, + protected toastService: ToastService, + protected dataService: RiskInsightsDataService, + protected criticalAppsService: CriticalAppsService, + protected reportService: RiskInsightsReportService, + protected i18nService: I18nService, + private configService: ConfigService, + private adminTaskService: DefaultAdminTaskService, ) { - this.dataSource.data = []; //applicationTableMockData; this.searchControl.valueChanges .pipe(debounceTime(200), takeUntilDestroyed()) .subscribe((v) => (this.dataSource.filter = v)); } + + showAppAtRiskMembers = async (applicationName: string) => { + const data = { + members: + this.dataSource.data.find((app) => app.applicationName === applicationName) + ?.atRiskMemberDetails ?? [], + applicationName, + }; + this.dataService.setDrawerForAppAtRiskMembers(data, applicationName); + }; + + showOrgAtRiskMembers = async (invokerId: string) => { + const data = this.reportService.generateAtRiskMemberList(this.dataSource.data); + this.dataService.setDrawerForOrgAtRiskMembers(data, invokerId); + }; + + showOrgAtRiskApps = async (invokerId: string) => { + const data = this.reportService.generateAtRiskApplicationList(this.dataSource.data); + this.dataService.setDrawerForOrgAtRiskApps(data, invokerId); + }; + + trackByFunction(_: number, item: ApplicationHealthReportDetailWithCriticalFlag) { + return item.applicationName; + } + isDrawerOpenForTableRow = (applicationName: string) => { + return this.dataService.drawerInvokerId === applicationName; + }; } diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health-members-uri.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health-members-uri.component.html index bdccc523e76..936d92d8701 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health-members-uri.component.html +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health-members-uri.component.html @@ -9,47 +9,43 @@ {{ "loading" | i18n }}
    - + - - {{ "application" | i18n }} - {{ "weakness" | i18n }} - {{ "timesReused" | i18n }} - {{ "timesExposed" | i18n }} - {{ "totalMembers" | i18n }} - + {{ "application" | i18n }} + {{ "weakness" | i18n }} + {{ "timesReused" | i18n }} + {{ "timesExposed" | i18n }} + {{ "totalMembers" | i18n }} - - - - - {{ r.hostURI }} - - - - - {{ passwordStrengthMap.get(r.id)[0] | i18n }} - - - - - {{ "reusedXTimes" | i18n: passwordUseMap.get(r.login.password) }} - - - - - {{ "exposedXTimes" | i18n: exposedPasswordMap.get(r.id) }} - - - - {{ totalMembersMap.get(r.id) || 0 }} - - + + + + {{ row.hostURI }} + + + + + {{ passwordStrengthMap.get(row.id)[0] | i18n }} + + + + + {{ "reusedXTimes" | i18n: passwordUseMap.get(row.login.password) }} + + + + + {{ "exposedXTimes" | i18n: exposedPasswordMap.get(row.id) }} + + + + {{ totalMembersMap.get(row.id) || 0 }} + - +
    diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health-members-uri.component.spec.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health-members-uri.component.spec.ts index 1e9e4171bc3..852e9adafb3 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health-members-uri.component.spec.ts +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health-members-uri.component.spec.ts @@ -10,8 +10,12 @@ import { import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { mockAccountServiceWith } from "@bitwarden/common/spec"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { TableModule } from "@bitwarden/components"; import { LooseComponentsModule } from "@bitwarden/web-vault/app/shared"; @@ -24,6 +28,7 @@ describe("PasswordHealthMembersUriComponent", () => { let fixture: ComponentFixture; let cipherServiceMock: MockProxy; const passwordHealthServiceMock = mock(); + const userId = Utils.newGuid() as UserId; const activeRouteParams = convertToParamMap({ organizationId: "orgId" }); @@ -36,6 +41,7 @@ describe("PasswordHealthMembersUriComponent", () => { { provide: I18nService, useValue: mock() }, { provide: AuditService, useValue: mock() }, { provide: OrganizationService, useValue: mock() }, + { provide: AccountService, useValue: mockAccountServiceWith(userId) }, { provide: PasswordStrengthServiceAbstraction, useValue: mock(), diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health-members.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health-members.component.html index 7f9b37f2a82..5c980f75a81 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health-members.component.html +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health-members.component.html @@ -8,57 +8,53 @@ {{ "loading" | i18n }}
    - + - - - {{ "name" | i18n }} - {{ "weakness" | i18n }} - {{ "timesReused" | i18n }} - {{ "timesExposed" | i18n }} - {{ "totalMembers" | i18n }} - + + {{ "name" | i18n }} + {{ "weakness" | i18n }} + {{ "timesReused" | i18n }} + {{ "timesExposed" | i18n }} + {{ "totalMembers" | i18n }} - - - - - - - - {{ r.name }} - -
    - {{ r.subTitle }} - - - - {{ passwordStrengthMap.get(r.id)[0] | i18n }} - - - - - {{ "reusedXTimes" | i18n: passwordUseMap.get(r.login.password) }} - - - - - {{ "exposedXTimes" | i18n: exposedPasswordMap.get(r.id) }} - - - - {{ totalMembersMap.get(r.id) || 0 }} - - + + + + + + + {{ row.name }} + +
    + {{ row.subTitle }} + + + + {{ passwordStrengthMap.get(row.id)[0] | i18n }} + + + + + {{ "reusedXTimes" | i18n: passwordUseMap.get(row.login.password) }} + + + + + {{ "exposedXTimes" | i18n: exposedPasswordMap.get(row.id) }} + + + + {{ totalMembersMap.get(row.id) || 0 }} +
    -
    +
    diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health.component.html index aeaa9f33197..b798a75ab3a 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health.component.html +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health.component.html @@ -9,49 +9,45 @@ {{ "loading" | i18n }}
    - + - - - {{ "name" | i18n }} - {{ "weakness" | i18n }} - {{ "timesReused" | i18n }} - {{ "timesExposed" | i18n }} - + + {{ "name" | i18n }} + {{ "weakness" | i18n }} + {{ "timesReused" | i18n }} + {{ "timesExposed" | i18n }} - - - - - - - - {{ r.name }} - -
    - {{ r.subTitle }} - - - - {{ r.weakPasswordDetail?.detailValue.label | i18n }} - - - - - {{ "reusedXTimes" | i18n: passwordUseMap.get(r.login.password) }} - - - - - {{ "exposedXTimes" | i18n: r.exposedPasswordDetail?.exposedXTimes }} - - - + + + + + + + {{ row.name }} + +
    + {{ row.subTitle }} + + + + {{ row.weakPasswordDetail?.detailValue.label | i18n }} + + + + + {{ "reusedXTimes" | i18n: passwordUseMap.get(row.login.password) }} + + + + + {{ "exposedXTimes" | i18n: row.exposedPasswordDetail?.exposedXTimes }} + +
    -
    +
    diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights-loading.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights-loading.component.html index d6f945bfb92..4e77838229e 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights-loading.component.html +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights-loading.component.html @@ -1,6 +1,6 @@
    diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.html index 7fe320ede6a..397e2a630de 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.html +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.html @@ -1,57 +1,132 @@ -
    {{ "accessIntelligence" | i18n }}
    -

    {{ "riskInsights" | i18n }}

    -
    - {{ "reviewAtRiskPasswords" | i18n }} -
    -
    - - {{ - "dataLastUpdated" | i18n: (dataLastUpdated$ | async | date: "MMMM d, y 'at' h:mm a") - }} - - - {{ "refresh" | i18n }} - - - + +
    + {{ "accessIntelligence" | i18n }} +
    +

    {{ "riskInsights" | i18n }}

    +
    + {{ "reviewAtRiskPasswords" | i18n }} +
    +
    + + {{ + "dataLastUpdated" | i18n: (dataLastUpdated$ | async | date: "MMMM d, y 'at' h:mm a") + }} + + + {{ "refresh" | i18n }} + + + + - -
    - - - - - - - - {{ "criticalApplicationsWithCount" | i18n: criticalAppsCount }} - - - - - - - - - - - - - +
    + + + + + + + + {{ "criticalApplicationsWithCount" | i18n: (criticalApps$ | async)?.length ?? 0 }} + + + + + + + + + + + + + + + + + + + + {{ + "atRiskMembersDescription" | i18n + }} +
    +
    {{ "email" | i18n }}
    +
    {{ "atRiskPasswords" | i18n }}
    +
    + +
    +
    {{ member.email }}
    +
    {{ member.atRiskPasswordCount }}
    +
    +
    +
    +
    + + + + + +
    + {{ "atRiskMembersWithCount" | i18n: dataService.appAtRiskMembers.members.length }} +
    +
    + {{ + "atRiskMembersDescriptionWithApp" | i18n: dataService.appAtRiskMembers.applicationName + }} +
    +
    + +
    {{ member.email }}
    +
    +
    +
    +
    + + + + + + + {{ + "atRiskApplicationsDescription" | i18n + }} +
    +
    {{ "application" | i18n }}
    +
    {{ "atRiskPasswords" | i18n }}
    +
    + +
    +
    {{ app.applicationName }}
    +
    {{ app.atRiskPasswordCount }}
    +
    +
    +
    +
    +
    +
    diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.ts index 75601994c70..68ec7bb2496 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.ts +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.ts @@ -2,16 +2,32 @@ import { CommonModule } from "@angular/common"; import { Component, DestroyRef, OnInit, inject } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ActivatedRoute, Router } from "@angular/router"; -import { Observable, EMPTY } from "rxjs"; +import { EMPTY, Observable } from "rxjs"; import { map, switchMap } from "rxjs/operators"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { RiskInsightsDataService } from "@bitwarden/bit-common/tools/reports/risk-insights"; -import { ApplicationHealthReportDetail } from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health"; +import { + CriticalAppsService, + RiskInsightsDataService, +} from "@bitwarden/bit-common/tools/reports/risk-insights"; +import { + ApplicationHealthReportDetail, + DrawerType, + PasswordHealthReportApplicationsResponse, +} from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { devFlagEnabled } from "@bitwarden/common/platform/misc/flags"; -import { AsyncActionsModule, ButtonModule, TabsModule } from "@bitwarden/components"; +import { OrganizationId } from "@bitwarden/common/types/guid"; +import { + AsyncActionsModule, + ButtonModule, + DrawerBodyComponent, + DrawerComponent, + DrawerHeaderComponent, + LayoutComponent, + TabsModule, +} from "@bitwarden/components"; import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module"; import { AllApplicationsComponent } from "./all-applications.component"; @@ -43,6 +59,10 @@ export enum RiskInsightsTabType { PasswordHealthMembersURIComponent, NotifiedMembersTableComponent, TabsModule, + DrawerComponent, + DrawerBodyComponent, + DrawerHeaderComponent, + LayoutComponent, ], }) export class RiskInsightsComponent implements OnInit { @@ -51,6 +71,7 @@ export class RiskInsightsComponent implements OnInit { dataLastUpdated: Date = new Date(); isCriticalAppsFeatureEnabled: boolean = false; + criticalApps$: Observable = new Observable(); showDebugTabs: boolean = false; appsCount: number = 0; @@ -68,11 +89,14 @@ export class RiskInsightsComponent implements OnInit { private route: ActivatedRoute, private router: Router, private configService: ConfigService, - private dataService: RiskInsightsDataService, + protected dataService: RiskInsightsDataService, + private criticalAppsService: CriticalAppsService, ) { this.route.queryParams.pipe(takeUntilDestroyed()).subscribe(({ tabIndex }) => { this.tabIndex = !isNaN(Number(tabIndex)) ? Number(tabIndex) : RiskInsightsTabType.AllApps; }); + const orgId = this.route.snapshot.paramMap.get("organizationId") ?? ""; + this.criticalApps$ = this.criticalAppsService.getAppsListForOrg(orgId); } async ngOnInit() { @@ -104,6 +128,7 @@ export class RiskInsightsComponent implements OnInit { if (applications) { this.appsCount = applications.length; } + this.criticalAppsService.setOrganizationId(this.organizationId as OrganizationId); }, }); } @@ -124,5 +149,13 @@ export class RiskInsightsComponent implements OnInit { queryParams: { tabIndex: newIndex }, queryParamsHandling: "merge", }); + + // close drawer when tabs are changed + this.dataService.closeDrawer(); + } + + // Get a list of drawer types + get drawerTypes(): typeof DrawerType { + return DrawerType; } } diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.html b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.html index b50f197a110..f2e550cb68e 100644 --- a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.html +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.html @@ -3,9 +3,16 @@ [formControl]="searchControl" [placeholder]="'searchMembers' | i18n" class="tw-grow" + *ngIf="!(isLoading$ | async)" > - @@ -17,7 +24,23 @@

    - + +
    + +

    {{ "loading" | i18n }}

    +
    +
    + + {{ "members" | i18n }} @@ -26,25 +49,23 @@ {{ "items" | i18n }} - - - -
    - -
    - + + +
    + +
    + -
    - {{ r.email }} -
    +
    + {{ row.email }}
    - - {{ r.groupsCount }} - {{ r.collectionsCount }} - {{ r.itemsCount }} - +
    + + {{ row.groupsCount }} + {{ row.collectionsCount }} + {{ row.itemsCount }}
    - + diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.ts b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.ts index 6aaaf1a4066..a05f0e6d4d3 100644 --- a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.ts +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.ts @@ -4,7 +4,7 @@ import { Component, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormControl } from "@angular/forms"; import { ActivatedRoute } from "@angular/router"; -import { debounceTime, firstValueFrom, lastValueFrom } from "rxjs"; +import { BehaviorSubject, debounceTime, firstValueFrom, lastValueFrom } from "rxjs"; import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe"; import { safeProvider } from "@bitwarden/angular/platform/utils/safe-provider"; @@ -48,6 +48,7 @@ export class MemberAccessReportComponent implements OnInit { protected searchControl = new FormControl("", { nonNullable: true }); protected organizationId: OrganizationId; protected orgIsOnSecretsManagerStandalone: boolean; + protected isLoading$ = new BehaviorSubject(true); constructor( private route: ActivatedRoute, @@ -64,6 +65,8 @@ export class MemberAccessReportComponent implements OnInit { } async ngOnInit() { + this.isLoading$.next(true); + const params = await firstValueFrom(this.route.params); this.organizationId = params.organizationId; @@ -74,6 +77,8 @@ export class MemberAccessReportComponent implements OnInit { this.orgIsOnSecretsManagerStandalone = billingMetadata.isOnSecretsManagerStandalone; await this.load(); + + this.isLoading$.next(false); } async load() { @@ -93,17 +98,16 @@ export class MemberAccessReportComponent implements OnInit { }); }; - edit = async (user: MemberAccessReportView | null): Promise => { + edit = async (user: MemberAccessReportView): Promise => { const dialog = openUserAddEditDialog(this.dialogService, { data: { + kind: "Edit", name: this.userNamePipe.transform(user), organizationId: this.organizationId, - organizationUserId: user != null ? user.userGuid : null, - allOrganizationUserEmails: this.dataSource.data?.map((user) => user.email) ?? [], - usesKeyConnector: user?.usesKeyConnector, + organizationUserId: user.userGuid, + usesKeyConnector: user.usesKeyConnector, isOnSecretsManagerStandalone: this.orgIsOnSecretsManagerStandalone, initialTab: MemberDialogTab.Role, - numConfirmedMembers: this.dataSource.data.length, }, }); diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.abstraction.ts b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.abstraction.ts index 9919da8d08e..cf2f3b6417b 100644 --- a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.abstraction.ts +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.abstraction.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { OrganizationId } from "@bitwarden/common/src/types/guid"; +import { OrganizationId } from "@bitwarden/common/types/guid"; import { MemberAccessExportItem } from "../view/member-access-export.view"; import { MemberAccessReportView } from "../view/member-access-report.view"; diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.spec.ts b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.spec.ts index 6aab54f77d5..7d6beca48ec 100644 --- a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.spec.ts +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.spec.ts @@ -1,7 +1,7 @@ import { mock } from "jest-mock-extended"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { OrganizationId } from "@bitwarden/common/src/types/guid"; +import { OrganizationId } from "@bitwarden/common/types/guid"; import { MemberAccessReportApiService } from "./member-access-report-api.service"; import { memberAccessReportsMock } from "./member-access-report.mock"; diff --git a/bitwarden_license/bit-web/src/app/vault/services/abstractions/admin-task.abstraction.ts b/bitwarden_license/bit-web/src/app/vault/services/abstractions/admin-task.abstraction.ts new file mode 100644 index 00000000000..014c9daa783 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/vault/services/abstractions/admin-task.abstraction.ts @@ -0,0 +1,34 @@ +import { CipherId, OrganizationId } from "@bitwarden/common/types/guid"; +import { SecurityTask, SecurityTaskStatus, SecurityTaskType } from "@bitwarden/vault"; + +/** + * Request type for creating tasks. + * @property cipherId - Optional. The ID of the cipher to create the task for. + * @property type - The type of task to create. Currently defined as "updateAtRiskCredential". + */ +export type CreateTasksRequest = Readonly<{ + cipherId?: CipherId; + type: SecurityTaskType.UpdateAtRiskCredential; +}>; + +export abstract class AdminTaskService { + /** + * Retrieves all tasks for a given organization. + * @param organizationId - The ID of the organization to retrieve tasks for. + * @param status - Optional. The status of the tasks to retrieve. + */ + abstract getAllTasks( + organizationId: OrganizationId, + status?: SecurityTaskStatus | undefined, + ): Promise; + + /** + * Creates multiple tasks for a given organization and sends out notifications to applicable users. + * @param organizationId - The ID of the organization to create tasks for. + * @param tasks - The tasks to create. + */ + abstract bulkCreateTasks( + organizationId: OrganizationId, + tasks: CreateTasksRequest[], + ): Promise; +} diff --git a/bitwarden_license/bit-web/src/app/vault/services/default-admin-task.service.spec.ts b/bitwarden_license/bit-web/src/app/vault/services/default-admin-task.service.spec.ts new file mode 100644 index 00000000000..49a4c16e159 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/vault/services/default-admin-task.service.spec.ts @@ -0,0 +1,65 @@ +import { MockProxy, mock } from "jest-mock-extended"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { CipherId, OrganizationId } from "@bitwarden/common/types/guid"; +import { SecurityTaskStatus, SecurityTaskType } from "@bitwarden/vault"; + +import { CreateTasksRequest } from "./abstractions/admin-task.abstraction"; +import { DefaultAdminTaskService } from "./default-admin-task.service"; + +describe("DefaultAdminTaskService", () => { + let defaultAdminTaskService: DefaultAdminTaskService; + let apiService: MockProxy; + + beforeEach(() => { + apiService = mock(); + defaultAdminTaskService = new DefaultAdminTaskService(apiService); + }); + + describe("getAllTasks", () => { + it("should call the api service with the correct parameters with status", async () => { + const organizationId = "orgId" as OrganizationId; + const status = SecurityTaskStatus.Pending; + const expectedUrl = `/tasks/organization?organizationId=${organizationId}&status=0`; + + await defaultAdminTaskService.getAllTasks(organizationId, status); + + expect(apiService.send).toHaveBeenCalledWith("GET", expectedUrl, null, true, true); + }); + + it("should call the api service with the correct parameters without status", async () => { + const organizationId = "orgId" as OrganizationId; + const expectedUrl = `/tasks/organization?organizationId=${organizationId}`; + + await defaultAdminTaskService.getAllTasks(organizationId); + + expect(apiService.send).toHaveBeenCalledWith("GET", expectedUrl, null, true, true); + }); + }); + + describe("bulkCreateTasks", () => { + it("should call the api service with the correct parameters", async () => { + const organizationId = "orgId" as OrganizationId; + const tasks: CreateTasksRequest[] = [ + { + cipherId: "cipherId-1" as CipherId, + type: SecurityTaskType.UpdateAtRiskCredential, + }, + { + cipherId: "cipherId-2" as CipherId, + type: SecurityTaskType.UpdateAtRiskCredential, + }, + ]; + + await defaultAdminTaskService.bulkCreateTasks(organizationId, tasks); + + expect(apiService.send).toHaveBeenCalledWith( + "POST", + `/tasks/${organizationId}/bulk-create`, + { tasks }, + true, + true, + ); + }); + }); +}); diff --git a/bitwarden_license/bit-web/src/app/vault/services/default-admin-task.service.ts b/bitwarden_license/bit-web/src/app/vault/services/default-admin-task.service.ts new file mode 100644 index 00000000000..520fb744486 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/vault/services/default-admin-task.service.ts @@ -0,0 +1,54 @@ +import { Injectable } from "@angular/core"; + +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/vault"; + +import { AdminTaskService, CreateTasksRequest } from "./abstractions/admin-task.abstraction"; + +@Injectable() +export class DefaultAdminTaskService implements AdminTaskService { + constructor(private apiService: ApiService) {} + + 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))); + } + + async bulkCreateTasks( + organizationId: OrganizationId, + tasks: CreateTasksRequest[], + ): Promise { + await this.apiService.send( + "POST", + `/tasks/${organizationId}/bulk-create`, + { tasks }, + true, + true, + ); + } +} diff --git a/bitwarden_license/bit-web/src/main.ts b/bitwarden_license/bit-web/src/main.ts index 1d1519c8b50..b202a170d26 100644 --- a/bitwarden_license/bit-web/src/main.ts +++ b/bitwarden_license/bit-web/src/main.ts @@ -11,6 +11,4 @@ if (process.env.NODE_ENV === "production") { enableProdMode(); } -// 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 -platformBrowserDynamic().bootstrapModule(AppModule, { preserveWhitespaces: true }); +void platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/bitwarden_license/bit-web/tsconfig.build.json b/bitwarden_license/bit-web/tsconfig.build.json index 9bebbeb5061..6313ce27863 100644 --- a/bitwarden_license/bit-web/tsconfig.build.json +++ b/bitwarden_license/bit-web/tsconfig.build.json @@ -9,6 +9,6 @@ ], "include": [ "../../apps/web/src/connectors/*.ts", - "../../libs/common/src/platform/services/**/*.worker.ts" + "../../libs/common/src/key-management/crypto/services/encrypt.worker.ts" ] } diff --git a/bitwarden_license/bit-web/tsconfig.json b/bitwarden_license/bit-web/tsconfig.json index c4304ec2bd9..679513a656f 100644 --- a/bitwarden_license/bit-web/tsconfig.json +++ b/bitwarden_license/bit-web/tsconfig.json @@ -21,11 +21,13 @@ "../../libs/tools/export/vault-export/vault-export-core/src" ], "@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-ui/src"], - "@bitwarden/importer/core": ["../../libs/importer/src"], - "@bitwarden/importer/ui": ["../../libs/importer/src/components"], + "@bitwarden/importer-core": ["../../libs/importer/src"], + "@bitwarden/importer-ui": ["../../libs/importer/src/components"], "@bitwarden/key-management": ["../../libs/key-management/src"], - "@bitwarden/key-management/angular": ["../../libs/key-management/src/angular"], + "@bitwarden/key-management-ui": ["../../libs/key-management-ui/src"], "@bitwarden/platform": ["../../libs/platform/src"], + "@bitwarden/ui-common": ["../../libs/ui/common/src"], + "@bitwarden/ui-common/setup-jest": ["../../libs/ui/common/src/setup-jest"], "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], "@bitwarden/tools-card": ["../../libs/tools/card/src"], "@bitwarden/vault": ["../../libs/vault/src"], @@ -45,7 +47,7 @@ "../../apps/web/src/connectors/*.ts", "../../apps/web/src/**/*.stories.ts", "../../apps/web/src/**/*.spec.ts", - "../../libs/common/src/platform/services/**/*.worker.ts", + "../../libs/common/src/key-management/crypto/services/encrypt.worker.ts", "src/**/*.stories.ts", "src/**/*.spec.ts" diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000000..9d93d1118c0 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,384 @@ +// @ts-check + +import eslint from "@eslint/js"; +import tseslint from "typescript-eslint"; +import angular from "angular-eslint"; +// @ts-ignore +import importPlugin from "eslint-plugin-import"; +import eslintConfigPrettier from "eslint-config-prettier"; +import eslintPluginTailwindCSS from "eslint-plugin-tailwindcss"; +import rxjs from "eslint-plugin-rxjs"; +import angularRxjs from "eslint-plugin-rxjs-angular"; +import storybook from "eslint-plugin-storybook"; + +import platformPlugins from "./libs/eslint/platform/index.mjs"; + +export default tseslint.config( + ...storybook.configs["flat/recommended"], + { + // Everything in this config object targets our TypeScript files (Components, Directives, Pipes etc) + files: ["**/*.ts", "**/*.js"], + extends: [ + eslint.configs.recommended, + ...tseslint.configs.recommended, + //...tseslint.configs.stylistic, + ...angular.configs.tsRecommended, + importPlugin.flatConfigs.recommended, + importPlugin.flatConfigs.typescript, + eslintConfigPrettier, // Disables rules that conflict with Prettier + ], + linterOptions: { + reportUnusedDisableDirectives: "error", + }, + plugins: { + rxjs: rxjs, + "rxjs-angular": angularRxjs, + "@bitwarden/platform": platformPlugins, + }, + languageOptions: { + parserOptions: { + project: ["./tsconfig.eslint.json"], + sourceType: "module", + ecmaVersion: 2020, + }, + }, + settings: { + "import/parsers": { + "@typescript-eslint/parser": [".ts"], + }, + "import/resolver": { + typescript: { + alwaysTryTypes: true, + }, + }, + }, + processor: angular.processInlineTemplates, + rules: { + ...rxjs.configs.recommended.rules, + "rxjs-angular/prefer-takeuntil": ["error", { alias: ["takeUntilDestroyed"] }], + "rxjs/no-exposed-subjects": ["error", { allowProtected: true }], + + // TODO: Enable these. + "@angular-eslint/component-class-suffix": 0, + "@angular-eslint/contextual-lifecycle": 0, + "@angular-eslint/directive-class-suffix": 0, + "@angular-eslint/no-empty-lifecycle-method": 0, + "@angular-eslint/no-host-metadata-property": 0, + "@angular-eslint/no-input-rename": 0, + "@angular-eslint/no-inputs-metadata-property": 0, + "@angular-eslint/no-output-native": 0, + "@angular-eslint/no-output-on-prefix": 0, + "@angular-eslint/no-output-rename": 0, + "@angular-eslint/no-outputs-metadata-property": 0, + "@angular-eslint/use-lifecycle-interface": "error", + "@angular-eslint/use-pipe-transform-interface": 0, + "@bitwarden/platform/required-using": "error", + "@typescript-eslint/explicit-member-accessibility": ["error", { accessibility: "no-public" }], + "@typescript-eslint/no-explicit-any": "off", // TODO: This should be re-enabled + "@typescript-eslint/no-floating-promises": "error", + "@typescript-eslint/no-misused-promises": ["error", { checksVoidReturn: false }], + "@typescript-eslint/no-this-alias": ["error", { allowedNames: ["self"] }], + "@typescript-eslint/no-unused-expressions": ["error", { allowTernary: true }], + "@typescript-eslint/no-unused-vars": ["error", { args: "none" }], + + curly: ["error", "all"], + "no-console": "error", + + "import/order": [ + "error", + { + alphabetize: { + order: "asc", + }, + "newlines-between": "always", + pathGroups: [ + { + pattern: "@bitwarden/**", + group: "external", + position: "after", + }, + { + pattern: "src/**/*", + group: "parent", + position: "before", + }, + ], + pathGroupsExcludedImportTypes: ["builtin"], + }, + ], + "import/namespace": ["off"], // This doesn't resolve namespace imports correctly, but TS will throw for this anyway + "import/no-restricted-paths": [ + "error", + { + zones: [ + { + target: ["libs/**/*"], + from: ["apps/**/*"], + message: "Libs should not import app-specific code.", + }, + { + // avoid specific frameworks or large dependencies in common + target: "./libs/common/**/*", + from: [ + // Angular + "./libs/angular/**/*", + "./node_modules/@angular*/**/*", + + // Node + "./libs/node/**/*", + + //Generator + "./libs/tools/generator/components/**/*", + "./libs/tools/generator/core/**/*", + "./libs/tools/generator/extensions/**/*", + + // Import/export + "./libs/importer/**/*", + "./libs/tools/export/vault-export/vault-export-core/**/*", + ], + }, + { + // avoid import of unexported state objects + target: [ + "!(libs)/**/*", + "libs/!(common)/**/*", + "libs/common/!(src)/**/*", + "libs/common/src/!(platform)/**/*", + "libs/common/src/platform/!(state)/**/*", + ], + from: ["./libs/common/src/platform/state/**/*"], + // allow module index import + except: ["**/state/index.ts"], + }, + ], + }, + ], + "import/no-unresolved": "off", // TODO: Look into turning off once each package is an actual package., + }, + }, + { + // Everything in this config object targets our HTML files (external templates, + // and inline templates as long as we have the `processor` set on our TypeScript config above) + files: ["**/*.html"], + extends: [ + // Apply the recommended Angular template rules + // ...angular.configs.templateRecommended, + // Apply the Angular template rules which focus on accessibility of our apps + // ...angular.configs.templateAccessibility, + ], + languageOptions: { + parser: angular.templateParser, + }, + plugins: { + "@angular-eslint/template": angular.templatePlugin, + tailwindcss: eslintPluginTailwindCSS, + }, + rules: { + "@angular-eslint/template/button-has-type": "error", + "tailwindcss/no-custom-classname": [ + "error", + { + // uses negative lookahead to whitelist any class that doesn't start with "tw-" + // in other words: classnames that start with tw- must be valid TailwindCSS classes + whitelist: ["(?!(tw)\\-).*"], + }, + ], + "tailwindcss/enforces-negative-arbitrary-values": "error", + "tailwindcss/enforces-shorthand": "error", + "tailwindcss/no-contradicting-classname": "error", + }, + }, + + // Global quirks + { + files: ["apps/browser/src/**/*.ts", "libs/**/*.ts"], + ignores: [ + "apps/browser/src/autofill/{deprecated/content,content,notification}/**/*.ts", + "apps/browser/src/**/background/**/*.ts", // It's okay to have long lived listeners in the background + "apps/browser/src/platform/background.ts", + ], + rules: { + "no-restricted-syntax": [ + "error", + { + message: + "Using addListener in the browser popup produces a memory leak in Safari, use `BrowserApi.addListener` instead", + // This selector covers events like chrome.storage.onChange & chrome.runtime.onMessage + selector: + "CallExpression > [object.object.object.name='chrome'][property.name='addListener']", + }, + { + message: + "Using addListener in the browser popup produces a memory leak in Safari, use `BrowserApi.addListener` instead", + // This selector covers events like chrome.storage.local.onChange + selector: + "CallExpression > [object.object.object.object.name='chrome'][property.name='addListener']", + }, + ], + }, + }, + { + files: ["**/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports(), + }, + }, + + // App overrides. Be considerate if you override these. + { + files: ["apps/browser/src/**/*.ts"], + ignores: [ + "apps/browser/src/**/{content,popup,spec}/**/*.ts", + "apps/browser/src/**/autofill/{notification,overlay}/**/*.ts", + "apps/browser/src/**/autofill/**/{autofill-overlay-content,collect-autofill-content,dom-element-visibility,insert-autofill-content}.service.ts", + "apps/browser/src/**/*.spec.ts", + ], + rules: { + "no-restricted-globals": [ + "error", + { + name: "window", + message: + "The `window` object is not available in service workers and may not be available within the background script. Consider using `self`, `globalThis`, or another global property instead.", + }, + ], + }, + }, + { + files: ["bitwarden_license/bit-common/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports(["@bitwarden/bit-common/*"]), + }, + }, + { + files: ["apps/**/*.ts"], + rules: { + // Catches static imports + "no-restricted-imports": buildNoRestrictedImports([ + "bitwarden_license/**", + "@bitwarden/bit-common/*", + "@bitwarden/bit-web/*", + ]), + }, + }, + { + files: ["apps/web/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + "bitwarden_license/**", + "@bitwarden/bit-common/*", + "@bitwarden/bit-web/*", + + "**/app/core/*", + "**/reports/*", + "**/app/shared/*", + "**/organizations/settings/*", + "**/organizations/policies/*", + ]), + }, + }, + + /// Team overrides + { + files: ["**/src/platform/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([], true), + }, + }, + { + files: [ + "apps/cli/src/admin-console/**/*.ts", + "apps/web/src/app/admin-console/**/*.ts", + "bitwarden_license/bit-cli/src/admin-console/**/*.ts", + "bitwarden_license/bit-web/src/app/admin-console/**/*.ts", + "libs/admin-console/src/**/*.ts", + ], + rules: { + "@angular-eslint/component-class-suffix": "error", + "@angular-eslint/contextual-lifecycle": "error", + "@angular-eslint/directive-class-suffix": "error", + "@angular-eslint/no-empty-lifecycle-method": "error", + "@angular-eslint/no-input-rename": "error", + "@angular-eslint/no-inputs-metadata-property": "error", + "@angular-eslint/no-output-native": "error", + "@angular-eslint/no-output-on-prefix": "error", + "@angular-eslint/no-output-rename": "error", + "@angular-eslint/no-outputs-metadata-property": "error", + "@angular-eslint/use-lifecycle-interface": "error", + "@angular-eslint/use-pipe-transform-interface": "error", + }, + }, + { + files: ["libs/common/src/state-migrations/**/*.ts"], + rules: { + "import/no-restricted-paths": [ + "error", + { + basePath: "libs/common/src/state-migrations", + zones: [ + { + target: "./", + from: "../", + // Relative to from, not basePath + except: ["state-migrations"], + message: + "State migrations should rarely import from the greater codebase. If you need to import from another location, take into account the likelihood of change in that code and consider copying to the migration instead.", + }, + ], + }, + ], + }, + }, + + // Keep ignores at the end + { + ignores: [ + "**/build/", + "**/dist/", + "**/coverage/", + ".angular/", + "storybook-static/", + + "**/node_modules/", + + "**/webpack.*.js", + "**/jest.config.js", + + "apps/browser/config/config.js", + "apps/browser/src/auth/scripts/duo.js", + "apps/browser/webpack/manifest.js", + + "apps/desktop/desktop_native", + "apps/desktop/src/auth/scripts/duo.js", + + "apps/web/config.js", + "apps/web/scripts/*.js", + "apps/web/tailwind.config.js", + + "apps/cli/config/config.js", + + "tailwind.config.js", + "libs/components/tailwind.config.base.js", + "libs/components/tailwind.config.js", + + "scripts/*.js", + ], + }, +); + +/** + * // Helper function for building no-restricted-imports rule + * @param {string[]} additionalForbiddenPatterns + * @returns {any} + */ +function buildNoRestrictedImports(additionalForbiddenPatterns = [], skipPlatform = false) { + return [ + "error", + { + patterns: [ + ...(skipPlatform ? [] : ["**/platform/**/internal", "**/platform/messaging/**"]), + "**/src/**/*", // Prevent relative imports across libs. + ].concat(additionalForbiddenPatterns), + }, + ]; +} diff --git a/jest.config.js b/jest.config.js index 3ed082bcbc3..e8815f92ffb 100644 --- a/jest.config.js +++ b/jest.config.js @@ -30,6 +30,7 @@ module.exports = { "/libs/billing/jest.config.js", "/libs/common/jest.config.js", "/libs/components/jest.config.js", + "/libs/eslint/jest.config.js", "/libs/tools/export/vault-export/vault-export-core/jest.config.js", "/libs/tools/generator/core/jest.config.js", "/libs/tools/generator/components/jest.config.js", @@ -42,6 +43,7 @@ module.exports = { "/libs/node/jest.config.js", "/libs/vault/jest.config.js", "/libs/key-management/jest.config.js", + "/libs/key-management-ui/jest.config.js", ], // Workaround for a memory leak that crashes tests in CI: diff --git a/libs/admin-console/.eslintrc.json b/libs/admin-console/.eslintrc.json deleted file mode 100644 index d8aa8f64a88..00000000000 --- a/libs/admin-console/.eslintrc.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "overrides": [ - { - "files": ["*.ts"], - "extends": ["plugin:@angular-eslint/recommended"], - "rules": { - "@angular-eslint/component-class-suffix": "error", - "@angular-eslint/contextual-lifecycle": "error", - "@angular-eslint/directive-class-suffix": "error", - "@angular-eslint/no-empty-lifecycle-method": "error", - "@angular-eslint/no-input-rename": "error", - "@angular-eslint/no-inputs-metadata-property": "error", - "@angular-eslint/no-output-native": "error", - "@angular-eslint/no-output-on-prefix": "error", - "@angular-eslint/no-output-rename": "error", - "@angular-eslint/no-outputs-metadata-property": "error", - "@angular-eslint/use-lifecycle-interface": "error", - "@angular-eslint/use-pipe-transform-interface": "error" - } - } - ] -} diff --git a/libs/admin-console/src/common/collections/models/collection.ts b/libs/admin-console/src/common/collections/models/collection.ts index f14ccb20141..5b6f1a6fb7a 100644 --- a/libs/admin-console/src/common/collections/models/collection.ts +++ b/libs/admin-console/src/common/collections/models/collection.ts @@ -39,11 +39,10 @@ export class Collection extends Domain { } decrypt(orgKey: OrgKey): Promise { - return this.decryptObj( + return this.decryptObj( + this, new CollectionView(this), - { - name: null, - }, + ["name"], this.organizationId, orgKey, ); diff --git a/libs/admin-console/src/common/collections/services/default-collection-admin.service.ts b/libs/admin-console/src/common/collections/services/default-collection-admin.service.ts index 6aafbaf4678..890353d9039 100644 --- a/libs/admin-console/src/common/collections/services/default-collection-admin.service.ts +++ b/libs/admin-console/src/common/collections/services/default-collection-admin.service.ts @@ -2,7 +2,7 @@ // @ts-strict-ignore import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { KeyService } from "@bitwarden/key-management"; diff --git a/libs/admin-console/src/common/collections/services/default-collection.service.spec.ts b/libs/admin-console/src/common/collections/services/default-collection.service.spec.ts index a230a20b2e3..7fe81ade4d2 100644 --- a/libs/admin-console/src/common/collections/services/default-collection.service.spec.ts +++ b/libs/admin-console/src/common/collections/services/default-collection.service.spec.ts @@ -1,7 +1,7 @@ import { mock } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; diff --git a/libs/admin-console/src/common/collections/services/default-collection.service.ts b/libs/admin-console/src/common/collections/services/default-collection.service.ts index 4070c92f27c..da50a25886e 100644 --- a/libs/admin-console/src/common/collections/services/default-collection.service.ts +++ b/libs/admin-console/src/common/collections/services/default-collection.service.ts @@ -3,7 +3,7 @@ import { combineLatest, firstValueFrom, map, Observable, of, switchMap } from "rxjs"; import { Jsonify } from "type-fest"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { diff --git a/libs/admin-console/src/common/collections/services/default-vnext-collection.service.spec.ts b/libs/admin-console/src/common/collections/services/default-vnext-collection.service.spec.ts index 4aa54429aad..9700fcb695a 100644 --- a/libs/admin-console/src/common/collections/services/default-vnext-collection.service.spec.ts +++ b/libs/admin-console/src/common/collections/services/default-vnext-collection.service.spec.ts @@ -1,7 +1,7 @@ import { mock, MockProxy } from "jest-mock-extended"; import { first, firstValueFrom, of, ReplaySubject, takeWhile } from "rxjs"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; @@ -91,7 +91,7 @@ describe("DefaultvNextCollectionService", () => { // Assert emitted values expect(result.length).toBe(2); - expect(result).toIncludeAllPartialMembers([ + expect(result).toContainPartialObjects([ { id: collection1.id, name: "DEC_NAME_" + collection1.id, @@ -167,7 +167,7 @@ describe("DefaultvNextCollectionService", () => { const result = await firstValueFrom(collectionService.encryptedCollections$(userId)); expect(result.length).toBe(2); - expect(result).toIncludeAllPartialMembers([ + expect(result).toContainPartialObjects([ { id: collection1.id, name: makeEncString("ENC_NAME_" + collection1.id), @@ -205,7 +205,7 @@ describe("DefaultvNextCollectionService", () => { const result = await firstValueFrom(collectionService.encryptedCollections$(userId)); expect(result.length).toBe(3); - expect(result).toIncludeAllPartialMembers([ + expect(result).toContainPartialObjects([ { id: collection1.id, name: makeEncString("UPDATED_ENC_NAME_" + collection1.id), @@ -230,7 +230,7 @@ describe("DefaultvNextCollectionService", () => { const result = await firstValueFrom(collectionService.encryptedCollections$(userId)); expect(result.length).toBe(1); - expect(result).toIncludeAllPartialMembers([ + expect(result).toContainPartialObjects([ { id: collection1.id, name: makeEncString("ENC_NAME_" + collection1.id), @@ -253,7 +253,7 @@ describe("DefaultvNextCollectionService", () => { const result = await firstValueFrom(collectionService.encryptedCollections$(userId)); expect(result.length).toBe(1); - expect(result).toIncludeAllPartialMembers([ + expect(result).toContainPartialObjects([ { id: newCollection3.id, name: makeEncString("ENC_NAME_" + newCollection3.id), diff --git a/libs/admin-console/src/common/collections/services/default-vnext-collection.service.ts b/libs/admin-console/src/common/collections/services/default-vnext-collection.service.ts index 2d5a083592b..0ef8ae99ab3 100644 --- a/libs/admin-console/src/common/collections/services/default-vnext-collection.service.ts +++ b/libs/admin-console/src/common/collections/services/default-vnext-collection.service.ts @@ -2,7 +2,7 @@ // @ts-strict-ignore import { combineLatest, filter, firstValueFrom, map } from "rxjs"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { StateProvider, DerivedState } from "@bitwarden/common/platform/state"; diff --git a/libs/admin-console/test.setup.ts b/libs/admin-console/test.setup.ts index 6be6e7b8dd1..7656954feca 100644 --- a/libs/admin-console/test.setup.ts +++ b/libs/admin-console/test.setup.ts @@ -1,5 +1,9 @@ import { webcrypto } from "crypto"; -import "jest-preset-angular/setup-jest"; + +import { addCustomMatchers } from "@bitwarden/common/spec"; +import "@bitwarden/ui-common/setup-jest"; + +addCustomMatchers(); Object.defineProperty(window, "CSS", { value: null }); Object.defineProperty(window, "getComputedStyle", { diff --git a/libs/admin-console/tsconfig.json b/libs/admin-console/tsconfig.json index 3d22cb2ec51..4f057fd6af0 100644 --- a/libs/admin-console/tsconfig.json +++ b/libs/admin-console/tsconfig.json @@ -8,6 +8,6 @@ "@bitwarden/key-management": ["../key-management/src"] } }, - "include": ["src", "spec"], + "include": ["src", "spec", "../../libs/common/custom-matchers.d.ts"], "exclude": ["node_modules", "dist"] } diff --git a/libs/angular/src/admin-console/components/collections.component.ts b/libs/angular/src/admin-console/components/collections.component.ts index 0b19935985a..5f39966468f 100644 --- a/libs/angular/src/admin-console/components/collections.component.ts +++ b/libs/angular/src/admin-console/components/collections.component.ts @@ -7,9 +7,11 @@ import { CollectionService, CollectionView } from "@bitwarden/admin-console/comm import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -45,11 +47,9 @@ export class CollectionsComponent implements OnInit { } async load() { - this.cipherDomain = await this.loadCipher(); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + this.cipherDomain = await this.loadCipher(activeUserId); this.collectionIds = this.loadCipherCollections(); - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); this.cipher = await this.cipherDomain.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId), ); @@ -63,7 +63,15 @@ export class CollectionsComponent implements OnInit { } if (this.organization == null) { - this.organization = await this.organizationService.get(this.cipher.organizationId); + this.organization = await firstValueFrom( + this.organizationService + .organizations$(activeUserId) + .pipe( + map((organizations) => + organizations.find((org) => org.id === this.cipher.organizationId), + ), + ), + ); } } @@ -87,7 +95,8 @@ export class CollectionsComponent implements OnInit { } this.cipherDomain.collectionIds = selectedCollectionIds; try { - this.formPromise = this.saveCollections(); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + this.formPromise = this.saveCollections(activeUserId); await this.formPromise; this.onSavedCollections.emit(); this.toastService.showToast({ @@ -106,8 +115,8 @@ export class CollectionsComponent implements OnInit { } } - protected loadCipher() { - return this.cipherService.get(this.cipherId); + protected loadCipher(userId: UserId) { + return this.cipherService.get(this.cipherId, userId); } protected loadCipherCollections() { @@ -121,7 +130,7 @@ export class CollectionsComponent implements OnInit { ); } - protected saveCollections() { - return this.cipherService.saveCollectionsWithServer(this.cipherDomain); + protected saveCollections(userId: UserId) { + return this.cipherService.saveCollectionsWithServer(this.cipherDomain, userId); } } diff --git a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-expired.component.ts b/libs/angular/src/auth/components/authentication-timeout.component.ts similarity index 89% rename from libs/angular/src/auth/components/two-factor-auth/two-factor-auth-expired.component.ts rename to libs/angular/src/auth/components/authentication-timeout.component.ts index faa08cf073b..1a5d398a291 100644 --- a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-expired.component.ts +++ b/libs/angular/src/auth/components/authentication-timeout.component.ts @@ -10,7 +10,7 @@ import { ButtonModule } from "@bitwarden/components"; * It provides a button to navigate to the login page. */ @Component({ - selector: "app-two-factor-expired", + selector: "app-authentication-timeout", standalone: true, imports: [CommonModule, JslibModule, ButtonModule, RouterModule], template: ` @@ -22,4 +22,4 @@ import { ButtonModule } from "@bitwarden/components"; `, }) -export class TwoFactorTimeoutComponent {} +export class AuthenticationTimeoutComponent {} diff --git a/libs/angular/src/auth/components/base-login-decryption-options-v1.component.ts b/libs/angular/src/auth/components/base-login-decryption-options-v1.component.ts deleted file mode 100644 index ca3906cead3..00000000000 --- a/libs/angular/src/auth/components/base-login-decryption-options-v1.component.ts +++ /dev/null @@ -1,307 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Directive, OnDestroy, OnInit } from "@angular/core"; -import { FormBuilder, FormControl } from "@angular/forms"; -import { ActivatedRoute, Router } from "@angular/router"; -import { - firstValueFrom, - switchMap, - Subject, - catchError, - from, - of, - finalize, - takeUntil, - defer, - throwError, - map, - Observable, - take, -} from "rxjs"; - -import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; -import { - LoginEmailServiceAbstraction, - UserDecryptionOptions, - UserDecryptionOptionsServiceAbstraction, -} from "@bitwarden/auth/common"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; -import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; -import { PasswordResetEnrollmentServiceAbstraction } from "@bitwarden/common/auth/abstractions/password-reset-enrollment.service.abstraction"; -import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; -import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; -import { KeysRequest } from "@bitwarden/common/models/request/keys.request"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; -import { UserId } from "@bitwarden/common/types/guid"; -import { ToastService } from "@bitwarden/components"; -import { KeyService } from "@bitwarden/key-management"; - -enum State { - NewUser, - ExistingUserUntrustedDevice, -} - -type NewUserData = { - readonly state: State.NewUser; - readonly organizationId: string; - readonly userEmail: string; -}; - -type ExistingUserUntrustedDeviceData = { - readonly state: State.ExistingUserUntrustedDevice; - readonly showApproveFromOtherDeviceBtn: boolean; - readonly showReqAdminApprovalBtn: boolean; - readonly showApproveWithMasterPasswordBtn: boolean; - readonly userEmail: string; -}; - -type Data = NewUserData | ExistingUserUntrustedDeviceData; - -@Directive() -export class BaseLoginDecryptionOptionsComponentV1 implements OnInit, OnDestroy { - private destroy$ = new Subject(); - - protected State = State; - - protected data?: Data; - protected loading = true; - - private email$: Observable; - - activeAccountId: UserId; - - // Remember device means for the user to trust the device - rememberDeviceForm = this.formBuilder.group({ - rememberDevice: [true], - }); - - get rememberDevice(): FormControl { - return this.rememberDeviceForm?.controls.rememberDevice; - } - - constructor( - protected formBuilder: FormBuilder, - protected devicesService: DevicesServiceAbstraction, - protected stateService: StateService, - protected router: Router, - protected activatedRoute: ActivatedRoute, - protected messagingService: MessagingService, - protected tokenService: TokenService, - protected loginEmailService: LoginEmailServiceAbstraction, - protected organizationApiService: OrganizationApiServiceAbstraction, - protected keyService: KeyService, - protected organizationUserApiService: OrganizationUserApiService, - protected apiService: ApiService, - protected i18nService: I18nService, - protected validationService: ValidationService, - protected deviceTrustService: DeviceTrustServiceAbstraction, - protected platformUtilsService: PlatformUtilsService, - protected userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, - protected passwordResetEnrollmentService: PasswordResetEnrollmentServiceAbstraction, - protected ssoLoginService: SsoLoginServiceAbstraction, - protected accountService: AccountService, - protected toastService: ToastService, - ) {} - - async ngOnInit() { - this.loading = true; - this.activeAccountId = (await firstValueFrom(this.accountService.activeAccount$))?.id; - this.email$ = this.accountService.activeAccount$.pipe( - map((a) => a?.email), - catchError((err: unknown) => { - this.validationService.showError(err); - return of(undefined); - }), - takeUntil(this.destroy$), - ); - - this.setupRememberDeviceValueChanges(); - - // Persist user choice from state if it exists - await this.setRememberDeviceDefaultValue(); - - try { - const userDecryptionOptions = await firstValueFrom( - this.userDecryptionOptionsService.userDecryptionOptions$, - ); - - // see sso-login.strategy - to determine if a user is new or not it just checks if there is a key on the token response.. - // can we check if they have a user key or master key in crypto service? Would that be sufficient? - if ( - !userDecryptionOptions?.trustedDeviceOption?.hasAdminApproval && - !userDecryptionOptions?.hasMasterPassword - ) { - // We are dealing with a new account if: - // - User does not have admin approval (i.e. has not enrolled into admin reset) - // - AND does not have a master password - - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.loadNewUserData(); - } else { - this.loadUntrustedDeviceData(userDecryptionOptions); - } - - // Note: this is probably not a comprehensive write up of all scenarios: - - // If the TDE feature flag is enabled and TDE is configured for the org that the user is a member of, - // then new and existing users can be redirected here after completing the SSO flow (and 2FA if enabled). - - // First we must determine user type (new or existing): - - // New User - // - present user with option to remember the device or not (trust the device) - // - present a continue button to proceed to the vault - // - loadNewUserData() --> will need to load enrollment status and user email address. - - // Existing User - // - Determine if user is an admin with access to account recovery in admin console - // - Determine if user has a MP or not, if not, they must be redirected to set one (see PM-1035) - // - Determine if device is trusted or not via device crypto service (method not yet written) - // - If not trusted, present user with login decryption options (approve from other device, approve with master password, request admin approval) - // - loadUntrustedDeviceData() - } catch (err) { - this.validationService.showError(err); - } - } - - private async setRememberDeviceDefaultValue() { - const rememberDeviceFromState = await this.deviceTrustService.getShouldTrustDevice( - this.activeAccountId, - ); - - const rememberDevice = rememberDeviceFromState ?? true; - - this.rememberDevice.setValue(rememberDevice); - } - - private setupRememberDeviceValueChanges() { - this.rememberDevice.valueChanges - .pipe( - switchMap((value) => - defer(() => this.deviceTrustService.setShouldTrustDevice(this.activeAccountId, value)), - ), - takeUntil(this.destroy$), - ) - .subscribe(); - } - - async loadNewUserData() { - const autoEnrollStatus$ = defer(() => - this.ssoLoginService.getActiveUserOrganizationSsoIdentifier(), - ).pipe( - switchMap((organizationIdentifier) => { - if (organizationIdentifier == undefined) { - return throwError(() => new Error(this.i18nService.t("ssoIdentifierRequired"))); - } - - return from(this.organizationApiService.getAutoEnrollStatus(organizationIdentifier)); - }), - catchError((err: unknown) => { - this.validationService.showError(err); - return of(undefined); - }), - ); - - const autoEnrollStatus = await firstValueFrom(autoEnrollStatus$); - const email = await firstValueFrom(this.email$); - - this.data = { state: State.NewUser, organizationId: autoEnrollStatus.id, userEmail: email }; - this.loading = false; - } - - loadUntrustedDeviceData(userDecryptionOptions: UserDecryptionOptions) { - this.loading = true; - - this.email$ - .pipe( - take(1), - finalize(() => { - this.loading = false; - }), - ) - .subscribe((email) => { - const showApproveFromOtherDeviceBtn = - userDecryptionOptions?.trustedDeviceOption?.hasLoginApprovingDevice || false; - - const showReqAdminApprovalBtn = - !!userDecryptionOptions?.trustedDeviceOption?.hasAdminApproval || false; - - const showApproveWithMasterPasswordBtn = userDecryptionOptions?.hasMasterPassword || false; - - const userEmail = email; - - this.data = { - state: State.ExistingUserUntrustedDevice, - showApproveFromOtherDeviceBtn, - showReqAdminApprovalBtn, - showApproveWithMasterPasswordBtn, - userEmail, - }; - }); - } - - async approveFromOtherDevice() { - if (this.data.state !== State.ExistingUserUntrustedDevice) { - return; - } - - this.loginEmailService.setLoginEmail(this.data.userEmail); - await this.router.navigate(["/login-with-device"]); - } - - async requestAdminApproval() { - this.loginEmailService.setLoginEmail(this.data.userEmail); - await this.router.navigate(["/admin-approval-requested"]); - } - - async approveWithMasterPassword() { - await this.router.navigate(["/lock"], { queryParams: { from: "login-initiated" } }); - } - - async createUser() { - if (this.data.state !== State.NewUser) { - return; - } - - // this.loading to support clients without async-actions-support - this.loading = true; - // errors must be caught in child components to prevent navigation - try { - const { publicKey, privateKey } = await this.keyService.initAccount(); - const keysRequest = new KeysRequest(publicKey, privateKey.encryptedString); - await this.apiService.postAccountKeys(keysRequest); - - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("accountSuccessfullyCreated"), - }); - - await this.passwordResetEnrollmentService.enroll(this.data.organizationId); - - if (this.rememberDeviceForm.value.rememberDevice) { - await this.deviceTrustService.trustDevice(this.activeAccountId); - } - } finally { - this.loading = false; - } - } - - logOut() { - this.loading = true; // to avoid an awkward delay in browser extension - this.messagingService.send("logout"); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } -} diff --git a/libs/angular/src/auth/components/change-password.component.ts b/libs/angular/src/auth/components/change-password.component.ts index 7f54f35cb2a..2582d6a7103 100644 --- a/libs/angular/src/auth/components/change-password.component.ts +++ b/libs/angular/src/auth/components/change-password.component.ts @@ -6,16 +6,14 @@ import { Subject, firstValueFrom, map, takeUntil } from "rxjs"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { UserKey, MasterKey } from "@bitwarden/common/types/key"; import { DialogService, ToastService } from "@bitwarden/components"; -import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { KdfConfig, KdfConfigService, KeyService } from "@bitwarden/key-management"; import { PasswordColorText } from "../../tools/password-strength/password-strength.component"; @@ -41,10 +39,8 @@ export class ChangePasswordComponent implements OnInit, OnDestroy { protected i18nService: I18nService, protected keyService: KeyService, protected messagingService: MessagingService, - protected passwordGenerationService: PasswordGenerationServiceAbstraction, protected platformUtilsService: PlatformUtilsService, protected policyService: PolicyService, - protected stateService: StateService, protected dialogService: DialogService, protected kdfConfigService: KdfConfigService, protected masterPasswordService: InternalMasterPasswordServiceAbstraction, diff --git a/libs/angular/src/auth/components/environment-selector.component.html b/libs/angular/src/auth/components/environment-selector.component.html index 786afe40371..19f49f73dd2 100644 --- a/libs/angular/src/auth/components/environment-selector.component.html +++ b/libs/angular/src/auth/components/environment-selector.component.html @@ -3,7 +3,7 @@ selectedRegion: selectedRegion$ | async, } as data" > -
    +
    {{ "accessing" | i18n }}: -
    - diff --git a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-yubikey.component.html b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-yubikey.component.html deleted file mode 100644 index c6654c00edb..00000000000 --- a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-yubikey.component.html +++ /dev/null @@ -1,17 +0,0 @@ -

    {{ "insertYubiKey" | i18n }}

    - - - - - - - {{ "verificationCode" | i18n }} - - diff --git a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.html b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.html deleted file mode 100644 index 8462a18ac2e..00000000000 --- a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.html +++ /dev/null @@ -1,76 +0,0 @@ -
    - - - - - - - {{ "rememberMe" | i18n }} - - - -

    {{ "noTwoStepProviders" | i18n }}

    -

    {{ "noTwoStepProviders2" | i18n }}

    -
    -
    - -
    - -
    - - - - - {{ "cancel" | i18n }} - -
    - - diff --git a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.ts b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.ts deleted file mode 100644 index 6aca189a79e..00000000000 --- a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.ts +++ /dev/null @@ -1,413 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { CommonModule } from "@angular/common"; -import { Component, Inject, OnInit, ViewChild } from "@angular/core"; -import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms"; -import { ActivatedRoute, NavigationExtras, Router, RouterLink } from "@angular/router"; -import { Subject, takeUntil, lastValueFrom, first, firstValueFrom } from "rxjs"; - -import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; -import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; -import { - LoginStrategyServiceAbstraction, - LoginEmailServiceAbstraction, - UserDecryptionOptionsServiceAbstraction, - TrustedDeviceUserDecryptionOption, - UserDecryptionOptions, -} from "@bitwarden/auth/common"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; -import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; -import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type"; -import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; -import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; -import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; -import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; -import { TwoFactorProviders } from "@bitwarden/common/auth/services/two-factor.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { - AsyncActionsModule, - ButtonModule, - DialogService, - FormFieldModule, - ToastService, -} from "@bitwarden/components"; - -import { CaptchaProtectedComponent } from "../captcha-protected.component"; - -import { TwoFactorAuthAuthenticatorComponent } from "./two-factor-auth-authenticator.component"; -import { TwoFactorAuthDuoComponent } from "./two-factor-auth-duo.component"; -import { TwoFactorAuthEmailComponent } from "./two-factor-auth-email.component"; -import { TwoFactorAuthWebAuthnComponent } from "./two-factor-auth-webauthn.component"; -import { TwoFactorAuthYubikeyComponent } from "./two-factor-auth-yubikey.component"; -import { - TwoFactorOptionsDialogResult, - TwoFactorOptionsComponent, - TwoFactorOptionsDialogResultType, -} from "./two-factor-options.component"; - -@Component({ - standalone: true, - selector: "app-two-factor-auth", - templateUrl: "two-factor-auth.component.html", - imports: [ - CommonModule, - JslibModule, - ReactiveFormsModule, - FormFieldModule, - AsyncActionsModule, - RouterLink, - ButtonModule, - TwoFactorOptionsComponent, - TwoFactorAuthAuthenticatorComponent, - TwoFactorAuthEmailComponent, - TwoFactorAuthDuoComponent, - TwoFactorAuthYubikeyComponent, - TwoFactorAuthWebAuthnComponent, - ], - providers: [I18nPipe], -}) -export class TwoFactorAuthComponent extends CaptchaProtectedComponent implements OnInit { - token = ""; - remember = false; - orgIdentifier: string = null; - - providers = TwoFactorProviders; - providerType = TwoFactorProviderType; - selectedProviderType: TwoFactorProviderType = TwoFactorProviderType.Authenticator; - providerData: any; - - @ViewChild("duoComponent") duoComponent!: TwoFactorAuthDuoComponent; - formGroup = this.formBuilder.group({ - token: [ - "", - { - validators: [Validators.required], - updateOn: "submit", - }, - ], - remember: [false], - }); - actionButtonText = ""; - title = ""; - formPromise: Promise; - - private destroy$ = new Subject(); - - onSuccessfulLogin: () => Promise; - onSuccessfulLoginNavigate: () => Promise; - - onSuccessfulLoginTde: () => Promise; - onSuccessfulLoginTdeNavigate: () => Promise; - - submitForm = async () => { - await this.submit(); - }; - goAfterLogIn = async () => { - this.loginEmailService.clearValues(); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate([this.successRoute], { - queryParams: { - identifier: this.orgIdentifier, - }, - }); - }; - - protected loginRoute = "login"; - - protected trustedDeviceEncRoute = "login-initiated"; - protected changePasswordRoute = "set-password"; - protected forcePasswordResetRoute = "update-temp-password"; - protected successRoute = "vault"; - - constructor( - protected loginStrategyService: LoginStrategyServiceAbstraction, - protected router: Router, - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - environmentService: EnvironmentService, - private dialogService: DialogService, - protected route: ActivatedRoute, - private logService: LogService, - protected twoFactorService: TwoFactorService, - private loginEmailService: LoginEmailServiceAbstraction, - private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, - protected ssoLoginService: SsoLoginServiceAbstraction, - protected configService: ConfigService, - private masterPasswordService: InternalMasterPasswordServiceAbstraction, - private accountService: AccountService, - private formBuilder: FormBuilder, - @Inject(WINDOW) protected win: Window, - protected toastService: ToastService, - ) { - super(environmentService, i18nService, platformUtilsService, toastService); - } - - async ngOnInit() { - if (!(await this.authing()) || (await this.twoFactorService.getProviders()) == null) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate([this.loginRoute]); - return; - } - - // eslint-disable-next-line rxjs-angular/prefer-takeuntil - this.route.queryParams.pipe(first()).subscribe((qParams) => { - if (qParams.identifier != null) { - this.orgIdentifier = qParams.identifier; - } - }); - - if (await this.needsLock()) { - this.successRoute = "lock"; - } - - const webAuthnSupported = this.platformUtilsService.supportsWebAuthn(this.win); - this.selectedProviderType = await this.twoFactorService.getDefaultProvider(webAuthnSupported); - const providerData = await this.twoFactorService.getProviders().then((providers) => { - return providers.get(this.selectedProviderType); - }); - this.providerData = providerData; - await this.updateUIToProviderData(); - - this.actionButtonText = this.i18nService.t("continue"); - this.formGroup.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((value) => { - this.token = value.token; - this.remember = value.remember; - }); - } - - async submit() { - await this.setupCaptcha(); - - if (this.token == null || this.token === "") { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("verificationCodeRequired"), - }); - return; - } - - try { - this.formPromise = this.loginStrategyService.logInTwoFactor( - new TokenTwoFactorRequest(this.selectedProviderType, this.token, this.remember), - this.captchaToken, - ); - const authResult: AuthResult = await this.formPromise; - this.logService.info("Successfully submitted two factor token"); - await this.handleLoginResponse(authResult); - } catch { - this.logService.error("Error submitting two factor token"); - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("invalidVerificationCode"), - }); - } - } - - async selectOtherTwofactorMethod() { - const dialogRef = TwoFactorOptionsComponent.open(this.dialogService); - const response: TwoFactorOptionsDialogResultType = await lastValueFrom(dialogRef.closed); - if (response.result === TwoFactorOptionsDialogResult.Provider) { - const providerData = await this.twoFactorService.getProviders().then((providers) => { - return providers.get(response.type); - }); - this.providerData = providerData; - this.selectedProviderType = response.type; - await this.updateUIToProviderData(); - } - } - - async launchDuo() { - if (this.duoComponent != null) { - await this.duoComponent.launchDuoFrameless(); - } - } - - protected handleMigrateEncryptionKey(result: AuthResult): boolean { - if (!result.requiresEncryptionKeyMigration) { - return false; - } - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["migrate-legacy-encryption"]); - return true; - } - - async updateUIToProviderData() { - if (this.selectedProviderType == null) { - this.title = this.i18nService.t("loginUnavailable"); - return; - } - - this.title = (TwoFactorProviders as any)[this.selectedProviderType].name; - } - - private async handleLoginResponse(authResult: AuthResult) { - if (this.handleCaptchaRequired(authResult)) { - return; - } else if (this.handleMigrateEncryptionKey(authResult)) { - return; - } - - // Save off the OrgSsoIdentifier for use in the TDE flows - // - TDE login decryption options component - // - Browser SSO on extension open - await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier(this.orgIdentifier); - this.loginEmailService.clearValues(); - - // note: this flow affects both TDE & standard users - if (this.isForcePasswordResetRequired(authResult)) { - return await this.handleForcePasswordReset(this.orgIdentifier); - } - - const userDecryptionOpts = await firstValueFrom( - this.userDecryptionOptionsService.userDecryptionOptions$, - ); - - const tdeEnabled = await this.isTrustedDeviceEncEnabled(userDecryptionOpts.trustedDeviceOption); - - if (tdeEnabled) { - return await this.handleTrustedDeviceEncryptionEnabled( - authResult, - this.orgIdentifier, - userDecryptionOpts, - ); - } - - // User must set password if they don't have one and they aren't using either TDE or key connector. - const requireSetPassword = - !userDecryptionOpts.hasMasterPassword && userDecryptionOpts.keyConnectorOption === undefined; - - if (requireSetPassword || authResult.resetMasterPassword) { - // Change implies going no password -> password in this case - return await this.handleChangePasswordRequired(this.orgIdentifier); - } - - return await this.handleSuccessfulLogin(); - } - - private async isTrustedDeviceEncEnabled( - trustedDeviceOption: TrustedDeviceUserDecryptionOption, - ): Promise { - const ssoTo2faFlowActive = this.route.snapshot.queryParamMap.get("sso") === "true"; - - return ssoTo2faFlowActive && trustedDeviceOption !== undefined; - } - - private async handleTrustedDeviceEncryptionEnabled( - authResult: AuthResult, - orgIdentifier: string, - userDecryptionOpts: UserDecryptionOptions, - ): Promise { - // If user doesn't have a MP, but has reset password permission, they must set a MP - if ( - !userDecryptionOpts.hasMasterPassword && - userDecryptionOpts.trustedDeviceOption.hasManageResetPasswordPermission - ) { - // Set flag so that auth guard can redirect to set password screen after decryption (trusted or untrusted device) - // Note: we cannot directly navigate to the set password screen in this scenario as we are in a pre-decryption state, and - // if you try to set a new MP before decrypting, you will invalidate the user's data by making a new user key. - const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; - await this.masterPasswordService.setForceSetPasswordReason( - ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission, - userId, - ); - } - - if (this.onSuccessfulLoginTde != null) { - // Note: awaiting this will currently cause a hang on desktop & browser as they will wait for a full sync to complete - // before navigating to the success route. - // 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.onSuccessfulLoginTde(); - } - - // 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.navigateViaCallbackOrRoute( - this.onSuccessfulLoginTdeNavigate, - // Navigate to TDE page (if user was on trusted device and TDE has decrypted - // their user key, the login-initiated guard will redirect them to the vault) - [this.trustedDeviceEncRoute], - ); - } - - private async handleChangePasswordRequired(orgIdentifier: string) { - await this.router.navigate([this.changePasswordRoute], { - queryParams: { - identifier: orgIdentifier, - }, - }); - } - - /** - * Determines if a user needs to reset their password based on certain conditions. - * Users can be forced to reset their password via an admin or org policy disallowing weak passwords. - * Note: this is different from the SSO component login flow as a user can - * login with MP and then have to pass 2FA to finish login and we can actually - * evaluate if they have a weak password at that time. - * - * @param {AuthResult} authResult - The authentication result. - * @returns {boolean} Returns true if a password reset is required, false otherwise. - */ - private isForcePasswordResetRequired(authResult: AuthResult): boolean { - const forceResetReasons = [ - ForceSetPasswordReason.AdminForcePasswordReset, - ForceSetPasswordReason.WeakMasterPassword, - ]; - - return forceResetReasons.includes(authResult.forcePasswordReset); - } - - private async handleForcePasswordReset(orgIdentifier: string) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate([this.forcePasswordResetRoute], { - queryParams: { - identifier: orgIdentifier, - }, - }); - } - - private async handleSuccessfulLogin() { - if (this.onSuccessfulLogin != null) { - // Note: awaiting this will currently cause a hang on desktop & browser as they will wait for a full sync to complete - // before navigating to the success route. - // 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.onSuccessfulLogin(); - } - await this.navigateViaCallbackOrRoute(this.onSuccessfulLoginNavigate, [this.successRoute]); - } - - private async navigateViaCallbackOrRoute( - callback: () => Promise, - commands: unknown[], - extras?: NavigationExtras, - ): Promise { - if (callback) { - await callback(); - } else { - await this.router.navigate(commands, extras); - } - } - - private async authing(): Promise { - return (await firstValueFrom(this.loginStrategyService.currentAuthType$)) !== null; - } - - private async needsLock(): Promise { - const authType = await firstValueFrom(this.loginStrategyService.currentAuthType$); - return authType == AuthenticationType.Sso || authType == AuthenticationType.UserApiKey; - } -} diff --git a/libs/angular/src/auth/components/two-factor-auth/two-factor-options.component.html b/libs/angular/src/auth/components/two-factor-auth/two-factor-options.component.html deleted file mode 100644 index bd19c4cd0bf..00000000000 --- a/libs/angular/src/auth/components/two-factor-auth/two-factor-options.component.html +++ /dev/null @@ -1,51 +0,0 @@ - - - {{ "twoStepOptions" | i18n }} - - -
    -
    -
    - -
    -
    -

    {{ p.name }}

    -

    {{ p.description }}

    -
    -
    - -
    -
    -
    -
    -
    -
    -
    - rc logo -
    -
    -

    {{ "recoveryCodeTitle" | i18n }}

    -

    {{ "recoveryCodeDesc" | i18n }}

    -
    -
    - -
    -
    -
    -
    - - - -
    diff --git a/libs/angular/src/auth/components/two-factor-auth/two-factor-options.component.ts b/libs/angular/src/auth/components/two-factor-auth/two-factor-options.component.ts deleted file mode 100644 index 61f225cc21c..00000000000 --- a/libs/angular/src/auth/components/two-factor-auth/two-factor-options.component.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { DialogRef } from "@angular/cdk/dialog"; -import { CommonModule } from "@angular/common"; -import { Component, EventEmitter, OnInit, Output } from "@angular/core"; -import { firstValueFrom } from "rxjs"; - -import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; -import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; -import { ClientType } from "@bitwarden/common/enums"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { ButtonModule, DialogModule, DialogService, TypographyModule } from "@bitwarden/components"; - -export enum TwoFactorOptionsDialogResult { - Provider = "Provider selected", - Recover = "Recover selected", -} - -export type TwoFactorOptionsDialogResultType = { - result: TwoFactorOptionsDialogResult; - type: TwoFactorProviderType; -}; - -@Component({ - standalone: true, - selector: "app-two-factor-options", - templateUrl: "two-factor-options.component.html", - imports: [CommonModule, JslibModule, DialogModule, ButtonModule, TypographyModule], - providers: [I18nPipe], -}) -export class TwoFactorOptionsComponent implements OnInit { - @Output() onProviderSelected = new EventEmitter(); - @Output() onRecoverSelected = new EventEmitter(); - - providers: any[] = []; - - // todo: remove after porting to two-factor-options-v2 - // icons cause the layout to break on browser extensions - areIconsDisabled = false; - - constructor( - private twoFactorService: TwoFactorService, - private environmentService: EnvironmentService, - private dialogRef: DialogRef, - private platformUtilsService: PlatformUtilsService, - ) { - // todo: remove after porting to two-factor-options-v2 - if (this.platformUtilsService.getClientType() == ClientType.Browser) { - this.areIconsDisabled = true; - } - } - - async ngOnInit() { - this.providers = await this.twoFactorService.getSupportedProviders(window); - } - - async choose(p: any) { - this.onProviderSelected.emit(p.type); - this.dialogRef.close({ result: TwoFactorOptionsDialogResult.Provider, type: p.type }); - } - - async recover() { - const env = await firstValueFrom(this.environmentService.environment$); - const webVault = env.getWebVaultUrl(); - this.platformUtilsService.launchUri(webVault + "/#/recover-2fa"); - this.onRecoverSelected.emit(); - this.dialogRef.close({ result: TwoFactorOptionsDialogResult.Recover }); - } - - static open(dialogService: DialogService) { - return dialogService.open(TwoFactorOptionsComponent); - } -} diff --git a/libs/angular/src/auth/components/two-factor-options.component.ts b/libs/angular/src/auth/components/two-factor-options-v1.component.ts similarity index 96% rename from libs/angular/src/auth/components/two-factor-options.component.ts rename to libs/angular/src/auth/components/two-factor-options-v1.component.ts index 1bbf81fa34f..f02eabcc156 100644 --- a/libs/angular/src/auth/components/two-factor-options.component.ts +++ b/libs/angular/src/auth/components/two-factor-options-v1.component.ts @@ -12,7 +12,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @Directive() -export class TwoFactorOptionsComponent implements OnInit { +export class TwoFactorOptionsComponentV1 implements OnInit { @Output() onProviderSelected = new EventEmitter(); @Output() onRecoverSelected = new EventEmitter(); diff --git a/libs/angular/src/auth/components/two-factor.component.spec.ts b/libs/angular/src/auth/components/two-factor-v1.component.spec.ts similarity index 96% rename from libs/angular/src/auth/components/two-factor.component.spec.ts rename to libs/angular/src/auth/components/two-factor-v1.component.spec.ts index 5a1903d6671..47075acc758 100644 --- a/libs/angular/src/auth/components/two-factor.component.spec.ts +++ b/libs/angular/src/auth/components/two-factor-v1.component.spec.ts @@ -4,7 +4,6 @@ import { ActivatedRoute, convertToParamMap, Router } from "@angular/router"; import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; -// eslint-disable-next-line no-restricted-imports import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; import { LoginStrategyServiceAbstraction, @@ -16,13 +15,13 @@ import { } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; -import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; +import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; @@ -34,11 +33,11 @@ import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/sp import { UserId } from "@bitwarden/common/types/guid"; import { ToastService } from "@bitwarden/components"; -import { TwoFactorComponent } from "./two-factor.component"; +import { TwoFactorComponentV1 } from "./two-factor-v1.component"; // test component that extends the TwoFactorComponent @Component({}) -class TestTwoFactorComponent extends TwoFactorComponent {} +class TestTwoFactorComponent extends TwoFactorComponentV1 {} interface TwoFactorComponentProtected { trustedDeviceEncRoute: string; @@ -86,12 +85,12 @@ describe("TwoFactorComponent", () => { }; let selectedUserDecryptionOptions: BehaviorSubject; - let twoFactorTimeoutSubject: BehaviorSubject; + let authenticationSessionTimeoutSubject: BehaviorSubject; beforeEach(() => { - twoFactorTimeoutSubject = new BehaviorSubject(false); + authenticationSessionTimeoutSubject = new BehaviorSubject(false); mockLoginStrategyService = mock(); - mockLoginStrategyService.twoFactorTimeout$ = twoFactorTimeoutSubject; + mockLoginStrategyService.authenticationSessionTimeout$ = authenticationSessionTimeoutSubject; mockRouter = mock(); mockI18nService = mock(); mockApiService = mock(); @@ -153,7 +152,9 @@ describe("TwoFactorComponent", () => { }), }; - selectedUserDecryptionOptions = new BehaviorSubject(null); + selectedUserDecryptionOptions = new BehaviorSubject( + mockUserDecryptionOpts.withMasterPassword, + ); mockUserDecryptionOptionsService.userDecryptionOptions$ = selectedUserDecryptionOptions; TestBed.configureTestingModule({ @@ -497,8 +498,8 @@ describe("TwoFactorComponent", () => { }); it("navigates to the timeout route when timeout expires", async () => { - twoFactorTimeoutSubject.next(true); + authenticationSessionTimeoutSubject.next(true); - expect(mockRouter.navigate).toHaveBeenCalledWith(["2fa-timeout"]); + expect(mockRouter.navigate).toHaveBeenCalledWith(["authentication-timeout"]); }); }); diff --git a/libs/angular/src/auth/components/two-factor.component.ts b/libs/angular/src/auth/components/two-factor-v1.component.ts similarity index 96% rename from libs/angular/src/auth/components/two-factor.component.ts rename to libs/angular/src/auth/components/two-factor-v1.component.ts index 18bfe546600..3fda2685f5e 100644 --- a/libs/angular/src/auth/components/two-factor.component.ts +++ b/libs/angular/src/auth/components/two-factor-v1.component.ts @@ -6,7 +6,6 @@ import { ActivatedRoute, NavigationExtras, Router } from "@angular/router"; import { firstValueFrom } from "rxjs"; import { first } from "rxjs/operators"; -// eslint-disable-next-line no-restricted-imports import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; import { LoginStrategyServiceAbstraction, @@ -17,7 +16,6 @@ import { } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type"; @@ -28,6 +26,7 @@ import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/ide import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request"; import { TwoFactorProviders } from "@bitwarden/common/auth/services/two-factor.service"; import { WebAuthnIFrame } from "@bitwarden/common/auth/webauthn-iframe"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; @@ -40,7 +39,7 @@ import { ToastService } from "@bitwarden/components"; import { CaptchaProtectedComponent } from "./captcha-protected.component"; @Directive() -export class TwoFactorComponent extends CaptchaProtectedComponent implements OnInit, OnDestroy { +export class TwoFactorComponentV1 extends CaptchaProtectedComponent implements OnInit, OnDestroy { token = ""; remember = false; webAuthnReady = false; @@ -71,7 +70,7 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI protected changePasswordRoute = "set-password"; protected forcePasswordResetRoute = "update-temp-password"; protected successRoute = "vault"; - protected twoFactorTimeoutRoute = "2fa-timeout"; + protected twoFactorTimeoutRoute = "authentication-timeout"; get isDuoProvider(): boolean { return ( @@ -102,10 +101,11 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI protected toastService: ToastService, ) { super(environmentService, i18nService, platformUtilsService, toastService); + this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win); - // Add subscription to twoFactorTimeout$ and navigate to twoFactorTimeoutRoute if expired - this.loginStrategyService.twoFactorTimeout$ + // Add subscription to authenticationSessionTimeout$ and navigate to twoFactorTimeoutRoute if expired + this.loginStrategyService.authenticationSessionTimeout$ .pipe(takeUntilDestroyed()) .subscribe(async (expired) => { if (!expired) { @@ -157,7 +157,7 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI this.toastService.showToast({ variant: "error", title: this.i18nService.t("errorOccurred"), - message: error, + message: this.i18nService.t("webauthnCancelOrTimeout"), }); }, (info: string) => { @@ -287,7 +287,8 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI // Save off the OrgSsoIdentifier for use in the TDE flows // - TDE login decryption options component // - Browser SSO on extension open - await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier(this.orgIdentifier); + const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; + await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier(this.orgIdentifier, userId); this.loginEmailService.clearValues(); // note: this flow affects both TDE & standard users diff --git a/libs/angular/src/auth/components/update-password.component.ts b/libs/angular/src/auth/components/update-password.component.ts index 1d1057d9aa6..77e854753d7 100644 --- a/libs/angular/src/auth/components/update-password.component.ts +++ b/libs/angular/src/auth/components/update-password.component.ts @@ -3,24 +3,22 @@ import { Directive } from "@angular/core"; import { Router } from "@angular/router"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; +import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { VerificationType } from "@bitwarden/common/auth/enums/verification-type"; import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request"; import { Verification } from "@bitwarden/common/auth/types/verification"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { MasterKey, UserKey } from "@bitwarden/common/types/key"; import { DialogService, ToastService } from "@bitwarden/components"; -import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { KdfConfigService, KeyService } from "@bitwarden/key-management"; import { ChangePasswordComponent as BaseChangePasswordComponent } from "./change-password.component"; @@ -39,12 +37,10 @@ export class UpdatePasswordComponent extends BaseChangePasswordComponent { protected router: Router, i18nService: I18nService, platformUtilsService: PlatformUtilsService, - passwordGenerationService: PasswordGenerationServiceAbstraction, policyService: PolicyService, keyService: KeyService, messagingService: MessagingService, - private apiService: ApiService, - stateService: StateService, + private masterPasswordApiService: MasterPasswordApiService, private userVerificationService: UserVerificationService, private logService: LogService, dialogService: DialogService, @@ -57,10 +53,8 @@ export class UpdatePasswordComponent extends BaseChangePasswordComponent { i18nService, keyService, messagingService, - passwordGenerationService, platformUtilsService, policyService, - stateService, dialogService, kdfConfigService, masterPasswordService, @@ -123,9 +117,7 @@ export class UpdatePasswordComponent extends BaseChangePasswordComponent { request.key = newUserKey[1].encryptedString; // Update user's password - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.apiService.postPassword(request); + await this.masterPasswordApiService.postPassword(request); this.toastService.showToast({ variant: "success", diff --git a/libs/angular/src/auth/components/update-temp-password.component.ts b/libs/angular/src/auth/components/update-temp-password.component.ts index 3fb1f7400ec..267beb2b822 100644 --- a/libs/angular/src/auth/components/update-temp-password.component.ts +++ b/libs/angular/src/auth/components/update-temp-password.component.ts @@ -4,11 +4,10 @@ import { Directive, OnInit } from "@angular/core"; import { Router } from "@angular/router"; import { firstValueFrom, map } from "rxjs"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; +import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { VerificationType } from "@bitwarden/common/auth/enums/verification-type"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; @@ -16,16 +15,15 @@ import { PasswordRequest } from "@bitwarden/common/auth/models/request/password. import { UpdateTdeOffboardingPasswordRequest } from "@bitwarden/common/auth/models/request/update-tde-offboarding-password.request"; import { UpdateTempPasswordRequest } from "@bitwarden/common/auth/models/request/update-temp-password.request"; import { MasterPasswordVerification } from "@bitwarden/common/auth/types/verification"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { MasterKey, UserKey } from "@bitwarden/common/types/key"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { DialogService, ToastService } from "@bitwarden/components"; -import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { KdfConfigService, KeyService } from "@bitwarden/key-management"; import { ChangePasswordComponent as BaseChangePasswordComponent } from "./change-password.component"; @@ -51,12 +49,10 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent imp constructor( i18nService: I18nService, platformUtilsService: PlatformUtilsService, - passwordGenerationService: PasswordGenerationServiceAbstraction, policyService: PolicyService, keyService: KeyService, messagingService: MessagingService, - private apiService: ApiService, - stateService: StateService, + private masterPasswordApiService: MasterPasswordApiService, private syncService: SyncService, private logService: LogService, private userVerificationService: UserVerificationService, @@ -71,10 +67,8 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent imp i18nService, keyService, messagingService, - passwordGenerationService, platformUtilsService, policyService, - stateService, dialogService, kdfConfigService, masterPasswordService, @@ -208,7 +202,7 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent imp request.newMasterPasswordHash = masterPasswordHash; request.masterPasswordHint = this.hint; - return this.apiService.putUpdateTempPassword(request); + return this.masterPasswordApiService.putUpdateTempPassword(request); } private async updatePassword(newMasterPasswordHash: string, userKey: [UserKey, EncString]) { @@ -220,7 +214,7 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent imp request.newMasterPasswordHash = newMasterPasswordHash; request.key = userKey[1].encryptedString; - return this.apiService.postPassword(request); + return this.masterPasswordApiService.postPassword(request); } private async updateTdeOffboardingPassword( @@ -232,6 +226,6 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent imp request.newMasterPasswordHash = masterPasswordHash; request.masterPasswordHint = this.hint; - return this.apiService.putUpdateTdeOffboardingPassword(request); + return this.masterPasswordApiService.putUpdateTdeOffboardingPassword(request); } } diff --git a/libs/angular/src/auth/components/user-verification.component.ts b/libs/angular/src/auth/components/user-verification.component.ts index 7af53805a09..408d8403b88 100644 --- a/libs/angular/src/auth/components/user-verification.component.ts +++ b/libs/angular/src/auth/components/user-verification.component.ts @@ -23,7 +23,6 @@ import { KeyService } from "@bitwarden/key-management"; @Directive({ selector: "app-user-verification", }) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil export class UserVerificationComponent implements ControlValueAccessor, OnInit, OnDestroy { private _invalidSecret = false; @Input() diff --git a/libs/angular/src/auth/functions/unauth-ui-refresh-redirect.spec.ts b/libs/angular/src/auth/functions/unauth-ui-refresh-redirect.spec.ts deleted file mode 100644 index 887f528d547..00000000000 --- a/libs/angular/src/auth/functions/unauth-ui-refresh-redirect.spec.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { TestBed } from "@angular/core/testing"; -import { Navigation, Router, UrlTree } from "@angular/router"; -import { mock, MockProxy } from "jest-mock-extended"; - -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; - -import { unauthUiRefreshRedirect } from "./unauth-ui-refresh-redirect"; - -describe("unauthUiRefreshRedirect", () => { - let configService: MockProxy; - let router: MockProxy; - - beforeEach(() => { - configService = mock(); - router = mock(); - - TestBed.configureTestingModule({ - providers: [ - { provide: ConfigService, useValue: configService }, - { provide: Router, useValue: router }, - ], - }); - }); - - it("returns true when UnauthenticatedExtensionUIRefresh flag is disabled", async () => { - configService.getFeatureFlag.mockResolvedValue(false); - - const result = await TestBed.runInInjectionContext(() => - unauthUiRefreshRedirect("/redirect")(), - ); - - expect(result).toBe(true); - expect(configService.getFeatureFlag).toHaveBeenCalledWith( - FeatureFlag.UnauthenticatedExtensionUIRefresh, - ); - expect(router.parseUrl).not.toHaveBeenCalled(); - }); - - it("returns UrlTree when UnauthenticatedExtensionUIRefresh flag is enabled and preserves query params", async () => { - configService.getFeatureFlag.mockResolvedValue(true); - - const urlTree = new UrlTree(); - urlTree.queryParams = { test: "test" }; - - const navigation: Navigation = { - extras: {}, - id: 0, - initialUrl: new UrlTree(), - extractedUrl: urlTree, - trigger: "imperative", - previousNavigation: undefined, - }; - - router.getCurrentNavigation.mockReturnValue(navigation); - - await TestBed.runInInjectionContext(() => unauthUiRefreshRedirect("/redirect")()); - - expect(configService.getFeatureFlag).toHaveBeenCalledWith( - FeatureFlag.UnauthenticatedExtensionUIRefresh, - ); - expect(router.createUrlTree).toHaveBeenCalledWith(["/redirect"], { - queryParams: urlTree.queryParams, - }); - }); -}); diff --git a/libs/angular/src/auth/functions/unauth-ui-refresh-redirect.ts b/libs/angular/src/auth/functions/unauth-ui-refresh-redirect.ts deleted file mode 100644 index 2cb53d5324f..00000000000 --- a/libs/angular/src/auth/functions/unauth-ui-refresh-redirect.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { inject } from "@angular/core"; -import { UrlTree, Router } from "@angular/router"; - -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; - -/** - * Helper function to redirect to a new URL based on the UnauthenticatedExtensionUIRefresh feature flag. - * @param redirectUrl - The URL to redirect to if the UnauthenticatedExtensionUIRefresh flag is enabled. - */ -export function unauthUiRefreshRedirect(redirectUrl: string): () => Promise { - return async () => { - const configService = inject(ConfigService); - const router = inject(Router); - const shouldRedirect = await configService.getFeatureFlag( - FeatureFlag.UnauthenticatedExtensionUIRefresh, - ); - if (shouldRedirect) { - const currentNavigation = router.getCurrentNavigation(); - const queryParams = currentNavigation?.extractedUrl?.queryParams || {}; - - // Preserve query params when redirecting as it is likely that the refreshed component - // will be consuming the same query params. - return router.createUrlTree([redirectUrl], { queryParams }); - } else { - return true; - } - }; -} diff --git a/libs/angular/src/auth/guards/active-auth.guard.spec.ts b/libs/angular/src/auth/guards/active-auth.guard.spec.ts new file mode 100644 index 00000000000..c3417b9d41d --- /dev/null +++ b/libs/angular/src/auth/guards/active-auth.guard.spec.ts @@ -0,0 +1,71 @@ +import { Component } from "@angular/core"; +import { TestBed } from "@angular/core/testing"; +import { Router } from "@angular/router"; +import { RouterTestingModule } from "@angular/router/testing"; +import { MockProxy, mock } from "jest-mock-extended"; +import { BehaviorSubject } from "rxjs"; + +import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; +import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; + +import { activeAuthGuard } from "./active-auth.guard"; + +@Component({ template: "" }) +class EmptyComponent {} + +describe("activeAuthGuard", () => { + const setup = (authType: AuthenticationType | null) => { + const loginStrategyService: MockProxy = + mock(); + const currentAuthTypeSubject = new BehaviorSubject(authType); + loginStrategyService.currentAuthType$ = currentAuthTypeSubject; + + const logService: MockProxy = mock(); + + const testBed = TestBed.configureTestingModule({ + imports: [ + RouterTestingModule.withRoutes([ + { path: "", component: EmptyComponent }, + { + path: "protected-route", + component: EmptyComponent, + canActivate: [activeAuthGuard()], + }, + { path: "login", component: EmptyComponent }, + ]), + ], + providers: [ + { provide: LoginStrategyServiceAbstraction, useValue: loginStrategyService }, + { provide: LogService, useValue: logService }, + ], + declarations: [EmptyComponent], + }); + + return { + router: testBed.inject(Router), + logService, + loginStrategyService, + }; + }; + + it("creates the guard", () => { + const { router } = setup(AuthenticationType.Password); + expect(router).toBeTruthy(); + }); + + it("allows access with an active login session", async () => { + const { router } = setup(AuthenticationType.Password); + + await router.navigate(["protected-route"]); + expect(router.url).toBe("/protected-route"); + }); + + it("redirects to login with no active session", async () => { + const { router, logService } = setup(null); + + await router.navigate(["protected-route"]); + expect(router.url).toBe("/login"); + expect(logService.error).toHaveBeenCalledWith("No active login session found."); + }); +}); diff --git a/libs/angular/src/auth/guards/active-auth.guard.ts b/libs/angular/src/auth/guards/active-auth.guard.ts new file mode 100644 index 00000000000..56213bbd979 --- /dev/null +++ b/libs/angular/src/auth/guards/active-auth.guard.ts @@ -0,0 +1,28 @@ +import { inject } from "@angular/core"; +import { CanActivateFn, Router } from "@angular/router"; +import { firstValueFrom } from "rxjs"; + +import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; + +/** + * Guard that ensures there is an active login session before allowing access + * to the new device verification route. + * If not, redirects to login. + */ +export function activeAuthGuard(): CanActivateFn { + return async () => { + const loginStrategyService = inject(LoginStrategyServiceAbstraction); + const logService = inject(LogService); + const router = inject(Router); + + // Check if we have a valid login session + const authType = await firstValueFrom(loginStrategyService.currentAuthType$); + if (authType === null) { + logService.error("No active login session found."); + return router.createUrlTree(["/login"]); + } + + return true; + }; +} diff --git a/libs/angular/src/auth/guards/auth.guard.spec.ts b/libs/angular/src/auth/guards/auth.guard.spec.ts index a6bfd72e23e..4ed72baf284 100644 --- a/libs/angular/src/auth/guards/auth.guard.spec.ts +++ b/libs/angular/src/auth/guards/auth.guard.spec.ts @@ -11,10 +11,10 @@ import { AccountService, } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; -import { MasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; +import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; +import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { UserId } from "@bitwarden/common/types/guid"; diff --git a/libs/angular/src/auth/guards/auth.guard.ts b/libs/angular/src/auth/guards/auth.guard.ts index 8d20d7323da..329e365e542 100644 --- a/libs/angular/src/auth/guards/auth.guard.ts +++ b/libs/angular/src/auth/guards/auth.guard.ts @@ -12,10 +12,10 @@ import { firstValueFrom } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; -import { MasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; +import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; +import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; export const authGuard: CanActivateFn = async ( diff --git a/libs/angular/src/auth/guards/index.ts b/libs/angular/src/auth/guards/index.ts index 1760a870b3a..026848c4b08 100644 --- a/libs/angular/src/auth/guards/index.ts +++ b/libs/angular/src/auth/guards/index.ts @@ -1,4 +1,5 @@ export * from "./auth.guard"; +export * from "./active-auth.guard"; export * from "./lock.guard"; export * from "./redirect.guard"; export * from "./tde-decryption-required.guard"; diff --git a/libs/angular/src/auth/guards/lock.guard.spec.ts b/libs/angular/src/auth/guards/lock.guard.spec.ts index d801ef0f8f9..32b8ecbb9dd 100644 --- a/libs/angular/src/auth/guards/lock.guard.spec.ts +++ b/libs/angular/src/auth/guards/lock.guard.spec.ts @@ -5,17 +5,17 @@ import { MockProxy, mock } from "jest-mock-extended"; import { BehaviorSubject, of } from "rxjs"; import { EmptyComponent } from "@bitwarden/angular/platform/guard/feature-flag.guard.spec"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { Account, AccountInfo, AccountService, } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { ClientType } from "@bitwarden/common/enums"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; +import { VaultTimeoutSettingsService } from "@bitwarden/common/key-management/vault-timeout"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { UserId } from "@bitwarden/common/types/guid"; diff --git a/libs/angular/src/auth/guards/lock.guard.ts b/libs/angular/src/auth/guards/lock.guard.ts index 244e9935281..10ad4917f32 100644 --- a/libs/angular/src/auth/guards/lock.guard.ts +++ b/libs/angular/src/auth/guards/lock.guard.ts @@ -7,13 +7,13 @@ import { } from "@angular/router"; import { firstValueFrom } from "rxjs"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { ClientType } from "@bitwarden/common/enums"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; +import { VaultTimeoutSettingsService } from "@bitwarden/common/key-management/vault-timeout"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { KeyService } from "@bitwarden/key-management"; diff --git a/libs/angular/src/auth/guards/redirect.guard.ts b/libs/angular/src/auth/guards/redirect.guard.ts index f79f5d3c4b4..00dd20c9909 100644 --- a/libs/angular/src/auth/guards/redirect.guard.ts +++ b/libs/angular/src/auth/guards/redirect.guard.ts @@ -3,8 +3,8 @@ import { CanActivateFn, Router } from "@angular/router"; import { firstValueFrom } from "rxjs"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { KeyService } from "@bitwarden/key-management"; diff --git a/libs/angular/src/auth/guards/tde-decryption-required.guard.ts b/libs/angular/src/auth/guards/tde-decryption-required.guard.ts index 6bb83021fd0..1d98b1fa740 100644 --- a/libs/angular/src/auth/guards/tde-decryption-required.guard.ts +++ b/libs/angular/src/auth/guards/tde-decryption-required.guard.ts @@ -8,8 +8,8 @@ import { import { firstValueFrom } from "rxjs"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { KeyService } from "@bitwarden/key-management"; diff --git a/libs/angular/src/auth/guards/unauth.guard.spec.ts b/libs/angular/src/auth/guards/unauth.guard.spec.ts index 6d8619f4d43..ad0ce680a1f 100644 --- a/libs/angular/src/auth/guards/unauth.guard.spec.ts +++ b/libs/angular/src/auth/guards/unauth.guard.spec.ts @@ -5,17 +5,48 @@ import { MockProxy, mock } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; import { EmptyComponent } from "@bitwarden/angular/platform/guard/feature-flag.guard.spec"; +import { Account, 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 { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { UserId } from "@bitwarden/common/types/guid"; +import { KeyService } from "@bitwarden/key-management"; import { unauthGuardFn } from "./unauth.guard"; describe("UnauthGuard", () => { - const setup = (authStatus: AuthenticationStatus) => { + const activeUser: Account = { + id: "fake_user_id" as UserId, + email: "test@email.com", + emailVerified: true, + name: "Test User", + }; + + const setup = ( + activeUser: Account | null, + authStatus: AuthenticationStatus | null = null, + tdeEnabled: boolean = false, + everHadUserKey: boolean = false, + ) => { + const accountService: MockProxy = mock(); const authService: MockProxy = mock(); - authService.getAuthStatus.mockResolvedValue(authStatus); - const activeAccountStatusObservable = new BehaviorSubject(authStatus); - authService.activeAccountStatus$ = activeAccountStatusObservable; + const keyService: MockProxy = mock(); + const deviceTrustService: MockProxy = + mock(); + const logService: MockProxy = mock(); + + accountService.activeAccount$ = new BehaviorSubject(activeUser); + + if (authStatus !== null) { + const activeAccountStatusObservable = new BehaviorSubject(authStatus); + authService.authStatusFor$.mockReturnValue(activeAccountStatusObservable); + } + + keyService.everHadUserKey$ = new BehaviorSubject(everHadUserKey); + deviceTrustService.supportsDeviceTrustByUserId$.mockReturnValue( + new BehaviorSubject(tdeEnabled), + ); const testBed = TestBed.configureTestingModule({ imports: [ @@ -30,6 +61,7 @@ describe("UnauthGuard", () => { { path: "lock", component: EmptyComponent }, { path: "testhomepage", component: EmptyComponent }, { path: "testlocked", component: EmptyComponent }, + { path: "login-initiated", component: EmptyComponent }, { path: "testOverrides", component: EmptyComponent, @@ -39,7 +71,13 @@ describe("UnauthGuard", () => { }, ]), ], - providers: [{ provide: AuthService, useValue: authService }], + providers: [ + { provide: AccountService, useValue: accountService }, + { provide: AuthService, useValue: authService }, + { provide: KeyService, useValue: keyService }, + { provide: DeviceTrustServiceAbstraction, useValue: deviceTrustService }, + { provide: LogService, useValue: logService }, + ], }); return { @@ -48,40 +86,54 @@ describe("UnauthGuard", () => { }; it("should be created", () => { - const { router } = setup(AuthenticationStatus.LoggedOut); + const { router } = setup(null, AuthenticationStatus.LoggedOut); expect(router).toBeTruthy(); }); it("should redirect to /vault for guarded routes when logged in and unlocked", async () => { - const { router } = setup(AuthenticationStatus.Unlocked); + const { router } = setup(activeUser, AuthenticationStatus.Unlocked); await router.navigateByUrl("unauth-guarded-route"); expect(router.url).toBe("/vault"); }); - it("should allow access to guarded routes when logged out", async () => { - const { router } = setup(AuthenticationStatus.LoggedOut); + it("should allow access to guarded routes when account is null", async () => { + const { router } = setup(null); await router.navigateByUrl("unauth-guarded-route"); expect(router.url).toBe("/unauth-guarded-route"); }); + it("should allow access to guarded routes when logged out", async () => { + const { router } = setup(null, AuthenticationStatus.LoggedOut); + + await router.navigateByUrl("unauth-guarded-route"); + expect(router.url).toBe("/unauth-guarded-route"); + }); + + it("should redirect to /login-initiated when locked, TDE is enabled, and the user hasn't decrypted yet", async () => { + const { router } = setup(activeUser, AuthenticationStatus.Locked, true, false); + + await router.navigateByUrl("unauth-guarded-route"); + expect(router.url).toBe("/login-initiated"); + }); + it("should redirect to /lock for guarded routes when locked", async () => { - const { router } = setup(AuthenticationStatus.Locked); + const { router } = setup(activeUser, AuthenticationStatus.Locked); await router.navigateByUrl("unauth-guarded-route"); expect(router.url).toBe("/lock"); }); it("should redirect to /testhomepage for guarded routes when testOverrides are provided and the account is unlocked", async () => { - const { router } = setup(AuthenticationStatus.Unlocked); + const { router } = setup(activeUser, AuthenticationStatus.Unlocked); await router.navigateByUrl("testOverrides"); expect(router.url).toBe("/testhomepage"); }); it("should redirect to /testlocked for guarded routes when testOverrides are provided and the account is locked", async () => { - const { router } = setup(AuthenticationStatus.Locked); + const { router } = setup(activeUser, AuthenticationStatus.Locked); await router.navigateByUrl("testOverrides"); expect(router.url).toBe("/testlocked"); diff --git a/libs/angular/src/auth/guards/unauth.guard.ts b/libs/angular/src/auth/guards/unauth.guard.ts index f96668773ef..6764b46843e 100644 --- a/libs/angular/src/auth/guards/unauth.guard.ts +++ b/libs/angular/src/auth/guards/unauth.guard.ts @@ -1,9 +1,13 @@ import { inject } from "@angular/core"; -import { CanActivateFn, Router, UrlTree } from "@angular/router"; -import { Observable, map } from "rxjs"; +import { ActivatedRouteSnapshot, CanActivateFn, Router, UrlTree } from "@angular/router"; +import { firstValueFrom } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { KeyService } from "@bitwarden/key-management"; type UnauthRoutes = { homepage: () => string; @@ -15,23 +19,54 @@ const defaultRoutes: UnauthRoutes = { locked: "/lock", }; -function unauthGuard(routes: UnauthRoutes): Observable { +// TODO: PM-17195 - Investigate consolidating unauthGuard and redirectGuard into AuthStatusGuard +async function unauthGuard( + route: ActivatedRouteSnapshot, + routes: UnauthRoutes, +): Promise { + const accountService = inject(AccountService); const authService = inject(AuthService); const router = inject(Router); + const keyService = inject(KeyService); + const deviceTrustService = inject(DeviceTrustServiceAbstraction); + const logService = inject(LogService); - return authService.activeAccountStatus$.pipe( - map((status) => { - if (status == null || status === AuthenticationStatus.LoggedOut) { - return true; - } else if (status === AuthenticationStatus.Locked) { - return router.createUrlTree([routes.locked]); - } else { - return router.createUrlTree([routes.homepage()]); - } - }), + const activeUser = await firstValueFrom(accountService.activeAccount$); + + if (!activeUser) { + return true; + } + + const authStatus = await firstValueFrom(authService.authStatusFor$(activeUser.id)); + + if (authStatus == null || authStatus === AuthenticationStatus.LoggedOut) { + return true; + } + + if (authStatus === AuthenticationStatus.Unlocked) { + return router.createUrlTree([routes.homepage()]); + } + + const tdeEnabled = await firstValueFrom( + deviceTrustService.supportsDeviceTrustByUserId$(activeUser.id), ); + const everHadUserKey = await firstValueFrom(keyService.everHadUserKey$); + + // If locked, TDE is enabled, and the user hasn't decrypted yet, then redirect to the + // login decryption options component. + if (authStatus === AuthenticationStatus.Locked && tdeEnabled && !everHadUserKey) { + logService.info( + "Sending user to TDE decryption options. AuthStatus is %s. TDE support is %s. Ever had user key is %s.", + AuthenticationStatus[authStatus], + tdeEnabled, + everHadUserKey, + ); + return router.createUrlTree(["/login-initiated"]); + } + + return router.createUrlTree([routes.locked]); } export function unauthGuardFn(overrides: Partial = {}): CanActivateFn { - return () => unauthGuard({ ...defaultRoutes, ...overrides }); + return async (route) => unauthGuard(route, { ...defaultRoutes, ...overrides }); } diff --git a/libs/angular/src/auth/services/device-trust-toast.service.abstraction.ts b/libs/angular/src/auth/services/device-trust-toast.service.abstraction.ts new file mode 100644 index 00000000000..3de288168b1 --- /dev/null +++ b/libs/angular/src/auth/services/device-trust-toast.service.abstraction.ts @@ -0,0 +1,9 @@ +import { Observable } from "rxjs"; + +export abstract class DeviceTrustToastService { + /** + * An observable pipeline that observes any cross-application toast messages + * that need to be shown as part of the trusted device encryption (TDE) process. + */ + abstract setupListeners$: Observable; +} diff --git a/libs/angular/src/auth/services/device-trust-toast.service.implementation.ts b/libs/angular/src/auth/services/device-trust-toast.service.implementation.ts new file mode 100644 index 00000000000..4013cf8df96 --- /dev/null +++ b/libs/angular/src/auth/services/device-trust-toast.service.implementation.ts @@ -0,0 +1,44 @@ +import { merge, Observable, tap } from "rxjs"; + +import { AuthRequestServiceAbstraction } from "@bitwarden/auth/common"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { ToastService } from "@bitwarden/components"; + +import { DeviceTrustToastService as DeviceTrustToastServiceAbstraction } from "./device-trust-toast.service.abstraction"; + +export class DeviceTrustToastService implements DeviceTrustToastServiceAbstraction { + private adminLoginApproved$: Observable; + private deviceTrusted$: Observable; + + setupListeners$: Observable; + + constructor( + private authRequestService: AuthRequestServiceAbstraction, + private deviceTrustService: DeviceTrustServiceAbstraction, + private i18nService: I18nService, + private toastService: ToastService, + ) { + this.adminLoginApproved$ = this.authRequestService.adminLoginApproved$.pipe( + tap(() => { + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("loginApproved"), + }); + }), + ); + + this.deviceTrusted$ = this.deviceTrustService.deviceTrusted$.pipe( + tap(() => { + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("deviceTrusted"), + }); + }), + ); + + this.setupListeners$ = merge(this.adminLoginApproved$, this.deviceTrusted$); + } +} diff --git a/libs/angular/src/auth/services/device-trust-toast.service.spec.ts b/libs/angular/src/auth/services/device-trust-toast.service.spec.ts new file mode 100644 index 00000000000..96b7db9d0ce --- /dev/null +++ b/libs/angular/src/auth/services/device-trust-toast.service.spec.ts @@ -0,0 +1,167 @@ +import { mock, MockProxy } from "jest-mock-extended"; +import { EMPTY, of } from "rxjs"; + +import { AuthRequestServiceAbstraction } from "@bitwarden/auth/common"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { ToastService } from "@bitwarden/components"; + +import { DeviceTrustToastService as DeviceTrustToastServiceAbstraction } from "./device-trust-toast.service.abstraction"; +import { DeviceTrustToastService } from "./device-trust-toast.service.implementation"; + +describe("DeviceTrustToastService", () => { + let authRequestService: MockProxy; + let deviceTrustService: MockProxy; + let i18nService: MockProxy; + let toastService: MockProxy; + + let sut: DeviceTrustToastServiceAbstraction; + + beforeEach(() => { + authRequestService = mock(); + deviceTrustService = mock(); + i18nService = mock(); + toastService = mock(); + + i18nService.t.mockImplementation((key: string) => key); // just return the key that was given + }); + + const initService = () => { + return new DeviceTrustToastService( + authRequestService, + deviceTrustService, + i18nService, + toastService, + ); + }; + + const loginApprovalToastOptions = { + variant: "success", + title: "", + message: "loginApproved", + }; + + const deviceTrustedToastOptions = { + variant: "success", + title: "", + message: "deviceTrusted", + }; + + describe("setupListeners$", () => { + describe("given adminLoginApproved$ emits and deviceTrusted$ emits", () => { + beforeEach(() => { + // Arrange + authRequestService.adminLoginApproved$ = of(undefined); + deviceTrustService.deviceTrusted$ = of(undefined); + sut = initService(); + }); + + it("should trigger a toast for login approval", (done) => { + // Act + sut.setupListeners$.subscribe({ + complete: () => { + expect(toastService.showToast).toHaveBeenCalledWith(loginApprovalToastOptions); // Assert + done(); + }, + }); + }); + + it("should trigger a toast for device trust", (done) => { + // Act + sut.setupListeners$.subscribe({ + complete: () => { + expect(toastService.showToast).toHaveBeenCalledWith(deviceTrustedToastOptions); // Assert + done(); + }, + }); + }); + }); + + describe("given adminLoginApproved$ emits and deviceTrusted$ does not emit", () => { + beforeEach(() => { + // Arrange + authRequestService.adminLoginApproved$ = of(undefined); + deviceTrustService.deviceTrusted$ = EMPTY; + sut = initService(); + }); + + it("should trigger a toast for login approval", (done) => { + // Act + sut.setupListeners$.subscribe({ + complete: () => { + expect(toastService.showToast).toHaveBeenCalledWith(loginApprovalToastOptions); // Assert + done(); + }, + }); + }); + + it("should NOT trigger a toast for device trust", (done) => { + // Act + sut.setupListeners$.subscribe({ + complete: () => { + expect(toastService.showToast).not.toHaveBeenCalledWith(deviceTrustedToastOptions); // Assert + done(); + }, + }); + }); + }); + + describe("given adminLoginApproved$ does not emit and deviceTrusted$ emits", () => { + beforeEach(() => { + // Arrange + authRequestService.adminLoginApproved$ = EMPTY; + deviceTrustService.deviceTrusted$ = of(undefined); + sut = initService(); + }); + + it("should NOT trigger a toast for login approval", (done) => { + // Act + sut.setupListeners$.subscribe({ + complete: () => { + expect(toastService.showToast).not.toHaveBeenCalledWith(loginApprovalToastOptions); // Assert + done(); + }, + }); + }); + + it("should trigger a toast for device trust", (done) => { + // Act + sut.setupListeners$.subscribe({ + complete: () => { + expect(toastService.showToast).toHaveBeenCalledWith(deviceTrustedToastOptions); // Assert + done(); + }, + }); + }); + }); + + describe("given adminLoginApproved$ does not emit and deviceTrusted$ does not emit", () => { + beforeEach(() => { + // Arrange + authRequestService.adminLoginApproved$ = EMPTY; + deviceTrustService.deviceTrusted$ = EMPTY; + sut = initService(); + }); + + it("should NOT trigger a toast for login approval", (done) => { + // Act + sut.setupListeners$.subscribe({ + complete: () => { + expect(toastService.showToast).not.toHaveBeenCalledWith(loginApprovalToastOptions); // Assert + done(); + }, + }); + }); + + it("should NOT trigger a toast for device trust", (done) => { + // Act + sut.setupListeners$.subscribe({ + complete: () => { + expect(toastService.showToast).not.toHaveBeenCalledWith(deviceTrustedToastOptions); // Assert + done(); + }, + }); + }); + }); + }); +}); diff --git a/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.ts b/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.ts index cebd81846c1..edb233cc76e 100644 --- a/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.ts +++ b/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.ts @@ -3,14 +3,14 @@ import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; import { Component, ElementRef, Inject, OnInit, ViewChild } from "@angular/core"; import { FormControl, FormGroup, Validators } from "@angular/forms"; -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; -import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { AccountService, AccountInfo } from "@bitwarden/common/auth/abstractions/account.service"; import { PaymentMethodType } from "@bitwarden/common/billing/enums"; import { BitPayInvoiceRequest } from "@bitwarden/common/billing/models/request/bit-pay-invoice.request"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -105,7 +105,16 @@ export class AddAccountCreditDialogComponent implements OnInit { this.formGroup.patchValue({ creditAmount: 20.0, }); - this.organization = await this.organizationService.get(this.dialogParams.organizationId); + this.user = await firstValueFrom(this.accountService.activeAccount$); + this.organization = await firstValueFrom( + this.organizationService + .organizations$(this.user.id) + .pipe( + map((organizations) => + organizations.find((org) => org.id === this.dialogParams.organizationId), + ), + ), + ); payPalCustomField = "organization_id:" + this.organization.id; this.payPalConfig.subject = this.organization.name; } else if (this.dialogParams.providerId) { @@ -119,7 +128,6 @@ export class AddAccountCreditDialogComponent implements OnInit { this.formGroup.patchValue({ creditAmount: 10.0, }); - this.user = await firstValueFrom(this.accountService.activeAccount$); payPalCustomField = "user_id:" + this.user.id; this.payPalConfig.subject = this.user.email; } diff --git a/libs/angular/src/billing/components/index.ts b/libs/angular/src/billing/components/index.ts index 675d7555ed2..dacb5b265bd 100644 --- a/libs/angular/src/billing/components/index.ts +++ b/libs/angular/src/billing/components/index.ts @@ -2,3 +2,4 @@ export * from "./add-account-credit-dialog/add-account-credit-dialog.component"; export * from "./invoices/invoices.component"; export * from "./invoices/no-invoices.component"; export * from "./manage-tax-information/manage-tax-information.component"; +export * from "./premium.component"; diff --git a/libs/angular/src/billing/components/invoices/invoices.component.html b/libs/angular/src/billing/components/invoices/invoices.component.html index 2a171e5b5bc..634baa4fa7f 100644 --- a/libs/angular/src/billing/components/invoices/invoices.component.html +++ b/libs/angular/src/billing/components/invoices/invoices.component.html @@ -1,10 +1,10 @@ - {{ "loading" | i18n }} + {{ "loading" | i18n }} diff --git a/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts b/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts index 13a6d2d0cc3..885afb1ae67 100644 --- a/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts +++ b/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts @@ -72,6 +72,10 @@ export class ManageTaxInformationComponent implements OnInit, OnDestroy { } } + markAllAsTouched() { + this.formGroup.markAllAsTouched(); + } + async ngOnInit() { if (this.startWith) { this.formGroup.controls.country.setValue(this.startWith.country); diff --git a/libs/angular/src/vault/components/premium.component.ts b/libs/angular/src/billing/components/premium.component.ts similarity index 76% rename from libs/angular/src/vault/components/premium.component.ts rename to libs/angular/src/billing/components/premium.component.ts index 8b1f215ef42..6d0b90385ba 100644 --- a/libs/angular/src/vault/components/premium.component.ts +++ b/libs/angular/src/billing/components/premium.component.ts @@ -6,13 +6,11 @@ import { firstValueFrom, Observable, switchMap } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { DialogService, SimpleDialogOptions } from "@bitwarden/components"; +import { DialogService, SimpleDialogOptions, ToastService } from "@bitwarden/components"; @Directive() export class PremiumComponent implements OnInit { @@ -20,17 +18,16 @@ export class PremiumComponent implements OnInit { price = 10; refreshPromise: Promise; cloudWebVaultUrl: string; - extensionRefreshFlagEnabled: boolean; constructor( protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService, protected apiService: ApiService, - protected configService: ConfigService, private logService: LogService, protected dialogService: DialogService, private environmentService: EnvironmentService, billingAccountProfileStateService: BillingAccountProfileStateService, + private toastService: ToastService, accountService: AccountService, ) { this.isPremium$ = accountService.activeAccount$.pipe( @@ -42,16 +39,17 @@ export class PremiumComponent implements OnInit { async ngOnInit() { this.cloudWebVaultUrl = await firstValueFrom(this.environmentService.cloudWebVaultUrl$); - this.extensionRefreshFlagEnabled = await this.configService.getFeatureFlag( - FeatureFlag.ExtensionRefresh, - ); } async refresh() { try { this.refreshPromise = this.apiService.refreshIdentityToken(); await this.refreshPromise; - this.platformUtilsService.showToast("success", null, this.i18nService.t("refreshComplete")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("refreshComplete"), + }); } catch (e) { this.logService.error(e); } @@ -61,15 +59,13 @@ export class PremiumComponent implements OnInit { const dialogOpts: SimpleDialogOptions = { title: { key: "continueToBitwardenDotCom" }, content: { - key: this.extensionRefreshFlagEnabled ? "premiumPurchaseAlertV2" : "premiumPurchaseAlert", + key: "premiumPurchaseAlertV2", }, type: "info", }; - if (this.extensionRefreshFlagEnabled) { - dialogOpts.acceptButtonText = { key: "continue" }; - dialogOpts.cancelButtonText = { key: "close" }; - } + dialogOpts.acceptButtonText = { key: "continue" }; + dialogOpts.cancelButtonText = { key: "close" }; const confirmed = await this.dialogService.openSimpleDialog(dialogOpts); diff --git a/libs/angular/src/directives/not-premium.directive.ts b/libs/angular/src/billing/directives/not-premium.directive.ts similarity index 100% rename from libs/angular/src/directives/not-premium.directive.ts rename to libs/angular/src/billing/directives/not-premium.directive.ts diff --git a/libs/angular/src/directives/premium.directive.ts b/libs/angular/src/billing/directives/premium.directive.ts similarity index 100% rename from libs/angular/src/directives/premium.directive.ts rename to libs/angular/src/billing/directives/premium.directive.ts diff --git a/libs/angular/src/components/share.component.ts b/libs/angular/src/components/share.component.ts index 37dec53b9c7..e785441b8e4 100644 --- a/libs/angular/src/components/share.component.ts +++ b/libs/angular/src/components/share.component.ts @@ -8,6 +8,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -54,7 +55,11 @@ export class ShareComponent implements OnInit, OnDestroy { const allCollections = await this.collectionService.getAllDecrypted(); this.writeableCollections = allCollections.map((c) => c).filter((c) => !c.readOnly); - this.organizations$ = this.organizationService.memberOrganizations$.pipe( + const userId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((account) => account?.id)), + ); + + this.organizations$ = this.organizationService.memberOrganizations$(userId).pipe( map((orgs) => { return orgs .filter((o) => o.enabled && o.status === OrganizationUserStatusType.Confirmed) @@ -69,10 +74,8 @@ export class ShareComponent implements OnInit, OnDestroy { } }); - const cipherDomain = await this.cipherService.get(this.cipherId); - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + const cipherDomain = await this.cipherService.get(this.cipherId, activeUserId); this.cipher = await cipherDomain.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId), ); @@ -100,10 +103,8 @@ export class ShareComponent implements OnInit, OnDestroy { return; } - const cipherDomain = await this.cipherService.get(this.cipherId); - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + const cipherDomain = await this.cipherService.get(this.cipherId, activeUserId); const cipherView = await cipherDomain.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId), ); diff --git a/libs/angular/src/jslib.module.ts b/libs/angular/src/jslib.module.ts index 4f5a8f6673c..6ef2cf1d4da 100644 --- a/libs/angular/src/jslib.module.ts +++ b/libs/angular/src/jslib.module.ts @@ -25,22 +25,22 @@ import { TableModule, ToastModule, TypographyModule, + CopyClickDirective, + A11yTitleDirective, } from "@bitwarden/components"; import { TwoFactorIconComponent } from "./auth/components/two-factor-icon.component"; +import { NotPremiumDirective } from "./billing/directives/not-premium.directive"; import { DeprecatedCalloutComponent } from "./components/callout.component"; import { A11yInvalidDirective } from "./directives/a11y-invalid.directive"; -import { A11yTitleDirective } from "./directives/a11y-title.directive"; import { ApiActionDirective } from "./directives/api-action.directive"; import { BoxRowDirective } from "./directives/box-row.directive"; -import { CopyClickDirective } from "./directives/copy-click.directive"; import { CopyTextDirective } from "./directives/copy-text.directive"; import { FallbackSrcDirective } from "./directives/fallback-src.directive"; import { IfFeatureDirective } from "./directives/if-feature.directive"; import { InputStripSpacesDirective } from "./directives/input-strip-spaces.directive"; import { InputVerbatimDirective } from "./directives/input-verbatim.directive"; import { LaunchClickDirective } from "./directives/launch-click.directive"; -import { NotPremiumDirective } from "./directives/not-premium.directive"; import { StopClickDirective } from "./directives/stop-click.directive"; import { StopPropDirective } from "./directives/stop-prop.directive"; import { TextDragDirective } from "./directives/text-drag.directive"; @@ -83,10 +83,11 @@ import { IconComponent } from "./vault/components/icon.component"; LinkModule, IconModule, TextDragDirective, + CopyClickDirective, + A11yTitleDirective, ], declarations: [ A11yInvalidDirective, - A11yTitleDirective, ApiActionDirective, AutofocusDirective, BoxRowDirective, @@ -105,7 +106,6 @@ import { IconComponent } from "./vault/components/icon.component"; StopClickDirective, StopPropDirective, TrueFalseValueDirective, - CopyClickDirective, LaunchClickDirective, UserNamePipe, PasswordStrengthComponent, diff --git a/libs/angular/src/platform/pipes/i18n.pipe.ts b/libs/angular/src/platform/pipes/i18n.pipe.ts index 1f92bbb19a4..a6fdbc78255 100644 --- a/libs/angular/src/platform/pipes/i18n.pipe.ts +++ b/libs/angular/src/platform/pipes/i18n.pipe.ts @@ -2,6 +2,9 @@ import { Pipe, PipeTransform } from "@angular/core"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +/** + * @deprecated: Please use the I18nPipe from @bitwarden/ui-common + */ @Pipe({ name: "i18n", }) diff --git a/libs/angular/src/platform/services/theming/angular-theming.service.ts b/libs/angular/src/platform/services/theming/angular-theming.service.ts index 2073abdcd10..8f1d863844f 100644 --- a/libs/angular/src/platform/services/theming/angular-theming.service.ts +++ b/libs/angular/src/platform/services/theming/angular-theming.service.ts @@ -59,8 +59,6 @@ export class AngularThemingService implements AbstractThemingService { document.documentElement.classList.remove( "theme_" + ThemeTypes.Light, "theme_" + ThemeTypes.Dark, - "theme_" + ThemeTypes.Nord, - "theme_" + ThemeTypes.SolarizedDark, ); document.documentElement.classList.add("theme_" + theme); }); diff --git a/libs/angular/src/platform/utils/safe-provider.ts b/libs/angular/src/platform/utils/safe-provider.ts index e7547f9b828..82b5affcc22 100644 --- a/libs/angular/src/platform/utils/safe-provider.ts +++ b/libs/angular/src/platform/utils/safe-provider.ts @@ -1,138 +1,4 @@ -import { Provider } from "@angular/core"; -import { Constructor, Opaque } from "type-fest"; - -import { SafeInjectionToken } from "../../services/injection-tokens"; - /** - * The return type of the {@link safeProvider} helper function. - * Used to distinguish a type safe provider definition from a non-type safe provider definition. + * @deprecated: Please use the SafeProvider & safeProvider from @bitwarden/ui-common */ -export type SafeProvider = Opaque; - -// TODO: type-fest also provides a type like this when we upgrade >= 3.7.0 -type AbstractConstructor = abstract new (...args: any) => T; - -type MapParametersToDeps = { - [K in keyof T]: AbstractConstructor | SafeInjectionToken; -}; - -type SafeInjectionTokenType = T extends SafeInjectionToken ? J : never; - -/** - * Gets the instance type from a constructor, abstract constructor, or SafeInjectionToken - */ -type ProviderInstanceType = - T extends SafeInjectionToken - ? InstanceType> - : T extends Constructor | AbstractConstructor - ? InstanceType - : never; - -/** - * Represents a dependency provided with the useClass option. - */ -type SafeClassProvider< - A extends AbstractConstructor | SafeInjectionToken, - I extends Constructor>, - D extends MapParametersToDeps>, -> = { - provide: A; - useClass: I; - deps: D; -}; - -/** - * Represents a dependency provided with the useValue option. - */ -type SafeValueProvider, V extends SafeInjectionTokenType> = { - provide: A; - useValue: V; -}; - -/** - * Represents a dependency provided with the useFactory option. - */ -type SafeFactoryProvider< - A extends AbstractConstructor | SafeInjectionToken, - I extends (...args: any) => ProviderInstanceType, - D extends MapParametersToDeps>, -> = { - provide: A; - useFactory: I; - deps: D; - multi?: boolean; -}; - -/** - * Represents a dependency provided with the useExisting option. - */ -type SafeExistingProvider< - A extends Constructor | AbstractConstructor | SafeInjectionToken, - I extends Constructor> | AbstractConstructor>, -> = { - provide: A; - useExisting: I; -}; - -/** - * Represents a dependency where there is no abstract token, the token is the implementation - */ -type SafeConcreteProvider< - I extends Constructor, - D extends MapParametersToDeps>, -> = { - provide: I; - deps: D; -}; - -/** - * If useAngularDecorators: true is specified, do not require a deps array. - * This is a manual override for where @Injectable decorators are used - */ -type UseAngularDecorators = Omit & { - useAngularDecorators: true; -}; - -/** - * Represents a type with a deps array that may optionally be overridden with useAngularDecorators - */ -type AllowAngularDecorators = T | UseAngularDecorators; - -/** - * A factory function that creates a provider for the ngModule providers array. - * This (almost) guarantees type safety for your provider definition. It does nothing at runtime. - * Warning: the useAngularDecorators option provides an override where your class uses the Injectable decorator, - * however this cannot be enforced by the type system and will not cause an error if the decorator is not used. - * @example safeProvider({ provide: MyService, useClass: DefaultMyService, deps: [AnotherService] }) - * @param provider Your provider object in the usual shape (e.g. using useClass, useValue, useFactory, etc.) - * @returns The exact same object without modification (pass-through). - */ -export const safeProvider = < - // types for useClass - AClass extends AbstractConstructor | SafeInjectionToken, - IClass extends Constructor>, - DClass extends MapParametersToDeps>, - // types for useValue - AValue extends SafeInjectionToken, - VValue extends SafeInjectionTokenType, - // types for useFactory - AFactory extends AbstractConstructor | SafeInjectionToken, - IFactory extends (...args: any) => ProviderInstanceType, - DFactory extends MapParametersToDeps>, - // types for useExisting - AExisting extends Constructor | AbstractConstructor | SafeInjectionToken, - IExisting extends - | Constructor> - | AbstractConstructor>, - // types for no token - IConcrete extends Constructor, - DConcrete extends MapParametersToDeps>, ->( - provider: - | AllowAngularDecorators> - | SafeValueProvider - | AllowAngularDecorators> - | SafeExistingProvider - | AllowAngularDecorators> - | Constructor, -): SafeProvider => provider as SafeProvider; +export { SafeProvider, safeProvider } from "@bitwarden/ui-common"; diff --git a/libs/angular/src/services/injection-tokens.ts b/libs/angular/src/services/injection-tokens.ts index 3842c3250e1..a63d862b0d8 100644 --- a/libs/angular/src/services/injection-tokens.ts +++ b/libs/angular/src/services/injection-tokens.ts @@ -1,10 +1,10 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { InjectionToken } from "@angular/core"; import { Observable, Subject } from "rxjs"; import { LogoutReason } from "@bitwarden/auth/common"; import { ClientType } from "@bitwarden/common/enums"; +import { VaultTimeout } from "@bitwarden/common/key-management/vault-timeout"; import { RegionConfig } from "@bitwarden/common/platform/abstractions/environment.service"; import { AbstractStorageService, @@ -13,18 +13,9 @@ import { import { Theme } from "@bitwarden/common/platform/enums"; import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; import { Message } from "@bitwarden/common/platform/messaging"; -import { VaultTimeout } from "@bitwarden/common/types/vault-timeout.type"; - -declare const tag: unique symbol; -/** - * A (more) typesafe version of InjectionToken which will more strictly enforce the generic type parameter. - * @remarks The default angular implementation does not use the generic type to define the structure of the object, - * so the structural type system will not complain about a mismatch in the type parameter. - * This is solved by assigning T to an arbitrary private property. - */ -export class SafeInjectionToken extends InjectionToken { - private readonly [tag]: T; -} +import { SafeInjectionToken } from "@bitwarden/ui-common"; +// Re-export the SafeInjectionToken from ui-common +export { SafeInjectionToken } from "@bitwarden/ui-common"; export const WINDOW = new SafeInjectionToken("WINDOW"); export const OBSERVABLE_MEMORY_STORAGE = new SafeInjectionToken< diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index f5940b8e144..9cb39a35856 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -20,6 +20,13 @@ import { DefaultLoginComponentService, LoginDecryptionOptionsService, DefaultLoginDecryptionOptionsService, + TwoFactorAuthComponentService, + DefaultTwoFactorAuthComponentService, + DefaultTwoFactorAuthEmailComponentService, + TwoFactorAuthEmailComponentService, + DefaultTwoFactorAuthWebAuthnComponentService, + TwoFactorAuthWebAuthnComponentService, + DefaultLoginApprovalComponentService, } from "@bitwarden/auth/angular"; import { AuthRequestServiceAbstraction, @@ -34,20 +41,17 @@ import { UserDecryptionOptionsService, UserDecryptionOptionsServiceAbstraction, LogoutReason, - RegisterRouteService, AuthRequestApiService, DefaultAuthRequestApiService, DefaultLoginSuccessHandlerService, LoginSuccessHandlerService, + LoginApprovalComponentServiceAbstraction, } from "@bitwarden/auth/common"; import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service"; import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service"; import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service"; -import { NotificationsService as NotificationsServiceAbstraction } from "@bitwarden/common/abstractions/notifications.service"; import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abstractions/search.service"; -import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; -import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { InternalOrganizationServiceAbstraction, @@ -66,8 +70,8 @@ import { } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction"; import { ProviderService as ProviderServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider.service"; +import { DefaultOrganizationService } from "@bitwarden/common/admin-console/services/organization/default-organization.service"; import { OrganizationApiService } from "@bitwarden/common/admin-console/services/organization/organization-api.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/services/organization/organization.service"; import { OrgDomainApiService } from "@bitwarden/common/admin-console/services/organization-domain/org-domain-api.service"; import { OrgDomainService } from "@bitwarden/common/admin-console/services/organization-domain/org-domain.service"; import { DefaultOrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/services/organization-management-preferences/default-organization-management-preferences.service"; @@ -83,14 +87,9 @@ import { import { AnonymousHubService as AnonymousHubServiceAbstraction } from "@bitwarden/common/auth/abstractions/anonymous-hub.service"; import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service"; import { AvatarService as AvatarServiceAbstraction } from "@bitwarden/common/auth/abstractions/avatar.service"; -import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; -import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarden/common/auth/abstractions/key-connector.service"; -import { - InternalMasterPasswordServiceAbstraction, - MasterPasswordServiceAbstraction, -} from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; +import { MasterPasswordApiService as MasterPasswordApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; import { PasswordResetEnrollmentServiceAbstraction } from "@bitwarden/common/auth/abstractions/password-reset-enrollment.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/abstractions/token.service"; @@ -105,11 +104,9 @@ import { AccountServiceImplementation } from "@bitwarden/common/auth/services/ac import { AnonymousHubService } from "@bitwarden/common/auth/services/anonymous-hub.service"; import { AuthService } from "@bitwarden/common/auth/services/auth.service"; import { AvatarService } from "@bitwarden/common/auth/services/avatar.service"; -import { DeviceTrustService } from "@bitwarden/common/auth/services/device-trust.service.implementation"; import { DevicesServiceImplementation } from "@bitwarden/common/auth/services/devices/devices.service.implementation"; import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation"; -import { KeyConnectorService } from "@bitwarden/common/auth/services/key-connector.service"; -import { MasterPasswordService } from "@bitwarden/common/auth/services/master-password/master-password.service"; +import { MasterPasswordApiService } from "@bitwarden/common/auth/services/master-password/master-password-api.service.implementation"; import { PasswordResetEnrollmentServiceImplementation } from "@bitwarden/common/auth/services/password-reset-enrollment.service.implementation"; import { SsoLoginService } from "@bitwarden/common/auth/services/sso-login.service"; import { TokenService } from "@bitwarden/common/auth/services/token.service"; @@ -145,13 +142,30 @@ import { BillingApiService } from "@bitwarden/common/billing/services/billing-ap import { OrganizationBillingApiService } from "@bitwarden/common/billing/services/organization/organization-billing-api.service"; import { OrganizationBillingService } from "@bitwarden/common/billing/services/organization-billing.service"; import { TaxService } from "@bitwarden/common/billing/services/tax.service"; +import { BulkEncryptService } from "@bitwarden/common/key-management/crypto/abstractions/bulk-encrypt.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { BulkEncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/bulk-encrypt.service.implementation"; +import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/multithread-encrypt.service.implementation"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; +import { DeviceTrustService } from "@bitwarden/common/key-management/device-trust/services/device-trust.service.implementation"; +import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; +import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/services/key-connector.service"; +import { + InternalMasterPasswordServiceAbstraction, + MasterPasswordServiceAbstraction, +} from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; +import { MasterPasswordService } from "@bitwarden/common/key-management/master-password/services/master-password.service"; +import { + DefaultVaultTimeoutService, + DefaultVaultTimeoutSettingsService, + VaultTimeoutService, + VaultTimeoutSettingsService, +} from "@bitwarden/common/key-management/vault-timeout"; import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { BulkEncryptService } from "@bitwarden/common/platform/abstractions/bulk-encrypt.service"; import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { EnvironmentService, RegionConfig, @@ -174,6 +188,16 @@ import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/inter import { devFlagEnabled } from "@bitwarden/common/platform/misc/flags"; import { Account } from "@bitwarden/common/platform/models/domain/account"; import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state"; +import { NotificationsService } from "@bitwarden/common/platform/notifications"; +// eslint-disable-next-line no-restricted-imports -- Needed for service creation +import { + DefaultNotificationsService, + NoopNotificationsService, + SignalRConnectionService, + UnsupportedWebPushConnectionService, + WebPushConnectionService, + WebPushNotificationsApiService, +} from "@bitwarden/common/platform/notifications/internal"; import { TaskSchedulerService, DefaultTaskSchedulerService, @@ -182,8 +206,6 @@ import { AppIdService } from "@bitwarden/common/platform/services/app-id.service import { ConfigApiService } from "@bitwarden/common/platform/services/config/config-api.service"; import { DefaultConfigService } from "@bitwarden/common/platform/services/config/default-config.service"; import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; -import { BulkEncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/bulk-encrypt.service.implementation"; -import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/multithread-encrypt.service.implementation"; import { DefaultBroadcasterService } from "@bitwarden/common/platform/services/default-broadcaster.service"; import { DefaultEnvironmentService } from "@bitwarden/common/platform/services/default-environment.service"; import { DefaultServerSettingsService } from "@bitwarden/common/platform/services/default-server-settings.service"; @@ -191,7 +213,6 @@ import { FileUploadService } from "@bitwarden/common/platform/services/file-uplo import { KeyGenerationService } from "@bitwarden/common/platform/services/key-generation.service"; import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service"; import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner"; -import { NoopNotificationsService } from "@bitwarden/common/platform/services/noop-notifications.service"; import { DefaultSdkService } from "@bitwarden/common/platform/services/sdk/default-sdk.service"; import { StateService } from "@bitwarden/common/platform/services/state.service"; import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider"; @@ -225,10 +246,7 @@ import { ApiService } from "@bitwarden/common/services/api.service"; import { AuditService } from "@bitwarden/common/services/audit.service"; import { EventCollectionService } from "@bitwarden/common/services/event/event-collection.service"; import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service"; -import { NotificationsService } from "@bitwarden/common/services/notifications.service"; import { SearchService } from "@bitwarden/common/services/search.service"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vault-timeout/vault-timeout-settings.service"; -import { VaultTimeoutService } from "@bitwarden/common/services/vault-timeout/vault-timeout.service"; import { PasswordStrengthService, PasswordStrengthServiceAbstraction, @@ -272,12 +290,6 @@ import { PasswordGenerationServiceAbstraction, UsernameGenerationServiceAbstraction, } from "@bitwarden/generator-legacy"; -import { - ImportApiService, - ImportApiServiceAbstraction, - ImportService, - ImportServiceAbstraction, -} from "@bitwarden/importer/core"; import { KeyService, DefaultKeyService, @@ -291,7 +303,15 @@ import { UserAsymmetricKeysRegenerationApiService, DefaultUserAsymmetricKeysRegenerationApiService, } from "@bitwarden/key-management"; -import { PasswordRepromptService } from "@bitwarden/vault"; +import { SafeInjectionToken } from "@bitwarden/ui-common"; +import { + DefaultTaskService, + DefaultEndUserNotificationService, + EndUserNotificationService, + NewDeviceVerificationNoticeService, + PasswordRepromptService, + TaskService, +} from "@bitwarden/vault"; import { VaultExportService, VaultExportServiceAbstraction, @@ -301,7 +321,8 @@ import { IndividualVaultExportServiceAbstraction, } from "@bitwarden/vault-export-core"; -import { NewDeviceVerificationNoticeService } from "../../../vault/src/services/new-device-verification-notice.service"; +import { DeviceTrustToastService as DeviceTrustToastServiceAbstraction } from "../auth/services/device-trust-toast.service.abstraction"; +import { DeviceTrustToastService } from "../auth/services/device-trust-toast.service.implementation"; import { FormValidationErrorsService as FormValidationErrorsServiceAbstraction } from "../platform/abstractions/form-validation-errors.service"; import { ViewCacheService } from "../platform/abstractions/view-cache.service"; import { FormValidationErrorsService } from "../platform/services/form-validation-errors.service"; @@ -319,7 +340,6 @@ import { MEMORY_STORAGE, OBSERVABLE_DISK_STORAGE, OBSERVABLE_MEMORY_STORAGE, - SafeInjectionToken, SECURE_STORAGE, STATE_FACTORY, SUPPORTS_SECURE_STORAGE, @@ -394,7 +414,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: ThemeStateService, useClass: DefaultThemeStateService, - deps: [GlobalStateProvider, ConfigService], + deps: [GlobalStateProvider], }), safeProvider({ provide: AbstractThemingService, @@ -449,7 +469,7 @@ const safeProviders: SafeProvider[] = [ InternalUserDecryptionOptionsServiceAbstraction, GlobalStateProvider, BillingAccountProfileStateService, - VaultTimeoutSettingsServiceAbstraction, + VaultTimeoutSettingsService, KdfConfigService, TaskSchedulerService, ], @@ -551,7 +571,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: InternalAccountService, useClass: AccountServiceImplementation, - deps: [MessagingServiceAbstraction, LogService, GlobalStateProvider], + deps: [MessagingServiceAbstraction, LogService, GlobalStateProvider, SingleUserStateProvider], }), safeProvider({ provide: AccountServiceAbstraction, @@ -589,7 +609,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: TotpServiceAbstraction, useClass: TotpService, - deps: [CryptoFunctionServiceAbstraction, LogService], + deps: [SdkService], }), safeProvider({ provide: TokenServiceAbstraction, @@ -683,7 +703,7 @@ const safeProviders: SafeProvider[] = [ REFRESH_ACCESS_TOKEN_ERROR_CALLBACK, LogService, LOGOUT_CALLBACK, - VaultTimeoutSettingsServiceAbstraction, + VaultTimeoutSettingsService, ], }), safeProvider({ @@ -748,8 +768,8 @@ const safeProviders: SafeProvider[] = [ deps: [MessageListener], }), safeProvider({ - provide: VaultTimeoutSettingsServiceAbstraction, - useClass: VaultTimeoutSettingsService, + provide: VaultTimeoutSettingsService, + useClass: DefaultVaultTimeoutSettingsService, deps: [ AccountServiceAbstraction, PinServiceAbstraction, @@ -764,8 +784,8 @@ const safeProviders: SafeProvider[] = [ ], }), safeProvider({ - provide: VaultTimeoutService, - useClass: VaultTimeoutService, + provide: DefaultVaultTimeoutService, + useClass: DefaultVaultTimeoutService, deps: [ AccountServiceAbstraction, InternalMasterPasswordServiceAbstraction, @@ -777,7 +797,7 @@ const safeProviders: SafeProvider[] = [ SearchServiceAbstraction, StateServiceAbstraction, AuthServiceAbstraction, - VaultTimeoutSettingsServiceAbstraction, + VaultTimeoutSettingsService, StateEventRunnerService, TaskSchedulerService, LogService, @@ -787,13 +807,13 @@ const safeProviders: SafeProvider[] = [ ], }), safeProvider({ - provide: VaultTimeoutServiceAbstraction, - useExisting: VaultTimeoutService, + provide: VaultTimeoutService, + useExisting: DefaultVaultTimeoutService, }), safeProvider({ provide: SsoLoginServiceAbstraction, useClass: SsoLoginService, - deps: [StateProvider], + deps: [StateProvider, LogService], }), safeProvider({ provide: STATE_FACTORY, @@ -814,26 +834,6 @@ const safeProviders: SafeProvider[] = [ MigrationRunner, ], }), - safeProvider({ - provide: ImportApiServiceAbstraction, - useClass: ImportApiService, - deps: [ApiServiceAbstraction], - }), - safeProvider({ - provide: ImportServiceAbstraction, - useClass: ImportService, - deps: [ - CipherServiceAbstraction, - FolderServiceAbstraction, - ImportApiServiceAbstraction, - I18nServiceAbstraction, - CollectionService, - KeyService, - EncryptService, - PinServiceAbstraction, - AccountServiceAbstraction, - ], - }), safeProvider({ provide: IndividualVaultExportServiceAbstraction, useClass: IndividualVaultExportService, @@ -874,19 +874,36 @@ const safeProviders: SafeProvider[] = [ deps: [LogService, I18nServiceAbstraction, StateProvider], }), safeProvider({ - provide: NotificationsServiceAbstraction, - useClass: devFlagEnabled("noopNotifications") ? NoopNotificationsService : NotificationsService, + provide: WebPushNotificationsApiService, + useClass: WebPushNotificationsApiService, + deps: [ApiServiceAbstraction, AppIdServiceAbstraction], + }), + safeProvider({ + provide: SignalRConnectionService, + useClass: SignalRConnectionService, + deps: [ApiServiceAbstraction, LogService], + }), + safeProvider({ + provide: WebPushConnectionService, + useClass: UnsupportedWebPushConnectionService, + deps: [], + }), + safeProvider({ + provide: NotificationsService, + useClass: devFlagEnabled("noopNotifications") + ? NoopNotificationsService + : DefaultNotificationsService, deps: [ LogService, SyncService, AppIdServiceAbstraction, - ApiServiceAbstraction, EnvironmentService, LOGOUT_CALLBACK, - StateServiceAbstraction, - AuthServiceAbstraction, MessagingServiceAbstraction, - TaskSchedulerService, + AccountServiceAbstraction, + SignalRConnectionService, + AuthServiceAbstraction, + WebPushConnectionService, ], }), safeProvider({ @@ -989,13 +1006,14 @@ const safeProviders: SafeProvider[] = [ }), safeProvider({ provide: InternalOrganizationServiceAbstraction, - useClass: OrganizationService, + useClass: DefaultOrganizationService, deps: [StateProvider], }), safeProvider({ provide: OrganizationServiceAbstraction, useExisting: InternalOrganizationServiceAbstraction, }), + safeProvider({ provide: OrganizationUserApiService, useClass: DefaultOrganizationUserApiService, @@ -1230,7 +1248,6 @@ const safeProviders: SafeProvider[] = [ deps: [ ApiServiceAbstraction, BillingApiServiceAbstraction, - ConfigService, KeyService, EncryptService, I18nServiceAbstraction, @@ -1271,7 +1288,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: BillingApiServiceAbstraction, useClass: BillingApiService, - deps: [ApiServiceAbstraction, LogService, ToastService], + deps: [ApiServiceAbstraction], }), safeProvider({ provide: TaxServiceAbstraction, @@ -1335,6 +1352,7 @@ const safeProviders: SafeProvider[] = [ useClass: DefaultSetPasswordJitService, deps: [ ApiServiceAbstraction, + MasterPasswordApiServiceAbstraction, KeyService, EncryptService, I18nServiceAbstraction, @@ -1350,11 +1368,6 @@ const safeProviders: SafeProvider[] = [ useClass: DefaultServerSettingsService, deps: [ConfigService], }), - safeProvider({ - provide: RegisterRouteService, - useClass: RegisterRouteService, - deps: [ConfigService], - }), safeProvider({ provide: AnonLayoutWrapperDataService, useClass: DefaultAnonLayoutWrapperDataService, @@ -1365,6 +1378,21 @@ const safeProviders: SafeProvider[] = [ useClass: DefaultRegistrationFinishService, deps: [KeyService, AccountApiServiceAbstraction], }), + safeProvider({ + provide: TwoFactorAuthComponentService, + useClass: DefaultTwoFactorAuthComponentService, + deps: [], + }), + safeProvider({ + provide: TwoFactorAuthWebAuthnComponentService, + useClass: DefaultTwoFactorAuthWebAuthnComponentService, + deps: [], + }), + safeProvider({ + provide: TwoFactorAuthEmailComponentService, + useClass: DefaultTwoFactorAuthEmailComponentService, + deps: [], + }), safeProvider({ provide: ViewCacheService, useExisting: NoopViewCacheService, @@ -1396,13 +1424,23 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: CipherAuthorizationService, useClass: DefaultCipherAuthorizationService, - deps: [CollectionService, OrganizationServiceAbstraction], + deps: [ + CollectionService, + OrganizationServiceAbstraction, + AccountServiceAbstraction, + ConfigService, + ], }), safeProvider({ provide: AuthRequestApiService, useClass: DefaultAuthRequestApiService, deps: [ApiServiceAbstraction, LogService], }), + safeProvider({ + provide: LoginApprovalComponentServiceAbstraction, + useClass: DefaultLoginApprovalComponentService, + deps: [], + }), safeProvider({ provide: LoginDecryptionOptionsService, useClass: DefaultLoginDecryptionOptionsService, @@ -1432,6 +1470,31 @@ const safeProviders: SafeProvider[] = [ useClass: DefaultLoginSuccessHandlerService, deps: [SyncService, UserAsymmetricKeysRegenerationService], }), + safeProvider({ + provide: TaskService, + useClass: DefaultTaskService, + deps: [StateProvider, ApiServiceAbstraction, OrganizationServiceAbstraction, ConfigService], + }), + safeProvider({ + provide: EndUserNotificationService, + useClass: DefaultEndUserNotificationService, + deps: [StateProvider, ApiServiceAbstraction], + }), + safeProvider({ + provide: DeviceTrustToastServiceAbstraction, + useClass: DeviceTrustToastService, + deps: [ + AuthRequestServiceAbstraction, + DeviceTrustServiceAbstraction, + I18nServiceAbstraction, + ToastService, + ], + }), + safeProvider({ + provide: MasterPasswordApiServiceAbstraction, + useClass: MasterPasswordApiService, + deps: [ApiServiceAbstraction, LogService], + }), ]; @NgModule({ diff --git a/libs/angular/src/tools/generator/components/generator.component.ts b/libs/angular/src/tools/generator/components/generator.component.ts deleted file mode 100644 index 1f3c635e499..00000000000 --- a/libs/angular/src/tools/generator/components/generator.component.ts +++ /dev/null @@ -1,389 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Directive, EventEmitter, Input, NgZone, OnDestroy, OnInit, Output } from "@angular/core"; -import { ActivatedRoute } from "@angular/router"; -import { BehaviorSubject, combineLatest, firstValueFrom, Subject } from "rxjs"; -import { debounceTime, first, map, skipWhile, takeUntil } from "rxjs/operators"; - -import { PasswordGeneratorPolicyOptions } from "@bitwarden/common/admin-console/models/domain/password-generator-policy-options"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { ToastService } from "@bitwarden/components"; -import { - GeneratorType, - DefaultPasswordBoundaries as DefaultBoundaries, -} from "@bitwarden/generator-core"; -import { - PasswordGenerationServiceAbstraction, - UsernameGenerationServiceAbstraction, - UsernameGeneratorOptions, - PasswordGeneratorOptions, -} from "@bitwarden/generator-legacy"; - -export class EmailForwarderOptions { - name: string; - value: string; - validForSelfHosted: boolean; -} - -@Directive() -export class GeneratorComponent implements OnInit, OnDestroy { - @Input() comingFromAddEdit = false; - @Input() type: GeneratorType | ""; - @Output() onSelected = new EventEmitter(); - - usernameGeneratingPromise: Promise; - typeOptions: any[]; - usernameTypeOptions: any[]; - subaddressOptions: any[]; - catchallOptions: any[]; - forwardOptions: EmailForwarderOptions[]; - usernameOptions: UsernameGeneratorOptions = { website: null }; - passwordOptions: PasswordGeneratorOptions = {}; - username = "-"; - password = "-"; - showOptions = false; - avoidAmbiguous = false; - enforcedPasswordPolicyOptions: PasswordGeneratorPolicyOptions; - usernameWebsite: string = null; - - get passTypeOptions() { - return this._passTypeOptions.filter((o) => !o.disabled); - } - private _passTypeOptions: { name: string; value: GeneratorType; disabled: boolean }[]; - - private destroy$ = new Subject(); - private isInitialized$ = new BehaviorSubject(false); - - // update screen reader minimum password length with 500ms debounce - // so that the user isn't flooded with status updates - private _passwordOptionsMinLengthForReader = new BehaviorSubject( - DefaultBoundaries.length.min, - ); - protected passwordOptionsMinLengthForReader$ = this._passwordOptionsMinLengthForReader.pipe( - map((val) => val || DefaultBoundaries.length.min), - debounceTime(500), - ); - - private _password = new BehaviorSubject("-"); - - constructor( - protected passwordGenerationService: PasswordGenerationServiceAbstraction, - protected usernameGenerationService: UsernameGenerationServiceAbstraction, - protected platformUtilsService: PlatformUtilsService, - protected accountService: AccountService, - protected i18nService: I18nService, - protected logService: LogService, - protected route: ActivatedRoute, - protected ngZone: NgZone, - private win: Window, - protected toastService: ToastService, - ) { - this.typeOptions = [ - { name: i18nService.t("password"), value: "password" }, - { name: i18nService.t("username"), value: "username" }, - ]; - this._passTypeOptions = [ - { name: i18nService.t("password"), value: "password", disabled: false }, - { name: i18nService.t("passphrase"), value: "passphrase", disabled: false }, - ]; - this.usernameTypeOptions = [ - { - name: i18nService.t("plusAddressedEmail"), - value: "subaddress", - desc: i18nService.t("plusAddressedEmailDesc"), - }, - { - name: i18nService.t("catchallEmail"), - value: "catchall", - desc: i18nService.t("catchallEmailDesc"), - }, - { - name: i18nService.t("forwardedEmail"), - value: "forwarded", - desc: i18nService.t("forwardedEmailDesc"), - }, - { name: i18nService.t("randomWord"), value: "word" }, - ]; - this.subaddressOptions = [{ name: i18nService.t("random"), value: "random" }]; - this.catchallOptions = [{ name: i18nService.t("random"), value: "random" }]; - - this.forwardOptions = [ - { name: "", value: "", validForSelfHosted: false }, - { name: "addy.io", value: "anonaddy", validForSelfHosted: true }, - { name: "DuckDuckGo", value: "duckduckgo", validForSelfHosted: false }, - { name: "Fastmail", value: "fastmail", validForSelfHosted: true }, - { name: "Firefox Relay", value: "firefoxrelay", validForSelfHosted: false }, - { name: "SimpleLogin", value: "simplelogin", validForSelfHosted: true }, - { name: "Forward Email", value: "forwardemail", validForSelfHosted: true }, - ].sort((a, b) => a.name.localeCompare(b.name)); - - this._password.pipe(debounceTime(250)).subscribe((password) => { - ngZone.run(() => { - this.password = password; - }); - this.passwordGenerationService.addHistory(this.password).catch((e) => { - this.logService.error(e); - }); - }); - } - - cascadeOptions(navigationType: GeneratorType = undefined, accountEmail: string) { - this.avoidAmbiguous = !this.passwordOptions.ambiguous; - - if (!this.type) { - if (navigationType) { - this.type = navigationType; - } else { - this.type = this.passwordOptions.type === "username" ? "username" : "password"; - } - } - - this.passwordOptions.type = - this.passwordOptions.type === "passphrase" ? "passphrase" : "password"; - - const overrideType = this.enforcedPasswordPolicyOptions.overridePasswordType ?? ""; - const isDisabled = overrideType.length - ? (value: string, policyValue: string) => value !== policyValue - : (_value: string, _policyValue: string) => false; - for (const option of this._passTypeOptions) { - option.disabled = isDisabled(option.value, overrideType); - } - - if (this.usernameOptions.type == null) { - this.usernameOptions.type = "word"; - } - if ( - this.usernameOptions.subaddressEmail == null || - this.usernameOptions.subaddressEmail === "" - ) { - this.usernameOptions.subaddressEmail = accountEmail; - } - if (this.usernameWebsite == null) { - this.usernameOptions.subaddressType = this.usernameOptions.catchallType = "random"; - } else { - this.usernameOptions.website = this.usernameWebsite; - } - } - - async ngOnInit() { - combineLatest([ - this.route.queryParams.pipe(first()), - this.accountService.activeAccount$.pipe(first()), - this.passwordGenerationService.getOptions$(), - this.usernameGenerationService.getOptions$(), - ]) - .pipe( - map(([qParams, account, [passwordOptions, passwordPolicy], usernameOptions]) => ({ - navigationType: qParams.type as GeneratorType, - accountEmail: account.email, - passwordOptions, - passwordPolicy, - usernameOptions, - })), - takeUntil(this.destroy$), - ) - .subscribe((options) => { - this.passwordOptions = options.passwordOptions; - this.enforcedPasswordPolicyOptions = options.passwordPolicy; - this.usernameOptions = options.usernameOptions; - - this.cascadeOptions(options.navigationType, options.accountEmail); - this._passwordOptionsMinLengthForReader.next(this.passwordOptions.minLength); - - if (this.regenerateWithoutButtonPress()) { - this.regenerate().catch((e) => { - this.logService.error(e); - }); - } - - this.isInitialized$.next(true); - }); - - // once initialization is complete, `ngOnInit` should return. - // - // FIXME(#6944): if a sync is in progress, wait to complete until after - // the sync completes. - await firstValueFrom( - this.isInitialized$.pipe( - skipWhile((initialized) => !initialized), - takeUntil(this.destroy$), - ), - ); - - if (this.usernameWebsite !== null) { - const websiteOption = { name: this.i18nService.t("websiteName"), value: "website-name" }; - this.subaddressOptions.push(websiteOption); - this.catchallOptions.push(websiteOption); - } - } - - ngOnDestroy() { - this.destroy$.next(); - this.destroy$.complete(); - this.isInitialized$.complete(); - this._passwordOptionsMinLengthForReader.complete(); - } - - async typeChanged() { - await this.savePasswordOptions(); - } - - async regenerate() { - if (this.type === "password") { - await this.regeneratePassword(); - } else if (this.type === "username") { - await this.regenerateUsername(); - } - } - - async sliderChanged() { - // 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.savePasswordOptions(); - await this.passwordGenerationService.addHistory(this.password); - } - - async onPasswordOptionsMinNumberInput($event: Event) { - // `savePasswordOptions()` replaces the null - this.passwordOptions.number = null; - - await this.savePasswordOptions(); - - // fixes UI desync that occurs when minNumber has a fixed value - // that is reset through normalization - ($event.target as HTMLInputElement).value = `${this.passwordOptions.minNumber}`; - } - - async setPasswordOptionsNumber($event: boolean) { - this.passwordOptions.number = $event; - // `savePasswordOptions()` replaces the null - this.passwordOptions.minNumber = null; - - await this.savePasswordOptions(); - } - - async onPasswordOptionsMinSpecialInput($event: Event) { - // `savePasswordOptions()` replaces the null - this.passwordOptions.special = null; - - await this.savePasswordOptions(); - - // fixes UI desync that occurs when minSpecial has a fixed value - // that is reset through normalization - ($event.target as HTMLInputElement).value = `${this.passwordOptions.minSpecial}`; - } - - async setPasswordOptionsSpecial($event: boolean) { - this.passwordOptions.special = $event; - // `savePasswordOptions()` replaces the null - this.passwordOptions.minSpecial = null; - - await this.savePasswordOptions(); - } - - async sliderInput() { - await this.normalizePasswordOptions(); - } - - async savePasswordOptions() { - // map navigation state into generator type - const restoreType = this.passwordOptions.type; - if (this.type === "username") { - this.passwordOptions.type = this.type; - } - - // save options - await this.normalizePasswordOptions(); - await this.passwordGenerationService.saveOptions(this.passwordOptions); - - // restore the original format - this.passwordOptions.type = restoreType; - } - - async saveUsernameOptions() { - await this.usernameGenerationService.saveOptions(this.usernameOptions); - if (this.usernameOptions.type === "forwarded") { - this.username = "-"; - } - } - - async regeneratePassword() { - this._password.next( - await this.passwordGenerationService.generatePassword(this.passwordOptions), - ); - } - - regenerateUsername() { - return this.generateUsername(); - } - - async generateUsername() { - try { - this.usernameGeneratingPromise = this.usernameGenerationService.generateUsername( - this.usernameOptions, - ); - this.username = await this.usernameGeneratingPromise; - if (this.username === "" || this.username === null) { - this.username = "-"; - } - } catch (e) { - this.logService.error(e); - } - } - - copy() { - const password = this.type === "password"; - const copyOptions = this.win != null ? { window: this.win } : null; - this.platformUtilsService.copyToClipboard( - password ? this.password : this.username, - copyOptions, - ); - this.toastService.showToast({ - variant: "info", - title: null, - message: this.i18nService.t( - "valueCopied", - this.i18nService.t(password ? "password" : "username"), - ), - }); - } - - select() { - this.onSelected.emit(this.type === "password" ? this.password : this.username); - } - - toggleOptions() { - this.showOptions = !this.showOptions; - } - - regenerateWithoutButtonPress() { - return this.type !== "username" || this.usernameOptions.type !== "forwarded"; - } - - private async normalizePasswordOptions() { - // Application level normalize options dependent on class variables - this.passwordOptions.ambiguous = !this.avoidAmbiguous; - - if ( - !this.passwordOptions.uppercase && - !this.passwordOptions.lowercase && - !this.passwordOptions.number && - !this.passwordOptions.special - ) { - this.passwordOptions.lowercase = true; - if (this.win != null) { - const lowercase = this.win.document.querySelector("#lowercase") as HTMLInputElement; - if (lowercase) { - lowercase.checked = true; - } - } - } - - await this.passwordGenerationService.enforcePasswordGeneratorPoliciesOnOptions( - this.passwordOptions, - ); - } -} diff --git a/libs/angular/src/tools/generator/components/password-generator-history.component.ts b/libs/angular/src/tools/generator/components/password-generator-history.component.ts deleted file mode 100644 index 2933163fce2..00000000000 --- a/libs/angular/src/tools/generator/components/password-generator-history.component.ts +++ /dev/null @@ -1,40 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Directive, OnInit } from "@angular/core"; - -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { ToastService } from "@bitwarden/components"; -import { GeneratedPasswordHistory } from "@bitwarden/generator-history"; -import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; - -@Directive() -export class PasswordGeneratorHistoryComponent implements OnInit { - history: GeneratedPasswordHistory[] = []; - - constructor( - protected passwordGenerationService: PasswordGenerationServiceAbstraction, - protected platformUtilsService: PlatformUtilsService, - protected i18nService: I18nService, - private win: Window, - protected toastService: ToastService, - ) {} - - async ngOnInit() { - this.history = await this.passwordGenerationService.getHistory(); - } - - clear = async () => { - this.history = await this.passwordGenerationService.clear(); - }; - - copy(password: string) { - const copyOptions = this.win != null ? { window: this.win } : null; - this.platformUtilsService.copyToClipboard(password, copyOptions); - this.toastService.showToast({ - variant: "info", - title: null, - message: this.i18nService.t("valueCopied", this.i18nService.t("password")), - }); - } -} diff --git a/libs/angular/src/tools/generator/generator-swap.ts b/libs/angular/src/tools/generator/generator-swap.ts deleted file mode 100644 index 16fafc67116..00000000000 --- a/libs/angular/src/tools/generator/generator-swap.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Type, inject } from "@angular/core"; -import { Route, Routes } from "@angular/router"; - -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; - -import { componentRouteSwap } from "../../utils/component-route-swap"; - -/** - * Helper function to swap between two components based on the GeneratorToolsModernization feature flag. - * @param defaultComponent - The current non-refreshed component to render. - * @param refreshedComponent - The new refreshed component to render. - * @param options - The shared route options to apply to the default component, and to the alt component if altOptions is not provided. - * @param altOptions - The alt route options to apply to the alt component. - */ -export function generatorSwap( - defaultComponent: Type, - refreshedComponent: Type, - options: Route, - altOptions?: Route, -): Routes { - return componentRouteSwap( - defaultComponent, - refreshedComponent, - async () => { - const configService = inject(ConfigService); - return configService.getFeatureFlag(FeatureFlag.GeneratorToolsModernization); - }, - options, - altOptions, - ); -} diff --git a/libs/angular/src/tools/password-strength/password-strength-v2.component.html b/libs/angular/src/tools/password-strength/password-strength-v2.component.html index b613f0f274b..48c1ada54f2 100644 --- a/libs/angular/src/tools/password-strength/password-strength-v2.component.html +++ b/libs/angular/src/tools/password-strength/password-strength-v2.component.html @@ -5,3 +5,7 @@ [showText]="showText" [barWidth]="scoreWidth" > + +
    + {{ "passwordStrengthScore" | i18n: text }} +
    diff --git a/libs/angular/src/tools/send/add-edit.component.ts b/libs/angular/src/tools/send/add-edit.component.ts index aeee1fa104c..4f7d4b6b600 100644 --- a/libs/angular/src/tools/send/add-edit.component.ts +++ b/libs/angular/src/tools/send/add-edit.component.ts @@ -16,6 +16,7 @@ import { 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 { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -164,9 +165,10 @@ export class AddEditComponent implements OnInit, OnDestroy { } }); - this.policyService - .getAll$(PolicyType.SendOptions) + this.accountService.activeAccount$ .pipe( + getUserId, + switchMap((userId) => this.policyService.getAll$(PolicyType.SendOptions, userId)), map((policies) => policies?.some((p) => p.data.disableHideEmail)), takeUntil(this.destroy$), ) diff --git a/libs/angular/src/tools/send/send.component.ts b/libs/angular/src/tools/send/send.component.ts index 6b7f911ed12..738960fc628 100644 --- a/libs/angular/src/tools/send/send.component.ts +++ b/libs/angular/src/tools/send/send.component.ts @@ -14,6 +14,8 @@ import { import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -79,9 +81,12 @@ export class SendComponent implements OnInit, OnDestroy { protected sendApiService: SendApiService, protected dialogService: DialogService, protected toastService: ToastService, + private accountService: AccountService, ) {} async ngOnInit() { + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + this.policyService .policyAppliesToActiveUser$(PolicyType.DisableSend) .pipe(takeUntil(this.destroy$)) @@ -91,7 +96,7 @@ export class SendComponent implements OnInit, OnDestroy { this._searchText$ .pipe( - switchMap((searchText) => from(this.searchService.isSearchable(searchText))), + switchMap((searchText) => from(this.searchService.isSearchable(userId, searchText))), takeUntil(this.destroy$), ) .subscribe((isSearchable) => { diff --git a/libs/angular/src/utils/extension-refresh-redirect.spec.ts b/libs/angular/src/utils/extension-refresh-redirect.spec.ts deleted file mode 100644 index 3291a4496ff..00000000000 --- a/libs/angular/src/utils/extension-refresh-redirect.spec.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { TestBed } from "@angular/core/testing"; -import { Navigation, Router, UrlTree } from "@angular/router"; -import { mock, MockProxy } from "jest-mock-extended"; - -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; - -import { extensionRefreshRedirect } from "./extension-refresh-redirect"; - -describe("extensionRefreshRedirect", () => { - let configService: MockProxy; - let router: MockProxy; - - beforeEach(() => { - configService = mock(); - router = mock(); - - TestBed.configureTestingModule({ - providers: [ - { provide: ConfigService, useValue: configService }, - { provide: Router, useValue: router }, - ], - }); - }); - - it("returns true when ExtensionRefresh flag is disabled", async () => { - configService.getFeatureFlag.mockResolvedValue(false); - - const result = await TestBed.runInInjectionContext(() => - extensionRefreshRedirect("/redirect")(), - ); - - expect(result).toBe(true); - expect(configService.getFeatureFlag).toHaveBeenCalledWith(FeatureFlag.ExtensionRefresh); - expect(router.parseUrl).not.toHaveBeenCalled(); - }); - - it("returns UrlTree when ExtensionRefresh flag is enabled and preserves query params", async () => { - configService.getFeatureFlag.mockResolvedValue(true); - - const urlTree = new UrlTree(); - urlTree.queryParams = { test: "test" }; - - const navigation: Navigation = { - extras: {}, - id: 0, - initialUrl: new UrlTree(), - extractedUrl: urlTree, - trigger: "imperative", - previousNavigation: undefined, - }; - - router.getCurrentNavigation.mockReturnValue(navigation); - - await TestBed.runInInjectionContext(() => extensionRefreshRedirect("/redirect")()); - - expect(configService.getFeatureFlag).toHaveBeenCalledWith(FeatureFlag.ExtensionRefresh); - expect(router.createUrlTree).toHaveBeenCalledWith(["/redirect"], { - queryParams: urlTree.queryParams, - }); - }); -}); diff --git a/libs/angular/src/utils/extension-refresh-redirect.ts b/libs/angular/src/utils/extension-refresh-redirect.ts deleted file mode 100644 index 2baa3a3ec89..00000000000 --- a/libs/angular/src/utils/extension-refresh-redirect.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { inject } from "@angular/core"; -import { UrlTree, Router } from "@angular/router"; - -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; - -/** - * Helper function to redirect to a new URL based on the ExtensionRefresh feature flag. - * @param redirectUrl - The URL to redirect to if the ExtensionRefresh flag is enabled. - */ -export function extensionRefreshRedirect(redirectUrl: string): () => Promise { - return async () => { - const configService = inject(ConfigService); - const router = inject(Router); - - const shouldRedirect = await configService.getFeatureFlag(FeatureFlag.ExtensionRefresh); - if (shouldRedirect) { - const currentNavigation = router.getCurrentNavigation(); - const queryParams = currentNavigation?.extractedUrl?.queryParams || {}; - - // Preserve query params when redirecting as it is likely that the refreshed component - // will be consuming the same query params. - return router.createUrlTree([redirectUrl], { queryParams }); - } else { - return true; - } - }; -} diff --git a/libs/angular/src/utils/extension-refresh-swap.ts b/libs/angular/src/utils/extension-refresh-swap.ts deleted file mode 100644 index 6512be032d2..00000000000 --- a/libs/angular/src/utils/extension-refresh-swap.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Type, inject } from "@angular/core"; -import { Route, Routes } from "@angular/router"; - -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; - -import { componentRouteSwap } from "./component-route-swap"; - -/** - * Helper function to swap between two components based on the ExtensionRefresh feature flag. - * @param defaultComponent - The current non-refreshed component to render. - * @param refreshedComponent - The new refreshed component to render. - * @param options - The shared route options to apply to the default component, and to the alt component if altOptions is not provided. - * @param altOptions - The alt route options to apply to the alt component. - */ -export function extensionRefreshSwap( - defaultComponent: Type, - refreshedComponent: Type, - options: Route, - altOptions?: Route, -): Routes { - return componentRouteSwap( - defaultComponent, - refreshedComponent, - async () => { - const configService = inject(ConfigService); - return configService.getFeatureFlag(FeatureFlag.ExtensionRefresh); - }, - options, - altOptions, - ); -} diff --git a/libs/angular/src/utils/two-factor-component-refactor-route-swap.ts b/libs/angular/src/utils/two-factor-component-refactor-route-swap.ts deleted file mode 100644 index 8b57a3eb94f..00000000000 --- a/libs/angular/src/utils/two-factor-component-refactor-route-swap.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Type, inject } from "@angular/core"; -import { Route, Routes } from "@angular/router"; - -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; - -import { componentRouteSwap } from "./component-route-swap"; -/** - * Helper function to swap between two components based on the TwoFactorComponentRefactor feature flag. - * @param defaultComponent - The current non-refactored component to render. - * @param refreshedComponent - The new refactored component to render. - * @param defaultOptions - The options to apply to the default component and the refactored component, if alt options are not provided. - * @param altOptions - The options to apply to the refactored component. - */ -export function twofactorRefactorSwap( - defaultComponent: Type, - refreshedComponent: Type, - defaultOptions: Route, - altOptions?: Route, -): Routes { - return componentRouteSwap( - defaultComponent, - refreshedComponent, - async () => { - const configService = inject(ConfigService); - return configService.getFeatureFlag(FeatureFlag.TwoFactorComponentRefactor); - }, - defaultOptions, - altOptions, - ); -} diff --git a/libs/angular/src/vault/components/add-edit.component.ts b/libs/angular/src/vault/components/add-edit.component.ts index 8d286e0a3f9..eed90c2ba70 100644 --- a/libs/angular/src/vault/components/add-edit.component.ts +++ b/libs/angular/src/vault/components/add-edit.component.ts @@ -7,17 +7,14 @@ import { concatMap, firstValueFrom, map, Observable, Subject, takeUntil } from " import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; -import { - isMember, - OrganizationService, -} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { OrganizationUserStatusType, PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { normalizeExpiryYearFormat } from "@bitwarden/common/autofill/utils"; import { EventType } 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"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -43,7 +40,7 @@ import { SshKeyView } from "@bitwarden/common/vault/models/view/ssh-key.view"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { DialogService, ToastService } from "@bitwarden/components"; import { generate_ssh_key } from "@bitwarden/sdk-internal"; -import { PasswordRepromptService } from "@bitwarden/vault"; +import { PasswordRepromptService, SshImportPromptService } from "@bitwarden/vault"; @Directive() export class AddEditComponent implements OnInit, OnDestroy { @@ -104,8 +101,6 @@ export class AddEditComponent implements OnInit, OnDestroy { private personalOwnershipPolicyAppliesToActiveUser: boolean; private previousCipherId: string; - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - get fido2CredentialCreationDateValue(): string { const dateCreated = this.i18nService.t("dateCreated"); const creationDate = this.datePipe.transform( @@ -135,7 +130,8 @@ export class AddEditComponent implements OnInit, OnDestroy { protected configService: ConfigService, protected cipherAuthorizationService: CipherAuthorizationService, protected toastService: ToastService, - private sdkService: SdkService, + protected sdkService: SdkService, + private sshImportPromptService: SshImportPromptService, ) { this.typeOptions = [ { name: i18nService.t("typeLogin"), value: CipherType.Login }, @@ -211,10 +207,7 @@ export class AddEditComponent implements OnInit, OnDestroy { this.writeableCollections = await this.loadCollections(); this.canUseReprompt = await this.passwordRepromptService.enabled(); - const sshKeysEnabled = await this.configService.getFeatureFlag(FeatureFlag.SSHKeyVaultItem); - if (sshKeysEnabled) { - this.typeOptions.push({ name: this.i18nService.t("typeSshKey"), value: CipherType.SshKey }); - } + this.typeOptions.push({ name: this.i18nService.t("typeSshKey"), value: CipherType.SshKey }); } ngOnDestroy() { @@ -235,9 +228,12 @@ export class AddEditComponent implements OnInit, OnDestroy { this.ownershipOptions.push({ name: myEmail, value: null }); } - const orgs = await this.organizationService.getAll(); + const userId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((account) => account?.id)), + ); + const orgs = await firstValueFrom(this.organizationService.organizations$(userId)); orgs - .filter(isMember) + .filter((org) => org.isMember) .sort(Utils.getSortFunction(this.i18nService, "name")) .forEach((o) => { if (o.enabled && o.status === OrganizationUserStatusType.Confirmed) { @@ -263,12 +259,13 @@ export class AddEditComponent implements OnInit, OnDestroy { this.title = this.i18nService.t("addItem"); } - const loadedAddEditCipherInfo = await this.loadAddEditCipherInfo(); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + + const loadedAddEditCipherInfo = await this.loadAddEditCipherInfo(activeUserId); - const activeUserId = await firstValueFrom(this.activeUserId$); if (this.cipher == null) { if (this.editMode) { - const cipher = await this.loadCipher(); + const cipher = await this.loadCipher(activeUserId); this.cipher = await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); @@ -313,10 +310,14 @@ export class AddEditComponent implements OnInit, OnDestroy { } // Only Admins can clone a cipher to different owner if (this.cloneMode && this.cipher.organizationId != null) { - const cipherOrg = (await firstValueFrom(this.organizationService.memberOrganizations$)).find( - (o) => o.id === this.cipher.organizationId, + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); + const cipherOrg = ( + await firstValueFrom(this.organizationService.memberOrganizations$(activeUserId)) + ).find((o) => o.id === this.cipher.organizationId); + if (cipherOrg != null && !cipherOrg.isAdmin && !cipherOrg.permissions.editAnyCollection) { this.ownershipOptions = [{ name: cipherOrg.name, value: cipherOrg.id }]; } @@ -372,11 +373,11 @@ export class AddEditComponent implements OnInit, OnDestroy { } if (this.cipher.name == null || this.cipher.name === "") { - this.platformUtilsService.showToast( - "error", - this.i18nService.t("errorOccurred"), - this.i18nService.t("nameRequired"), - ); + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: this.i18nService.t("nameRequired"), + }); return false; } @@ -385,11 +386,11 @@ export class AddEditComponent implements OnInit, OnDestroy { !this.allowPersonal && this.cipher.organizationId == null ) { - this.platformUtilsService.showToast( - "error", - this.i18nService.t("errorOccurred"), - this.i18nService.t("personalOwnershipSubmitError"), - ); + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: this.i18nService.t("personalOwnershipSubmitError"), + }); return false; } @@ -416,19 +417,17 @@ export class AddEditComponent implements OnInit, OnDestroy { this.cipher.id = null; } - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const cipher = await this.encryptCipher(activeUserId); try { this.formPromise = this.saveCipher(cipher); await this.formPromise; this.cipher.id = cipher.id; - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t(this.editMode && !this.cloneMode ? "editedItem" : "addedItem"), - ); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t(this.editMode && !this.cloneMode ? "editedItem" : "addedItem"), + }); this.onSavedCipher.emit(this.cipher); this.messagingService.send(this.editMode && !this.cloneMode ? "editedCipher" : "addedCipher"); return true; @@ -512,13 +511,16 @@ export class AddEditComponent implements OnInit, OnDestroy { } try { - this.deletePromise = this.deleteCipher(); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + this.deletePromise = this.deleteCipher(activeUserId); await this.deletePromise; - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t(this.cipher.isDeleted ? "permanentlyDeletedItem" : "deletedItem"), - ); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t( + this.cipher.isDeleted ? "permanentlyDeletedItem" : "deletedItem", + ), + }); this.onDeletedCipher.emit(this.cipher); this.messagingService.send( this.cipher.isDeleted ? "permanentlyDeletedCipher" : "deletedCipher", @@ -536,9 +538,14 @@ export class AddEditComponent implements OnInit, OnDestroy { } try { - this.restorePromise = this.restoreCipher(); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + this.restorePromise = this.restoreCipher(activeUserId); await this.restorePromise; - this.platformUtilsService.showToast("success", null, this.i18nService.t("restoredItem")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("restoredItem"), + }); this.onRestoredCipher.emit(this.cipher); this.messagingService.send("restoredCipher"); } catch (e) { @@ -652,7 +659,13 @@ export class AddEditComponent implements OnInit, OnDestroy { if (this.collections.length === 1) { (this.collections[0] as any).checked = true; } - const org = await this.organizationService.get(this.cipher.organizationId); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + + const org = ( + await firstValueFrom(this.organizationService.organizations$(activeUserId)) + ).find((org) => org.id === this.cipher.organizationId); if (org != null) { this.cipher.organizationUseTotp = org.useTotp; } @@ -679,13 +692,17 @@ export class AddEditComponent implements OnInit, OnDestroy { this.checkPasswordPromise = null; if (matches > 0) { - this.platformUtilsService.showToast( - "warning", - null, - this.i18nService.t("passwordExposed", matches.toString()), - ); + this.toastService.showToast({ + variant: "warning", + title: null, + message: this.i18nService.t("passwordExposed", matches.toString()), + }); } else { - this.platformUtilsService.showToast("success", null, this.i18nService.t("passwordSafe")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("passwordSafe"), + }); } } @@ -705,8 +722,8 @@ export class AddEditComponent implements OnInit, OnDestroy { return allCollections.filter((c) => !c.readOnly); } - protected loadCipher() { - return this.cipherService.get(this.cipherId); + protected loadCipher(userId: UserId) { + return this.cipherService.get(this.cipherId, userId); } protected encryptCipher(userId: UserId) { @@ -726,14 +743,14 @@ export class AddEditComponent implements OnInit, OnDestroy { : this.cipherService.updateWithServer(cipher, orgAdmin); } - protected deleteCipher() { + protected deleteCipher(userId: UserId) { return this.cipher.isDeleted - ? this.cipherService.deleteWithServer(this.cipher.id, this.asAdmin) - : this.cipherService.softDeleteWithServer(this.cipher.id, this.asAdmin); + ? this.cipherService.deleteWithServer(this.cipher.id, userId, this.asAdmin) + : this.cipherService.softDeleteWithServer(this.cipher.id, userId, this.asAdmin); } - protected restoreCipher() { - return this.cipherService.restoreWithServer(this.cipher.id, this.asAdmin); + protected restoreCipher(userId: UserId) { + return this.cipherService.restoreWithServer(this.cipher.id, userId, this.asAdmin); } /** @@ -753,8 +770,10 @@ export class AddEditComponent implements OnInit, OnDestroy { return this.ownershipOptions[0].value; } - async loadAddEditCipherInfo(): Promise { - const addEditCipherInfo: any = await firstValueFrom(this.cipherService.addEditCipherInfo$); + async loadAddEditCipherInfo(userId: UserId): Promise { + const addEditCipherInfo: any = await firstValueFrom( + this.cipherService.addEditCipherInfo$(userId), + ); const loadedSavedInfo = addEditCipherInfo != null; if (loadedSavedInfo) { @@ -767,7 +786,7 @@ export class AddEditComponent implements OnInit, OnDestroy { } } - await this.cipherService.setAddEditCipherInfo(null); + await this.cipherService.setAddEditCipherInfo(null, userId); return loadedSavedInfo; } @@ -779,11 +798,11 @@ export class AddEditComponent implements OnInit, OnDestroy { const copyOptions = this.win != null ? { window: this.win } : null; this.platformUtilsService.copyToClipboard(value, copyOptions); - this.platformUtilsService.showToast( - "info", - null, - this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)), - ); + this.toastService.showToast({ + variant: "info", + title: null, + message: this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)), + }); if (typeI18nKey === "password") { void this.eventCollectionService.collectMany(EventType.Cipher_ClientCopiedPassword, [ @@ -802,12 +821,21 @@ export class AddEditComponent implements OnInit, OnDestroy { return true; } + async importSshKeyFromClipboard() { + const key = await this.sshImportPromptService.importSshKeyFromClipboard(); + if (key != null) { + this.cipher.sshKey.privateKey = key.privateKey; + this.cipher.sshKey.publicKey = key.publicKey; + this.cipher.sshKey.keyFingerprint = key.keyFingerprint; + } + } + private async generateSshKey(showNotification: boolean = true) { await firstValueFrom(this.sdkService.client$); const sshKey = generate_ssh_key("Ed25519"); - this.cipher.sshKey.privateKey = sshKey.private_key; - this.cipher.sshKey.publicKey = sshKey.public_key; - this.cipher.sshKey.keyFingerprint = sshKey.key_fingerprint; + this.cipher.sshKey.privateKey = sshKey.privateKey; + this.cipher.sshKey.publicKey = sshKey.publicKey; + this.cipher.sshKey.keyFingerprint = sshKey.fingerprint; if (showNotification) { this.toastService.showToast({ diff --git a/libs/angular/src/vault/components/attachments.component.ts b/libs/angular/src/vault/components/attachments.component.ts index 425b4be2840..b1bfcde852a 100644 --- a/libs/angular/src/vault/components/attachments.component.ts +++ b/libs/angular/src/vault/components/attachments.component.ts @@ -1,13 +1,14 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core"; -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; 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 { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -16,6 +17,7 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -26,7 +28,7 @@ import { KeyService } from "@bitwarden/key-management"; export class AttachmentsComponent implements OnInit { @Input() cipherId: string; @Input() viewOnly: boolean; - @Output() onUploadedAttachment = new EventEmitter(); + @Output() onUploadedAttachment = new EventEmitter(); @Output() onDeletedAttachment = new EventEmitter(); @Output() onReuploadedAttachment = new EventEmitter(); @@ -34,7 +36,7 @@ export class AttachmentsComponent implements OnInit { cipherDomain: Cipher; canAccessAttachments: boolean; formPromise: Promise; - deletePromises: { [id: string]: Promise } = {}; + deletePromises: { [id: string]: Promise } = {}; reuploadPromises: { [id: string]: Promise } = {}; emergencyAccessId?: string = null; protected componentName = ""; @@ -64,35 +66,37 @@ export class AttachmentsComponent implements OnInit { const fileEl = document.getElementById("file") as HTMLInputElement; const files = fileEl.files; if (files == null || files.length === 0) { - this.platformUtilsService.showToast( - "error", - this.i18nService.t("errorOccurred"), - this.i18nService.t("selectFile"), - ); + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: this.i18nService.t("selectFile"), + }); return; } if (files[0].size > 524288000) { // 500 MB - this.platformUtilsService.showToast( - "error", - this.i18nService.t("errorOccurred"), - this.i18nService.t("maxFileSize"), - ); + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: this.i18nService.t("maxFileSize"), + }); return; } try { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); this.formPromise = this.saveCipherAttachment(files[0], activeUserId); this.cipherDomain = await this.formPromise; this.cipher = await this.cipherDomain.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId), ); - this.platformUtilsService.showToast("success", null, this.i18nService.t("attachmentSaved")); - this.onUploadedAttachment.emit(); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("attachmentSaved"), + }); + this.onUploadedAttachment.emit(this.cipher); } catch (e) { this.logService.error(e); } @@ -120,9 +124,21 @@ export class AttachmentsComponent implements OnInit { } try { - this.deletePromises[attachment.id] = this.deleteCipherAttachment(attachment.id); - await this.deletePromises[attachment.id]; - this.platformUtilsService.showToast("success", null, this.i18nService.t("deletedAttachment")); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + + this.deletePromises[attachment.id] = this.deleteCipherAttachment(attachment.id, activeUserId); + const updatedCipher = await this.deletePromises[attachment.id]; + + const cipher = new Cipher(updatedCipher); + this.cipher = await cipher.decrypt( + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), + ); + + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("deletedAttachment"), + }); const i = this.cipher.attachments.indexOf(attachment); if (i > -1) { this.cipher.attachments.splice(i, 1); @@ -132,7 +148,7 @@ export class AttachmentsComponent implements OnInit { } this.deletePromises[attachment.id] = null; - this.onDeletedAttachment.emit(); + this.onDeletedAttachment.emit(this.cipher); } async download(attachment: AttachmentView) { @@ -142,11 +158,11 @@ export class AttachmentsComponent implements OnInit { } if (!this.canAccessAttachments) { - this.platformUtilsService.showToast( - "error", - this.i18nService.t("premiumRequired"), - this.i18nService.t("premiumRequiredDesc"), - ); + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("premiumRequired"), + message: this.i18nService.t("premiumRequiredDesc"), + }); return; } @@ -171,7 +187,11 @@ export class AttachmentsComponent implements OnInit { a.downloading = true; const response = await fetch(new Request(url, { cache: "no-store" })); if (response.status !== 200) { - this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("errorOccurred"), + }); a.downloading = false; return; } @@ -192,18 +212,22 @@ export class AttachmentsComponent implements OnInit { title: null, message: this.i18nService.t("fileSavedToDevice"), }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { - this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("errorOccurred"), + }); } a.downloading = false; } protected async init() { - this.cipherDomain = await this.loadCipher(); - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + this.cipherDomain = await this.loadCipher(activeUserId); this.cipher = await this.cipherDomain.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId), ); @@ -241,7 +265,11 @@ export class AttachmentsComponent implements OnInit { a.downloading = true; const response = await fetch(new Request(attachment.url, { cache: "no-store" })); if (response.status !== 200) { - this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("errorOccurred"), + }); a.downloading = false; return; } @@ -255,7 +283,7 @@ export class AttachmentsComponent implements OnInit { : await this.keyService.getOrgKey(this.cipher.organizationId); const decBuf = await this.encryptService.decryptToBytes(encBuf, key); const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), + this.accountService.activeAccount$.pipe(getUserId), ); this.cipherDomain = await this.cipherService.saveAttachmentRawWithServer( this.cipherDomain, @@ -269,7 +297,10 @@ export class AttachmentsComponent implements OnInit { ); // 3. Delete old - this.deletePromises[attachment.id] = this.deleteCipherAttachment(attachment.id); + this.deletePromises[attachment.id] = this.deleteCipherAttachment( + attachment.id, + activeUserId, + ); await this.deletePromises[attachment.id]; const foundAttachment = this.cipher.attachments.filter((a2) => a2.id === attachment.id); if (foundAttachment.length > 0) { @@ -279,14 +310,20 @@ export class AttachmentsComponent implements OnInit { } } - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("attachmentSaved"), - ); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("attachmentSaved"), + }); this.onReuploadedAttachment.emit(); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { - this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("errorOccurred"), + }); } a.downloading = false; @@ -297,16 +334,16 @@ export class AttachmentsComponent implements OnInit { } } - protected loadCipher() { - return this.cipherService.get(this.cipherId); + protected loadCipher(userId: UserId) { + return this.cipherService.get(this.cipherId, userId); } protected saveCipherAttachment(file: File, userId: UserId) { return this.cipherService.saveAttachmentWithServer(this.cipherDomain, file, userId); } - protected deleteCipherAttachment(attachmentId: string) { - return this.cipherService.deleteAttachmentWithServer(this.cipher.id, attachmentId); + protected deleteCipherAttachment(attachmentId: string, userId: UserId) { + return this.cipherService.deleteAttachmentWithServer(this.cipher.id, attachmentId, userId); } protected async reupload(attachment: AttachmentView) { diff --git a/libs/angular/src/vault/components/folder-add-edit.component.ts b/libs/angular/src/vault/components/folder-add-edit.component.ts index 205733ba48d..28ed0dc2aed 100644 --- a/libs/angular/src/vault/components/folder-add-edit.component.ts +++ b/libs/angular/src/vault/components/folder-add-edit.component.ts @@ -11,7 +11,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; @Directive() @@ -43,6 +43,7 @@ export class FolderAddEditComponent implements OnInit { protected logService: LogService, protected dialogService: DialogService, protected formBuilder: FormBuilder, + protected toastService: ToastService, ) {} async ngOnInit() { @@ -52,11 +53,11 @@ export class FolderAddEditComponent implements OnInit { async submit(): Promise { this.folder.name = this.formGroup.controls.name.value; if (this.folder.name == null || this.folder.name === "") { - this.platformUtilsService.showToast( - "error", - this.i18nService.t("errorOccurred"), - this.i18nService.t("nameRequired"), - ); + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: this.i18nService.t("nameRequired"), + }); return false; } @@ -66,11 +67,11 @@ export class FolderAddEditComponent implements OnInit { const folder = await this.folderService.encrypt(this.folder, userKey); this.formPromise = this.folderApiService.save(folder, activeUserId); await this.formPromise; - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t(this.editMode ? "editedFolder" : "addedFolder"), - ); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t(this.editMode ? "editedFolder" : "addedFolder"), + }); this.onSavedFolder.emit(this.folder); return true; } catch (e) { @@ -95,7 +96,11 @@ export class FolderAddEditComponent implements OnInit { const activeUserId = await firstValueFrom(this.activeUserId$); this.deletePromise = this.folderApiService.delete(this.folder.id, activeUserId); await this.deletePromise; - this.platformUtilsService.showToast("success", null, this.i18nService.t("deletedFolder")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("deletedFolder"), + }); this.onDeletedFolder.emit(this.folder); } catch (e) { this.logService.error(e); diff --git a/libs/angular/src/vault/components/icon.component.html b/libs/angular/src/vault/components/icon.component.html index 9fec22f4a64..2dae3b26cc5 100644 --- a/libs/angular/src/vault/components/icon.component.html +++ b/libs/angular/src/vault/components/icon.component.html @@ -2,16 +2,18 @@
    diff --git a/libs/angular/src/vault/components/icon.component.ts b/libs/angular/src/vault/components/icon.component.ts index c30fb8a53e7..248378bf5ee 100644 --- a/libs/angular/src/vault/components/icon.component.ts +++ b/libs/angular/src/vault/components/icon.component.ts @@ -1,18 +1,18 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { ChangeDetectionStrategy, Component, Input, OnInit } from "@angular/core"; +import { ChangeDetectionStrategy, Component, input, signal } from "@angular/core"; +import { toObservable } from "@angular/core/rxjs-interop"; import { - BehaviorSubject, combineLatest, distinctUntilChanged, - filter, map, + tap, Observable, + startWith, + pairwise, } from "rxjs"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { buildCipherIcon } from "@bitwarden/common/vault/icon/build-cipher-icon"; +import { buildCipherIcon, CipherIconDetails } from "@bitwarden/common/vault/icon/build-cipher-icon"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @Component({ @@ -20,33 +20,40 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; templateUrl: "icon.component.html", changeDetection: ChangeDetectionStrategy.OnPush, }) -export class IconComponent implements OnInit { - @Input() - set cipher(value: CipherView) { - this.cipher$.next(value); - } +export class IconComponent { + /** + * The cipher to display the icon for. + */ + cipher = input.required(); - protected data$: Observable<{ - imageEnabled: boolean; - image?: string; - fallbackImage: string; - icon?: string; - }>; + imageLoaded = signal(false); - private cipher$ = new BehaviorSubject(undefined); + protected data$: Observable; constructor( private environmentService: EnvironmentService, private domainSettingsService: DomainSettingsService, - ) {} - - async ngOnInit() { - this.data$ = combineLatest([ + ) { + const iconSettings$ = combineLatest([ this.environmentService.environment$.pipe(map((e) => e.getIconsUrl())), this.domainSettingsService.showFavicons$.pipe(distinctUntilChanged()), - this.cipher$.pipe(filter((c) => c !== undefined)), ]).pipe( - map(([iconsUrl, showFavicon, cipher]) => buildCipherIcon(iconsUrl, cipher, showFavicon)), + map(([iconsUrl, showFavicon]) => ({ iconsUrl, showFavicon })), + startWith({ iconsUrl: null, showFavicon: false }), // Start with a safe default to avoid flickering icons + distinctUntilChanged(), + ); + + this.data$ = combineLatest([iconSettings$, toObservable(this.cipher)]).pipe( + map(([{ iconsUrl, showFavicon }, cipher]) => buildCipherIcon(iconsUrl, cipher, showFavicon)), + startWith(null), + pairwise(), + tap(([prev, next]) => { + if (prev?.image !== next?.image) { + // The image changed, reset the loaded state to not show an empty icon + this.imageLoaded.set(false); + } + }), + map(([_, next]) => next!), ); } } diff --git a/libs/angular/src/vault/components/password-history.component.ts b/libs/angular/src/vault/components/password-history.component.ts index 942a34c58bb..4df9f4bd24d 100644 --- a/libs/angular/src/vault/components/password-history.component.ts +++ b/libs/angular/src/vault/components/password-history.component.ts @@ -1,13 +1,15 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Directive, OnInit } from "@angular/core"; -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { PasswordHistoryView } from "@bitwarden/common/vault/models/view/password-history.view"; +import { ToastService } from "@bitwarden/components"; @Directive() export class PasswordHistoryComponent implements OnInit { @@ -20,6 +22,7 @@ export class PasswordHistoryComponent implements OnInit { protected i18nService: I18nService, protected accountService: AccountService, private win: Window, + private toastService: ToastService, ) {} async ngOnInit() { @@ -29,18 +32,16 @@ export class PasswordHistoryComponent implements OnInit { copy(password: string) { const copyOptions = this.win != null ? { window: this.win } : null; this.platformUtilsService.copyToClipboard(password, copyOptions); - this.platformUtilsService.showToast( - "info", - null, - this.i18nService.t("valueCopied", this.i18nService.t("password")), - ); + this.toastService.showToast({ + variant: "info", + title: null, + message: this.i18nService.t("valueCopied", this.i18nService.t("password")), + }); } protected async init() { - const cipher = await this.cipherService.get(this.cipherId); - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + const cipher = await this.cipherService.get(this.cipherId, activeUserId); const decCipher = await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); diff --git a/libs/angular/src/vault/components/vault-items.component.ts b/libs/angular/src/vault/components/vault-items.component.ts index 4ef00e90063..f7280cb74b3 100644 --- a/libs/angular/src/vault/components/vault-items.component.ts +++ b/libs/angular/src/vault/components/vault-items.component.ts @@ -1,10 +1,13 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; -import { BehaviorSubject, firstValueFrom, from, Subject, switchMap, takeUntil } from "rxjs"; +import { BehaviorSubject, Subject, firstValueFrom, from, switchMap, takeUntil } from "rxjs"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -18,14 +21,13 @@ export class VaultItemsComponent implements OnInit, OnDestroy { loaded = false; ciphers: CipherView[] = []; - searchPlaceholder: string = null; filter: (cipher: CipherView) => boolean = null; deleted = false; organization: Organization; - accessEvents = false; protected searchPending = false; + private userId: UserId; private destroy$ = new Subject(); private searchTimeout: any = null; private isSearchable: boolean = false; @@ -40,12 +42,15 @@ export class VaultItemsComponent implements OnInit, OnDestroy { constructor( protected searchService: SearchService, protected cipherService: CipherService, + protected accountService: AccountService, ) {} - ngOnInit(): void { + async ngOnInit() { + this.userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + this._searchText$ .pipe( - switchMap((searchText) => from(this.searchService.isSearchable(searchText))), + switchMap((searchText) => from(this.searchService.isSearchable(this.userId, searchText))), takeUntil(this.destroy$), ) .subscribe((isSearchable) => { @@ -116,15 +121,22 @@ export class VaultItemsComponent implements OnInit, OnDestroy { protected deletedFilter: (cipher: CipherView) => boolean = (c) => c.isDeleted === this.deleted; - protected async doSearch(indexedCiphers?: CipherView[]) { - indexedCiphers = indexedCiphers ?? (await this.cipherService.getAllDecrypted()); + protected async doSearch(indexedCiphers?: CipherView[], userId?: UserId) { + // Get userId from activeAccount if not provided from parent stream + if (!userId) { + userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + } - const failedCiphers = await firstValueFrom(this.cipherService.failedToDecryptCiphers$); + indexedCiphers = + indexedCiphers ?? (await firstValueFrom(this.cipherService.cipherViews$(userId))); + + const failedCiphers = await firstValueFrom(this.cipherService.failedToDecryptCiphers$(userId)); if (failedCiphers != null && failedCiphers.length > 0) { indexedCiphers = [...failedCiphers, ...indexedCiphers]; } this.ciphers = await this.searchService.searchCiphers( + this.userId, this.searchText, [this.filter, this.deletedFilter], indexedCiphers, diff --git a/libs/angular/src/vault/components/view.component.ts b/libs/angular/src/vault/components/view.component.ts index fc12aeff2f2..a2285e6a835 100644 --- a/libs/angular/src/vault/components/view.component.ts +++ b/libs/angular/src/vault/components/view.component.ts @@ -11,36 +11,38 @@ import { OnInit, Output, } from "@angular/core"; -import { firstValueFrom, map, Observable } from "rxjs"; +import { filter, firstValueFrom, map, Observable } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EventType } from "@bitwarden/common/enums"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; -import { CollectionId } from "@bitwarden/common/types/guid"; +import { CollectionId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; -import { FieldType, CipherType } from "@bitwarden/common/vault/enums"; +import { CipherType, FieldType } from "@bitwarden/common/vault/enums"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; import { Launchable } from "@bitwarden/common/vault/interfaces/launchable"; import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; -import { DialogService } from "@bitwarden/components"; +import { TotpInfo } from "@bitwarden/common/vault/services/totp.service"; +import { DialogService, ToastService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; import { PasswordRepromptService } from "@bitwarden/vault"; @@ -65,21 +67,18 @@ export class ViewComponent implements OnDestroy, OnInit { showPrivateKey: boolean; canAccessPremium: boolean; showPremiumRequiredTotp: boolean; - totpCode: string; - totpCodeFormatted: string; - totpDash: number; - totpSec: number; - totpLow: boolean; fieldType = FieldType; checkPasswordPromise: Promise; folder: FolderView; cipherType = CipherType; - private totpInterval: any; private previousCipherId: string; private passwordReprompted = false; - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + /** + * Represents TOTP information including display formatting and timing + */ + protected totpInfo$: Observable | undefined; get fido2CredentialCreationDateValue(): string { const dateCreated = this.i18nService.t("dateCreated"); @@ -114,6 +113,7 @@ export class ViewComponent implements OnDestroy, OnInit { protected datePipe: DatePipe, protected accountService: AccountService, private billingAccountProfileStateService: BillingAccountProfileStateService, + protected toastService: ToastService, private cipherAuthorizationService: CipherAuthorizationService, ) {} @@ -142,11 +142,15 @@ export class ViewComponent implements OnDestroy, OnInit { async load() { this.cleanUp(); - const cipher = await this.cipherService.get(this.cipherId); - const activeUserId = await firstValueFrom(this.activeUserId$); - this.cipher = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), + // Grab individual cipher from `cipherViews$` for the most up-to-date information + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + this.cipher = await firstValueFrom( + this.cipherService.cipherViews$(activeUserId).pipe( + map((ciphers) => ciphers?.find((c) => c.id === this.cipherId)), + filter((cipher) => !!cipher), + ), ); + this.canAccessPremium = await firstValueFrom( this.billingAccountProfileStateService.hasPremiumFromAnySource$(activeUserId), ); @@ -162,19 +166,33 @@ export class ViewComponent implements OnDestroy, OnInit { ).find((f) => f.id == this.cipher.folderId); } - if ( + const canGenerateTotp = this.cipher.type === CipherType.Login && this.cipher.login.totp && - (cipher.organizationUseTotp || this.canAccessPremium) - ) { - await this.totpUpdateCode(); - const interval = this.totpService.getTimeInterval(this.cipher.login.totp); - await this.totpTick(interval); + (this.cipher.organizationUseTotp || this.canAccessPremium); - this.totpInterval = setInterval(async () => { - await this.totpTick(interval); - }, 1000); - } + this.totpInfo$ = canGenerateTotp + ? this.totpService.getCode$(this.cipher.login.totp).pipe( + map((response) => { + const epoch = Math.round(new Date().getTime() / 1000.0); + const mod = epoch % response.period; + + // Format code + const totpCodeFormatted = + response.code.length > 4 + ? `${response.code.slice(0, Math.floor(response.code.length / 2))} ${response.code.slice(Math.floor(response.code.length / 2))}` + : response.code; + + return { + totpCode: response.code, + totpCodeFormatted, + totpDash: +(Math.round(((78.6 / response.period) * mod + "e+2") as any) + "e-2"), + totpSec: response.period - mod, + totpLow: response.period - mod <= 7, + } as TotpInfo; + }), + ) + : undefined; if (this.previousCipherId !== this.cipherId) { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. @@ -241,12 +259,15 @@ export class ViewComponent implements OnDestroy, OnInit { } try { - await this.deleteCipher(); - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t(this.cipher.isDeleted ? "permanentlyDeletedItem" : "deletedItem"), - ); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + await this.deleteCipher(activeUserId); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t( + this.cipher.isDeleted ? "permanentlyDeletedItem" : "deletedItem", + ), + }); this.onDeletedCipher.emit(this.cipher); } catch (e) { this.logService.error(e); @@ -261,8 +282,13 @@ export class ViewComponent implements OnDestroy, OnInit { } try { - await this.restoreCipher(); - this.platformUtilsService.showToast("success", null, this.i18nService.t("restoredItem")); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + await this.restoreCipher(activeUserId); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("restoredItem"), + }); this.onRestoredCipher.emit(this.cipher); } catch (e) { this.logService.error(e); @@ -345,13 +371,17 @@ export class ViewComponent implements OnDestroy, OnInit { const matches = await this.checkPasswordPromise; if (matches > 0) { - this.platformUtilsService.showToast( - "warning", - null, - this.i18nService.t("passwordExposed", matches.toString()), - ); + this.toastService.showToast({ + variant: "warning", + title: null, + message: this.i18nService.t("passwordExposed", matches.toString()), + }); } else { - this.platformUtilsService.showToast("success", null, this.i18nService.t("passwordSafe")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("passwordSafe"), + }); } } @@ -361,7 +391,8 @@ export class ViewComponent implements OnDestroy, OnInit { } if (cipherId) { - await this.cipherService.updateLastLaunchedDate(cipherId); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + await this.cipherService.updateLastLaunchedDate(cipherId, activeUserId); } this.platformUtilsService.launchUri(uri.launchUri); @@ -381,11 +412,11 @@ export class ViewComponent implements OnDestroy, OnInit { const copyOptions = this.win != null ? { window: this.win } : null; this.platformUtilsService.copyToClipboard(value, copyOptions); - this.platformUtilsService.showToast( - "info", - null, - this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)), - ); + this.toastService.showToast({ + variant: "info", + title: null, + message: this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)), + }); if (typeI18nKey === "password") { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. @@ -418,11 +449,11 @@ export class ViewComponent implements OnDestroy, OnInit { } if (this.cipher.organizationId == null && !this.canAccessPremium) { - this.platformUtilsService.showToast( - "error", - this.i18nService.t("premiumRequired"), - this.i18nService.t("premiumRequiredDesc"), - ); + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("premiumRequired"), + message: this.i18nService.t("premiumRequiredDesc"), + }); return; } @@ -446,7 +477,11 @@ export class ViewComponent implements OnDestroy, OnInit { a.downloading = true; const response = await fetch(new Request(url, { cache: "no-store" })); if (response.status !== 200) { - this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("errorOccurred"), + }); a.downloading = false; return; } @@ -462,21 +497,27 @@ export class ViewComponent implements OnDestroy, OnInit { fileName: attachment.fileName, blobData: decBuf, }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { - this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("errorOccurred"), + }); } a.downloading = false; } - protected deleteCipher() { + protected deleteCipher(userId: UserId) { return this.cipher.isDeleted - ? this.cipherService.deleteWithServer(this.cipher.id) - : this.cipherService.softDeleteWithServer(this.cipher.id); + ? this.cipherService.deleteWithServer(this.cipher.id, userId) + : this.cipherService.softDeleteWithServer(this.cipher.id, userId); } - protected restoreCipher() { - return this.cipherService.restoreWithServer(this.cipher.id); + protected restoreCipher(userId: UserId) { + return this.cipherService.restoreWithServer(this.cipher.id, userId); } protected async promptPassword() { @@ -488,56 +529,11 @@ export class ViewComponent implements OnDestroy, OnInit { } private cleanUp() { - this.totpCode = null; this.cipher = null; this.folder = null; this.showPassword = false; this.showCardNumber = false; this.showCardCode = false; this.passwordReprompted = false; - if (this.totpInterval) { - clearInterval(this.totpInterval); - } - } - - private async totpUpdateCode() { - if ( - this.cipher == null || - this.cipher.type !== CipherType.Login || - this.cipher.login.totp == null - ) { - if (this.totpInterval) { - clearInterval(this.totpInterval); - } - return; - } - - this.totpCode = await this.totpService.getCode(this.cipher.login.totp); - if (this.totpCode != null) { - if (this.totpCode.length > 4) { - const half = Math.floor(this.totpCode.length / 2); - this.totpCodeFormatted = - this.totpCode.substring(0, half) + " " + this.totpCode.substring(half); - } else { - this.totpCodeFormatted = this.totpCode; - } - } else { - this.totpCodeFormatted = null; - if (this.totpInterval) { - clearInterval(this.totpInterval); - } - } - } - - private async totpTick(intervalSeconds: number) { - const epoch = Math.round(new Date().getTime() / 1000.0); - const mod = epoch % intervalSeconds; - - this.totpSec = intervalSeconds - mod; - this.totpDash = +(Math.round(((78.6 / intervalSeconds) * mod + "e+2") as any) + "e-2"); - this.totpLow = this.totpSec <= 7; - if (mod === 0) { - await this.totpUpdateCode(); - } } } diff --git a/libs/angular/src/vault/guards/new-device-verification-notice.guard.spec.ts b/libs/angular/src/vault/guards/new-device-verification-notice.guard.spec.ts index e278113a653..53e2cef0d05 100644 --- a/libs/angular/src/vault/guards/new-device-verification-notice.guard.spec.ts +++ b/libs/angular/src/vault/guards/new-device-verification-notice.guard.spec.ts @@ -2,14 +2,13 @@ import { TestBed } from "@angular/core/testing"; import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from "@angular/router"; import { BehaviorSubject } from "rxjs"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; 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 { NewDeviceVerificationNoticeService } from "@bitwarden/vault"; -import { NewDeviceVerificationNoticeService } from "../../../../vault/src/services/new-device-verification-notice.service"; import { VaultProfileService } from "../services/vault-profile.service"; import { NewDeviceVerificationNoticeGuard } from "./new-device-verification-notice.guard"; @@ -34,31 +33,42 @@ describe("NewDeviceVerificationNoticeGuard", () => { return Promise.resolve(false); }); - const isSelfHost = jest.fn().mockResolvedValue(false); + const isSelfHost = jest.fn().mockReturnValue(false); const getProfileTwoFactorEnabled = jest.fn().mockResolvedValue(false); - const policyAppliesToActiveUser$ = jest.fn().mockReturnValue(new BehaviorSubject(false)); const noticeState$ = jest.fn().mockReturnValue(new BehaviorSubject(null)); + const skipState$ = jest.fn().mockReturnValue(new BehaviorSubject(null)); const getProfileCreationDate = jest.fn().mockResolvedValue(eightDaysAgo); + const hasMasterPasswordAndMasterKeyHash = jest.fn().mockResolvedValue(true); + const getUserSSOBound = jest.fn().mockResolvedValue(false); + const getUserSSOBoundAdminOwner = jest.fn().mockResolvedValue(false); beforeEach(() => { getFeatureFlag.mockClear(); isSelfHost.mockClear(); getProfileCreationDate.mockClear(); getProfileTwoFactorEnabled.mockClear(); - policyAppliesToActiveUser$.mockClear(); createUrlTree.mockClear(); + hasMasterPasswordAndMasterKeyHash.mockClear(); + getUserSSOBound.mockClear(); + getUserSSOBoundAdminOwner.mockClear(); + skipState$.mockClear(); TestBed.configureTestingModule({ providers: [ { provide: Router, useValue: { createUrlTree } }, { provide: ConfigService, useValue: { getFeatureFlag } }, - { provide: NewDeviceVerificationNoticeService, useValue: { noticeState$ } }, + { provide: NewDeviceVerificationNoticeService, useValue: { noticeState$, skipState$ } }, { provide: AccountService, useValue: { activeAccount$ } }, { provide: PlatformUtilsService, useValue: { isSelfHost } }, - { provide: PolicyService, useValue: { policyAppliesToActiveUser$ } }, + { provide: UserVerificationService, useValue: { hasMasterPasswordAndMasterKeyHash } }, { provide: VaultProfileService, - useValue: { getProfileCreationDate, getProfileTwoFactorEnabled }, + useValue: { + getProfileCreationDate, + getProfileTwoFactorEnabled, + getUserSSOBound, + getUserSSOBoundAdminOwner, + }, }, ], }); @@ -90,7 +100,7 @@ describe("NewDeviceVerificationNoticeGuard", () => { expect(isSelfHost).not.toHaveBeenCalled(); expect(getProfileTwoFactorEnabled).not.toHaveBeenCalled(); expect(getProfileCreationDate).not.toHaveBeenCalled(); - expect(policyAppliesToActiveUser$).not.toHaveBeenCalled(); + expect(hasMasterPasswordAndMasterKeyHash).not.toHaveBeenCalled(); }); }); @@ -121,13 +131,6 @@ describe("NewDeviceVerificationNoticeGuard", () => { expect(await newDeviceGuard()).toBe(true); }); - it("returns `true` SSO is required", async () => { - policyAppliesToActiveUser$.mockReturnValueOnce(new BehaviorSubject(true)); - - expect(await newDeviceGuard()).toBe(true); - expect(policyAppliesToActiveUser$).toHaveBeenCalledWith(PolicyType.RequireSso); - }); - it("returns `true` when the profile was created less than a week ago", async () => { const sixDaysAgo = new Date(); sixDaysAgo.setDate(sixDaysAgo.getDate() - 6); @@ -137,6 +140,71 @@ describe("NewDeviceVerificationNoticeGuard", () => { expect(await newDeviceGuard()).toBe(true); }); + it("returns `true` when the profile service throws an error", async () => { + getProfileCreationDate.mockRejectedValueOnce(new Error("test")); + + expect(await newDeviceGuard()).toBe(true); + }); + + it("returns `true` when the skip state value is set to true", async () => { + skipState$.mockReturnValueOnce(new BehaviorSubject(true)); + + expect(await newDeviceGuard()).toBe(true); + expect(skipState$.mock.calls[0][0]).toBe("account-id"); + expect(skipState$.mock.calls.length).toBe(1); + }); + + describe("SSO bound", () => { + beforeEach(() => { + getFeatureFlag.mockImplementation((key) => { + if (key === FeatureFlag.NewDeviceVerificationPermanentDismiss) { + return Promise.resolve(true); + } + + return Promise.resolve(false); + }); + }); + + afterAll(() => { + getFeatureFlag.mockReturnValue(false); + }); + + it('returns "true" when the user is SSO bound and not an admin or owner', async () => { + getUserSSOBound.mockResolvedValueOnce(true); + getUserSSOBoundAdminOwner.mockResolvedValueOnce(false); + + expect(await newDeviceGuard()).toBe(true); + }); + + it('returns "true" when the user is an admin or owner of an SSO bound organization and has not logged in with their master password', async () => { + getUserSSOBound.mockResolvedValueOnce(true); + getUserSSOBoundAdminOwner.mockResolvedValueOnce(true); + hasMasterPasswordAndMasterKeyHash.mockResolvedValueOnce(false); + + expect(await newDeviceGuard()).toBe(true); + }); + + it("shows notice when the user is an admin or owner of an SSO bound organization and logged in with their master password", async () => { + getUserSSOBound.mockResolvedValueOnce(true); + getUserSSOBoundAdminOwner.mockResolvedValueOnce(true); + hasMasterPasswordAndMasterKeyHash.mockResolvedValueOnce(true); + + await newDeviceGuard(); + + expect(createUrlTree).toHaveBeenCalledWith(["/new-device-notice"]); + }); + + it("shows notice when the user that is not in an SSO bound organization", async () => { + getUserSSOBound.mockResolvedValueOnce(false); + getUserSSOBoundAdminOwner.mockResolvedValueOnce(false); + hasMasterPasswordAndMasterKeyHash.mockResolvedValueOnce(true); + + await newDeviceGuard(); + + expect(createUrlTree).toHaveBeenCalledWith(["/new-device-notice"]); + }); + }); + describe("temp flag", () => { beforeEach(() => { getFeatureFlag.mockImplementation((key) => { diff --git a/libs/angular/src/vault/guards/new-device-verification-notice.guard.ts b/libs/angular/src/vault/guards/new-device-verification-notice.guard.ts index 20550e0e8cf..4e26149249b 100644 --- a/libs/angular/src/vault/guards/new-device-verification-notice.guard.ts +++ b/libs/angular/src/vault/guards/new-device-verification-notice.guard.ts @@ -1,15 +1,14 @@ import { inject } from "@angular/core"; import { ActivatedRouteSnapshot, CanActivateFn, Router } from "@angular/router"; -import { Observable, firstValueFrom } from "rxjs"; +import { firstValueFrom, Observable } from "rxjs"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; 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 { NewDeviceVerificationNoticeService } from "@bitwarden/vault"; -import { NewDeviceVerificationNoticeService } from "../../../../vault/src/services/new-device-verification-notice.service"; import { VaultProfileService } from "../services/vault-profile.service"; export const NewDeviceVerificationNoticeGuard: CanActivateFn = async ( @@ -20,8 +19,8 @@ export const NewDeviceVerificationNoticeGuard: CanActivateFn = async ( const newDeviceVerificationNoticeService = inject(NewDeviceVerificationNoticeService); const accountService = inject(AccountService); const platformUtilsService = inject(PlatformUtilsService); - const policyService = inject(PolicyService); const vaultProfileService = inject(VaultProfileService); + const userVerificationService = inject(UserVerificationService); if (route.queryParams["fromNewDeviceVerification"]) { return true; @@ -45,17 +44,33 @@ export const NewDeviceVerificationNoticeGuard: CanActivateFn = async ( return router.createUrlTree(["/login"]); } - const has2FAEnabled = await hasATwoFactorProviderEnabled(vaultProfileService, currentAcct.id); - const isSelfHosted = await platformUtilsService.isSelfHost(); - const requiresSSO = await isSSORequired(policyService); - const isProfileLessThanWeekOld = await profileIsLessThanWeekOld( - vaultProfileService, - currentAcct.id, - ); + // Currently used by the auth recovery login flow and will get cleaned up in PM-18485. + if (await firstValueFrom(newDeviceVerificationNoticeService.skipState$(currentAcct.id))) { + return true; + } - // When any of the following are true, the device verification notice is - // not applicable for the user. - if (has2FAEnabled || isSelfHosted || requiresSSO || isProfileLessThanWeekOld) { + try { + const isSelfHosted = platformUtilsService.isSelfHost(); + const userIsSSOUser = await ssoAppliesToUser( + userVerificationService, + vaultProfileService, + currentAcct.id, + ); + const has2FAEnabled = await hasATwoFactorProviderEnabled(vaultProfileService, currentAcct.id); + const isProfileLessThanWeekOld = await profileIsLessThanWeekOld( + vaultProfileService, + currentAcct.id, + ); + + // When any of the following are true, the device verification notice is + // not applicable for the user. When the user has *not* logged in with their + // master password, assume they logged in with SSO. + if (has2FAEnabled || isSelfHosted || userIsSSOUser || isProfileLessThanWeekOld) { + return true; + } + } catch { + // Skip showing the notice if there was a problem determining applicability + // The most likely problem to occur is the user not having a network connection return true; } @@ -99,9 +114,39 @@ async function profileIsLessThanWeekOld( return !isMoreThan7DaysAgo(creationDate); } -/** Returns true when the user is required to login via SSO */ -async function isSSORequired(policyService: PolicyService) { - return firstValueFrom(policyService.policyAppliesToActiveUser$(PolicyType.RequireSso)); +/** + * Returns true when either: + * - The user is SSO bound to an organization and is not an Admin or Owner + * - The user is an Admin or Owner of an organization with SSO bound and has not logged in with their master password + * + * NOTE: There are edge cases where this does not satisfy the original requirement of showing the notice to + * users who are subject to the SSO required policy. When Owners and Admins log in with their MP they will see the notice + * when they log in with SSO they will not. This is a concession made because the original logic references policies would not work for TDE users. + * When this guard is run for those users a sync hasn't occurred and thus the policies are not available. + */ +async function ssoAppliesToUser( + userVerificationService: UserVerificationService, + vaultProfileService: VaultProfileService, + userId: string, +) { + const userSSOBound = await vaultProfileService.getUserSSOBound(userId); + const userSSOBoundAdminOwner = await vaultProfileService.getUserSSOBoundAdminOwner(userId); + const userLoggedInWithMP = await userLoggedInWithMasterPassword(userVerificationService, userId); + + const nonOwnerAdminSsoUser = userSSOBound && !userSSOBoundAdminOwner; + const ssoAdminOwnerLoggedInWithMP = userSSOBoundAdminOwner && !userLoggedInWithMP; + + return nonOwnerAdminSsoUser || ssoAdminOwnerLoggedInWithMP; +} + +/** + * Returns true when the user logged in with their master password. + */ +async function userLoggedInWithMasterPassword( + userVerificationService: UserVerificationService, + userId: string, +) { + return userVerificationService.hasMasterPasswordAndMasterKeyHash(userId); } /** Returns the true when the date given is older than 7 days */ diff --git a/libs/angular/src/vault/services/vault-profile.service.spec.ts b/libs/angular/src/vault/services/vault-profile.service.spec.ts index 7761503253a..ade34da39a6 100644 --- a/libs/angular/src/vault/services/vault-profile.service.spec.ts +++ b/libs/angular/src/vault/services/vault-profile.service.spec.ts @@ -1,6 +1,7 @@ import { TestBed } from "@angular/core/testing"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationUserType } from "@bitwarden/common/admin-console/enums"; import { VaultProfileService } from "./vault-profile.service"; @@ -13,6 +14,12 @@ describe("VaultProfileService", () => { creationDate: hardcodedDateString, twoFactorEnabled: true, id: "new-user-id", + organizations: [ + { + ssoBound: true, + type: OrganizationUserType.Admin, + }, + ], }); beforeEach(() => { @@ -91,4 +98,64 @@ describe("VaultProfileService", () => { expect(getProfile).not.toHaveBeenCalled(); }); }); + + describe("getUserSSOBound", () => { + it("calls `getProfile` when stored ssoBound property is not stored", async () => { + expect(service["userIsSsoBound"]).toBeNull(); + + const userIsSsoBound = await service.getUserSSOBound(userId); + + expect(userIsSsoBound).toBe(true); + expect(getProfile).toHaveBeenCalled(); + }); + + it("calls `getProfile` when stored profile id does not match", async () => { + service["userIsSsoBound"] = false; + service["userId"] = "old-user-id"; + + const userIsSsoBound = await service.getUserSSOBound(userId); + + expect(userIsSsoBound).toBe(true); + expect(getProfile).toHaveBeenCalled(); + }); + + it("does not call `getProfile` when ssoBound property is already stored", async () => { + service["userIsSsoBound"] = false; + + const userIsSsoBound = await service.getUserSSOBound(userId); + + expect(userIsSsoBound).toBe(false); + expect(getProfile).not.toHaveBeenCalled(); + }); + }); + + describe("getUserSSOBoundAdminOwner", () => { + it("calls `getProfile` when stored userIsSsoBoundAdminOwner property is not stored", async () => { + expect(service["userIsSsoBoundAdminOwner"]).toBeNull(); + + const userIsSsoBoundAdminOwner = await service.getUserSSOBoundAdminOwner(userId); + + expect(userIsSsoBoundAdminOwner).toBe(true); + expect(getProfile).toHaveBeenCalled(); + }); + + it("calls `getProfile` when stored profile id does not match", async () => { + service["userIsSsoBoundAdminOwner"] = false; + service["userId"] = "old-user-id"; + + const userIsSsoBoundAdminOwner = await service.getUserSSOBoundAdminOwner(userId); + + expect(userIsSsoBoundAdminOwner).toBe(true); + expect(getProfile).toHaveBeenCalled(); + }); + + it("does not call `getProfile` when userIsSsoBoundAdminOwner property is already stored", async () => { + service["userIsSsoBoundAdminOwner"] = false; + + const userIsSsoBoundAdminOwner = await service.getUserSSOBoundAdminOwner(userId); + + expect(userIsSsoBoundAdminOwner).toBe(false); + expect(getProfile).not.toHaveBeenCalled(); + }); + }); }); diff --git a/libs/angular/src/vault/services/vault-profile.service.ts b/libs/angular/src/vault/services/vault-profile.service.ts index b368a973781..21f4ecc2285 100644 --- a/libs/angular/src/vault/services/vault-profile.service.ts +++ b/libs/angular/src/vault/services/vault-profile.service.ts @@ -1,6 +1,7 @@ import { Injectable, inject } from "@angular/core"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationUserType } from "@bitwarden/common/admin-console/enums"; import { ProfileResponse } from "@bitwarden/common/models/response/profile.response"; @Injectable({ @@ -24,6 +25,12 @@ export class VaultProfileService { /** True when 2FA is enabled on the profile. */ private profile2FAEnabled: boolean | null = null; + /** True when ssoBound is true for any of the users organizations */ + private userIsSsoBound: boolean | null = null; + + /** True when the user is an admin or owner of the ssoBound organization */ + private userIsSsoBoundAdminOwner: boolean | null = null; + /** * Returns the creation date of the profile. * Note: `Date`s are mutable in JS, creating a new @@ -52,12 +59,43 @@ export class VaultProfileService { return profile.twoFactorEnabled; } + /** + * Returns whether the user logs in with SSO for any organization. + */ + async getUserSSOBound(userId: string): Promise { + if (this.userIsSsoBound !== null && userId === this.userId) { + return Promise.resolve(this.userIsSsoBound); + } + + await this.fetchAndCacheProfile(); + + return !!this.userIsSsoBound; + } + + /** + * Returns true when the user is an Admin or Owner of an organization with `ssoBound` true. + */ + async getUserSSOBoundAdminOwner(userId: string): Promise { + if (this.userIsSsoBoundAdminOwner !== null && userId === this.userId) { + return Promise.resolve(this.userIsSsoBoundAdminOwner); + } + + await this.fetchAndCacheProfile(); + + return !!this.userIsSsoBoundAdminOwner; + } + private async fetchAndCacheProfile(): Promise { const profile = await this.apiService.getProfile(); this.userId = profile.id; this.profileCreatedDate = profile.creationDate; this.profile2FAEnabled = profile.twoFactorEnabled; + const ssoBoundOrg = profile.organizations.find((org) => org.ssoBound); + this.userIsSsoBound = !!ssoBoundOrg; + this.userIsSsoBoundAdminOwner = + ssoBoundOrg?.type === OrganizationUserType.Admin || + ssoBoundOrg?.type === OrganizationUserType.Owner; return profile; } diff --git a/libs/angular/src/vault/vault-filter/services/vault-filter.service.ts b/libs/angular/src/vault/vault-filter/services/vault-filter.service.ts index dd0b49f356a..01fa3384b82 100644 --- a/libs/angular/src/vault/vault-filter/services/vault-filter.service.ts +++ b/libs/angular/src/vault/vault-filter/services/vault-filter.service.ts @@ -1,29 +1,27 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Injectable } from "@angular/core"; -import { firstValueFrom, from, map, mergeMap, Observable, switchMap } from "rxjs"; +import { firstValueFrom, from, map, mergeMap, Observable, switchMap, take } from "rxjs"; import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; -import { - isMember, - OrganizationService, -} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { ActiveUserState, StateProvider } from "@bitwarden/common/platform/state"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { ServiceUtils } from "@bitwarden/common/vault/service-utils"; +import { COLLAPSED_GROUPINGS } from "@bitwarden/common/vault/services/key-state/collapsed-groupings.state"; import { DeprecatedVaultFilterService as DeprecatedVaultFilterServiceAbstraction } from "../../abstractions/deprecated-vault-filter.service"; import { DynamicTreeNode } from "../models/dynamic-tree-node.model"; -import { COLLAPSED_GROUPINGS } from "./../../../../../common/src/vault/services/key-state/collapsed-groupings.state"; - const NestingDelimiter = "/"; @Injectable() @@ -33,8 +31,6 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti private readonly collapsedGroupings$: Observable> = this.collapsedGroupingsState.state$.pipe(map((c) => new Set(c))); - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - constructor( protected organizationService: OrganizationService, protected folderService: FolderService, @@ -54,16 +50,19 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti } async buildOrganizations(): Promise { - let organizations = await this.organizationService.getAll(); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + let organizations = await firstValueFrom(this.organizationService.organizations$(userId)); if (organizations != null) { - organizations = organizations.filter(isMember).sort((a, b) => a.name.localeCompare(b.name)); + organizations = organizations + .filter((o) => o.isMember) + .sort((a, b) => a.name.localeCompare(b.name)); } return organizations; } buildNestedFolders(organizationId?: string): Observable> { - const transformation = async (storedFolders: FolderView[]) => { + const transformation = async (storedFolders: FolderView[], userId: UserId) => { let folders: FolderView[]; // If no org or "My Vault" is selected, show all folders @@ -71,7 +70,7 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti folders = storedFolders; } else { // Otherwise, show only folders that have ciphers from the selected org and the "no folder" folder - const ciphers = await this.cipherService.getAllDecrypted(); + const ciphers = await this.cipherService.getAllDecrypted(userId); const orgCiphers = ciphers.filter((c) => c.organizationId == organizationId); folders = storedFolders.filter( (f) => orgCiphers.some((oc) => oc.folderId == f.id) || f.id == null, @@ -85,9 +84,14 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti }); }; - return this.activeUserId$.pipe( - switchMap((userId) => this.folderService.folderViews$(userId)), - mergeMap((folders) => from(transformation(folders))), + return this.accountService.activeAccount$.pipe( + take(1), + getUserId, + switchMap((userId) => + this.folderService + .folderViews$(userId) + .pipe(mergeMap((folders) => from(transformation(folders, userId)))), + ), ); } @@ -131,7 +135,7 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti } async getFolderNested(id: string): Promise> { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const folders = await this.getAllFoldersNested( await firstValueFrom(this.folderService.folderViews$(activeUserId)), ); diff --git a/libs/angular/test.setup.ts b/libs/angular/test.setup.ts index 6be6e7b8dd1..159c28d2be5 100644 --- a/libs/angular/test.setup.ts +++ b/libs/angular/test.setup.ts @@ -1,5 +1,5 @@ import { webcrypto } from "crypto"; -import "jest-preset-angular/setup-jest"; +import "@bitwarden/ui-common/setup-jest"; Object.defineProperty(window, "CSS", { value: null }); Object.defineProperty(window, "getComputedStyle", { diff --git a/libs/angular/tsconfig.json b/libs/angular/tsconfig.json index 6c510f81492..d77e56d778e 100644 --- a/libs/angular/tsconfig.json +++ b/libs/angular/tsconfig.json @@ -14,8 +14,10 @@ "@bitwarden/generator-legacy": ["../tools/generator/extensions/legacy/src"], "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"], "@bitwarden/importer/core": ["../importer/src"], + "@bitwarden/importer-ui": ["../importer/src/components"], "@bitwarden/key-management": ["../key-management/src"], "@bitwarden/platform": ["../platform/src"], + "@bitwarden/ui-common": ["../ui/common/src"], "@bitwarden/vault-export-core": ["../tools/export/vault-export/vault-export-core/src"], "@bitwarden/vault": ["../vault/src"] } diff --git a/libs/auth/package.json b/libs/auth/package.json index 3a915d727b1..52c1be63f81 100644 --- a/libs/auth/package.json +++ b/libs/auth/package.json @@ -16,10 +16,5 @@ "clean": "rimraf dist", "build": "npm run clean && tsc", "build:watch": "npm run clean && tsc -watch" - }, - "dependencies": { - "@bitwarden/angular": "file:../angular", - "@bitwarden/common": "file:../common", - "@bitwarden/components": "file:../components" } } diff --git a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.mdx b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.mdx index a218eaa1492..8fe332b8caf 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.mdx +++ b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.mdx @@ -6,10 +6,6 @@ import * as stories from "./anon-layout-wrapper.stories"; # Anon Layout Wrapper -NOTE: These stories will treat "Light & Dark" mode as "Light" mode. This is done to avoid a bug with -the way that we render the same component twice in the same iframe and how that interacts with the -`router-outlet`. - ## Anon Layout Wrapper Component The auth owned `AnonLayoutWrapperComponent` orchestrates routing configuration data and feeds it diff --git a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.stories.ts b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.stories.ts index b07504b7c8d..9f504c75d29 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.stories.ts +++ b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.stories.ts @@ -18,7 +18,8 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { ButtonModule } from "@bitwarden/components"; // FIXME: remove `/apps` import from `/libs` -// eslint-disable-next-line import/no-restricted-paths +// FIXME: remove `src` and fix import +// eslint-disable-next-line import/no-restricted-paths, no-restricted-imports import { PreloadedEnglishI18nModule } from "../../../../../apps/web/src/app/core/tests"; import { LockIcon } from "../icons"; import { RegistrationCheckEmailIcon } from "../icons/registration-check-email.icon"; diff --git a/libs/auth/src/angular/anon-layout/anon-layout.component.html b/libs/auth/src/angular/anon-layout/anon-layout.component.html index cb3445abd96..4120ea59002 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout.component.html +++ b/libs/auth/src/angular/anon-layout/anon-layout.component.html @@ -1,5 +1,5 @@
    -
    +
    @@ -40,14 +40,14 @@ [ngClass]="{ 'tw-max-w-md': maxWidth === 'md', 'tw-max-w-3xl': maxWidth === '3xl' }" >
    -
    +
    {{ "accessing" | i18n }} {{ hostname }}
    diff --git a/libs/auth/src/angular/anon-layout/anon-layout.component.ts b/libs/auth/src/angular/anon-layout/anon-layout.component.ts index 91229f38ab2..05ddb9614f1 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout.component.ts +++ b/libs/auth/src/angular/anon-layout/anon-layout.component.ts @@ -9,8 +9,14 @@ import { ClientType } from "@bitwarden/common/enums"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { IconModule, Icon } from "../../../../components/src/icon"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { SharedModule } from "../../../../components/src/shared"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TypographyModule } from "../../../../components/src/typography"; import { BitwardenLogo, BitwardenShield } from "../icons"; diff --git a/libs/auth/src/angular/anon-layout/anon-layout.stories.ts b/libs/auth/src/angular/anon-layout/anon-layout.stories.ts index 27eb27c53b9..c7e15d9dcfa 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout.stories.ts +++ b/libs/auth/src/angular/anon-layout/anon-layout.stories.ts @@ -7,7 +7,11 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ButtonModule } from "../../../../components/src/button"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { I18nMockService } from "../../../../components/src/utils/i18n-mock.service"; import { LockIcon } from "../icons"; diff --git a/libs/auth/src/angular/fingerprint-dialog/fingerprint-dialog.component.html b/libs/auth/src/angular/fingerprint-dialog/fingerprint-dialog.component.html index d6005c970f7..d66a3a77d93 100644 --- a/libs/auth/src/angular/fingerprint-dialog/fingerprint-dialog.component.html +++ b/libs/auth/src/angular/fingerprint-dialog/fingerprint-dialog.component.html @@ -1,5 +1,5 @@ - + {{ "yourAccountsFingerprint" | i18n }}: diff --git a/libs/auth/src/angular/icons/device-verification.icon.ts b/libs/auth/src/angular/icons/device-verification.icon.ts new file mode 100644 index 00000000000..b1be4efdfb3 --- /dev/null +++ b/libs/auth/src/angular/icons/device-verification.icon.ts @@ -0,0 +1,18 @@ +import { svgIcon } from "@bitwarden/components"; + +export const DeviceVerificationIcon = svgIcon` + + + + + + + + + + + + + + +`; diff --git a/libs/auth/src/angular/icons/index.ts b/libs/auth/src/angular/icons/index.ts index 0e86ee7fc8e..0ec92d54547 100644 --- a/libs/auth/src/angular/icons/index.ts +++ b/libs/auth/src/angular/icons/index.ts @@ -12,3 +12,4 @@ export * from "./registration-lock-alt.icon"; export * from "./registration-expired-link.icon"; export * from "./sso-key.icon"; export * from "./two-factor-timeout.icon"; +export * from "./device-verification.icon"; diff --git a/libs/auth/src/angular/icons/two-factor-auth/index.ts b/libs/auth/src/angular/icons/two-factor-auth/index.ts new file mode 100644 index 00000000000..d5ac187a5b9 --- /dev/null +++ b/libs/auth/src/angular/icons/two-factor-auth/index.ts @@ -0,0 +1,6 @@ +export * from "./two-factor-auth-authenticator.icon"; +export * from "./two-factor-auth-email.icon"; +export * from "./two-factor-auth-webauthn.icon"; +export * from "./two-factor-auth-security-key.icon"; +export * from "./two-factor-auth-duo.icon"; +export * from "./two-factor-auth-yubico.icon"; diff --git a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-authenticator.icon.ts b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-authenticator.icon.ts new file mode 100644 index 00000000000..daef1f94dca --- /dev/null +++ b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-authenticator.icon.ts @@ -0,0 +1,39 @@ +import { svgIcon } from "@bitwarden/components"; + +export const TwoFactorAuthAuthenticatorIcon = svgIcon` + + + + + + + + + + + + + + + + + + +`; diff --git a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-duo.icon.ts b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-duo.icon.ts new file mode 100644 index 00000000000..c81433d0fc1 --- /dev/null +++ b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-duo.icon.ts @@ -0,0 +1,20 @@ +import { svgIcon } from "@bitwarden/components"; + +export const TwoFactorAuthDuoIcon = svgIcon` + + + + + + + + + + + + + + +`; diff --git a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-email.icon.ts b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-email.icon.ts new file mode 100644 index 00000000000..833ab3f8e98 --- /dev/null +++ b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-email.icon.ts @@ -0,0 +1,18 @@ +import { svgIcon } from "@bitwarden/components"; + +export const TwoFactorAuthEmailIcon = svgIcon` + + + + + + + + + + +`; diff --git a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-security-key.icon.ts b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-security-key.icon.ts new file mode 100644 index 00000000000..f6ac90cfd5d --- /dev/null +++ b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-security-key.icon.ts @@ -0,0 +1,52 @@ +import { svgIcon } from "@bitwarden/components"; + +export const TwoFactorAuthSecurityKeyIcon = svgIcon` + + + + + + + + + + + + + + + + + + + + + + + + + + +`; diff --git a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-webauthn.icon.ts b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-webauthn.icon.ts new file mode 100644 index 00000000000..233533fc807 --- /dev/null +++ b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-webauthn.icon.ts @@ -0,0 +1,40 @@ +import { svgIcon } from "@bitwarden/components"; + +export const TwoFactorAuthWebAuthnIcon = svgIcon` + + + + + + + + + + + + + + +`; diff --git a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-yubico.icon.ts b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-yubico.icon.ts new file mode 100644 index 00000000000..6bb989a9d15 --- /dev/null +++ b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-yubico.icon.ts @@ -0,0 +1,15 @@ +import { svgIcon } from "@bitwarden/components"; + +export const TwoFactorAuthYubicoIcon = svgIcon` + + + + + + + + + + +`; diff --git a/libs/auth/src/angular/index.ts b/libs/auth/src/angular/index.ts index 66111f3e5af..bb2956b7569 100644 --- a/libs/auth/src/angular/index.ts +++ b/libs/auth/src/angular/index.ts @@ -71,3 +71,9 @@ export * from "./self-hosted-env-config-dialog/self-hosted-env-config-dialog.com // login approval export * from "./login-approval/login-approval.component"; export * from "./login-approval/default-login-approval-component.service"; + +// two factor auth +export * from "./two-factor-auth"; + +// device verification +export * from "./new-device-verification/new-device-verification.component"; diff --git a/libs/auth/src/angular/input-password/input-password.component.ts b/libs/auth/src/angular/input-password/input-password.component.ts index d6b7fad5c35..6059f638832 100644 --- a/libs/auth/src/angular/input-password/input-password.component.ts +++ b/libs/auth/src/angular/input-password/input-password.component.ts @@ -33,7 +33,11 @@ import { NEW_ARGON2_DEFAULT_KDF_CONFIG, } from "@bitwarden/key-management"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { InputsFieldMatch } from "../../../../angular/src/auth/validators/inputs-field-match.validator"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { SharedModule } from "../../../../components/src/shared"; import { PasswordCalloutComponent } from "../password-callout/password-callout.component"; diff --git a/libs/auth/src/angular/input-password/input-password.stories.ts b/libs/auth/src/angular/input-password/input-password.stories.ts index 99c0aba81b8..41577328f87 100644 --- a/libs/auth/src/angular/input-password/input-password.stories.ts +++ b/libs/auth/src/angular/input-password/input-password.stories.ts @@ -14,7 +14,8 @@ import { DialogService, ToastService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; // FIXME: remove `/apps` import from `/libs` -// eslint-disable-next-line import/no-restricted-paths +// FIXME: remove `src` and fix import +// eslint-disable-next-line import/no-restricted-paths, no-restricted-imports import { PreloadedEnglishI18nModule } from "../../../../../apps/web/src/app/core/tests"; import { InputPasswordComponent } from "./input-password.component"; diff --git a/libs/auth/src/angular/login-approval/login-approval.component.html b/libs/auth/src/angular/login-approval/login-approval.component.html index ddbc48d71a3..d37e30c5e0a 100644 --- a/libs/auth/src/angular/login-approval/login-approval.component.html +++ b/libs/auth/src/angular/login-approval/login-approval.component.html @@ -1,23 +1,34 @@ - {{ "areYouTryingtoLogin" | i18n }} + {{ "areYouTryingToAccessYourAccount" | i18n }} -

    {{ "logInAttemptBy" | i18n: email }}

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

    {{ fingerprintPhrase }}

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

    {{ authRequestResponse?.requestDeviceType }}

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

    {{ authRequestResponse?.requestIpAddress }}

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

    {{ requestTimeText }}

    -
    + +
    + +
    +
    + + +

    {{ "accessAttemptBy" | i18n: email }}

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

    {{ fingerprintPhrase }}

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

    {{ authRequestResponse?.requestDeviceType }}

    +
    +
    + {{ "location" | i18n }} +

    + {{ authRequestResponse?.requestCountryName }} + ({{ authRequestResponse?.requestIpAddress }}) +

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

    {{ requestTimeText }}

    +
    +
    diff --git a/libs/auth/src/angular/login-approval/login-approval.component.spec.ts b/libs/auth/src/angular/login-approval/login-approval.component.spec.ts index ff598bdeb91..da30df62fff 100644 --- a/libs/auth/src/angular/login-approval/login-approval.component.spec.ts +++ b/libs/auth/src/angular/login-approval/login-approval.component.spec.ts @@ -13,6 +13,7 @@ import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { UserId } from "@bitwarden/common/types/guid"; import { ToastService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; @@ -29,6 +30,7 @@ describe("LoginApprovalComponent", () => { let i18nService: MockProxy; let dialogRef: MockProxy; let toastService: MockProxy; + let validationService: MockProxy; const testNotificationId = "test-notification-id"; const testEmail = "test@bitwarden.com"; @@ -41,6 +43,7 @@ describe("LoginApprovalComponent", () => { i18nService = mock(); dialogRef = mock(); toastService = mock(); + validationService = mock(); accountService.activeAccount$ = of({ email: testEmail, @@ -62,6 +65,7 @@ describe("LoginApprovalComponent", () => { { provide: KeyService, useValue: mock() }, { provide: DialogRef, useValue: dialogRef }, { provide: ToastService, useValue: toastService }, + { provide: ValidationService, useValue: validationService }, { provide: LoginApprovalComponentServiceAbstraction, useValue: mock(), diff --git a/libs/auth/src/angular/login-approval/login-approval.component.ts b/libs/auth/src/angular/login-approval/login-approval.component.ts index 5192334a0ca..54d90306e5c 100644 --- a/libs/auth/src/angular/login-approval/login-approval.component.ts +++ b/libs/auth/src/angular/login-approval/login-approval.component.ts @@ -16,6 +16,7 @@ import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { AsyncActionsModule, @@ -40,6 +41,8 @@ export interface LoginApprovalDialogParams { imports: [CommonModule, AsyncActionsModule, ButtonModule, DialogModule, JslibModule], }) export class LoginApprovalComponent implements OnInit, OnDestroy { + loading = true; + notificationId: string; private destroy$ = new Subject(); @@ -62,27 +65,27 @@ export class LoginApprovalComponent implements OnInit, OnDestroy { private dialogRef: DialogRef, private toastService: ToastService, private loginApprovalComponentService: LoginApprovalComponentService, + private validationService: ValidationService, ) { this.notificationId = params.notificationId; } async ngOnDestroy(): Promise { clearInterval(this.interval); - const closedWithButton = await firstValueFrom(this.dialogRef.closed); - if (!closedWithButton) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.retrieveAuthRequestAndRespond(false); - } this.destroy$.next(); this.destroy$.complete(); } async ngOnInit() { if (this.notificationId != null) { - this.authRequestResponse = await this.apiService.getAuthRequest(this.notificationId); + try { + this.authRequestResponse = await this.apiService.getAuthRequest(this.notificationId); + } catch (error) { + this.validationService.showError(error); + } + const publicKey = Utils.fromB64ToArray(this.authRequestResponse.publicKey); - this.email = await await firstValueFrom( + this.email = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.email)), ); this.fingerprintPhrase = await this.authRequestService.getFingerprintPhrase( @@ -96,6 +99,8 @@ export class LoginApprovalComponent implements OnInit, OnDestroy { }, RequestTimeUpdate); this.loginApprovalComponentService.showLoginRequestedAlertIfWindowNotVisible(this.email); + + this.loading = false; } } @@ -131,6 +136,8 @@ export class LoginApprovalComponent implements OnInit, OnDestroy { ); this.showResultToast(loginResponse); } + + this.dialogRef.close(approve); } showResultToast(loginResponse: AuthRequestResponse) { diff --git a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.html b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.html index b3d218389bf..bf37380dc39 100644 --- a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.html +++ b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.html @@ -1,5 +1,5 @@ -
    +
    - this.ssoLoginService.getActiveUserOrganizationSsoIdentifier(), + this.ssoLoginService.getActiveUserOrganizationSsoIdentifier(this.activeAccountId), ).pipe( switchMap((organizationIdentifier) => { if (organizationIdentifier == undefined) { diff --git a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.html b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.html index a1d0f200c15..d6b91b960b0 100644 --- a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.html +++ b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.html @@ -1,6 +1,20 @@
    -

    {{ "makeSureYourAccountIsUnlockedAndTheFingerprintEtc" | i18n }}

    +

    + {{ "notificationSentDevicePart1" | i18n }} + {{ "notificationSentDeviceAnchor" | i18n }}. {{ "notificationSentDevicePart2" | i18n }} +

    +

    + {{ "notificationSentDeviceComplete" | i18n }} +

    {{ "fingerprintPhraseHeader" | i18n }}
    {{ fingerprintPhrase }} @@ -12,13 +26,14 @@ block buttonType="secondary" class="tw-mt-4" - (click)="startStandardAuthRequestLogin()" + (click)="startStandardAuthRequestLogin(true)" > {{ "resendNotification" | i18n }}
    - {{ "needAnotherOptionV1" | i18n }} + {{ "needAnotherOptionV1" | i18n }}  {{ "viewAllLogInOptions" | i18n }} @@ -32,7 +47,8 @@ {{ fingerprintPhrase }}
    - {{ "troubleLoggingIn" | i18n }} + {{ "troubleLoggingIn" | i18n }}  {{ "viewAllLogInOptions" | i18n }} diff --git a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts index b9a5ee4fe73..ea753a3f0c5 100644 --- a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts +++ b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts @@ -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, OnDestroy, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; @@ -17,7 +15,6 @@ import { import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AnonymousHubService } from "@bitwarden/common/auth/abstractions/anonymous-hub.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { AuthRequestType } from "@bitwarden/common/auth/enums/auth-request-type"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AdminAuthRequestStorable } from "@bitwarden/common/auth/models/domain/admin-auth-req-storable"; @@ -25,10 +22,15 @@ import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { AuthRequest } from "@bitwarden/common/auth/models/request/auth.request"; import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; +import { LoginViaAuthRequestView } from "@bitwarden/common/auth/models/view/login-via-auth-request.view"; import { ClientType, HttpStatusCode } from "@bitwarden/common/enums"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -39,6 +41,7 @@ import { ButtonModule, LinkModule, ToastService } from "@bitwarden/components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { AuthRequestApiService } from "../../common/abstractions/auth-request-api.service"; +import { LoginViaAuthRequestCacheService } from "../../common/services/auth-request/default-login-via-auth-request-cache.service"; enum Flow { StandardAuthRequest, // when user clicks "Login with device" from /login or "Approve from your other device" from /login-initiated @@ -56,21 +59,26 @@ const matchOptions: IsActiveMatchOptions = { standalone: true, templateUrl: "./login-via-auth-request.component.html", imports: [ButtonModule, CommonModule, JslibModule, LinkModule, RouterModule], + providers: [{ provide: LoginViaAuthRequestCacheService }], }) export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { - private authRequest: AuthRequest; - private authRequestKeyPair: { publicKey: Uint8Array; privateKey: Uint8Array }; - private authStatus: AuthenticationStatus; + private authRequest: AuthRequest | undefined = undefined; + private authRequestKeyPair: + | { publicKey: Uint8Array | undefined; privateKey: Uint8Array | undefined } + | undefined = undefined; + private authStatus: AuthenticationStatus | undefined = undefined; private showResendNotificationTimeoutSeconds = 12; protected backToRoute = "/login"; protected clientType: ClientType; protected ClientType = ClientType; - protected email: string; - protected fingerprintPhrase: string; + protected email: string | undefined = undefined; + protected fingerprintPhrase: string | undefined = undefined; protected showResendNotification = false; protected Flow = Flow; protected flow = Flow.StandardAuthRequest; + protected webVaultUrl: string | undefined = undefined; + protected deviceManagementUrl: string | undefined; constructor( private accountService: AccountService, @@ -81,6 +89,7 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { private authService: AuthService, private cryptoFunctionService: CryptoFunctionService, private deviceTrustService: DeviceTrustServiceAbstraction, + private environmentService: EnvironmentService, private i18nService: I18nService, private logService: LogService, private loginEmailService: LoginEmailServiceAbstraction, @@ -91,6 +100,8 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { private toastService: ToastService, private validationService: ValidationService, private loginSuccessHandlerService: LoginSuccessHandlerService, + private loginViaAuthRequestCacheService: LoginViaAuthRequestCacheService, + private configService: ConfigService, ) { this.clientType = this.platformUtilsService.getClientType(); @@ -109,11 +120,18 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { this.logService.error("Failed to use approved auth request: " + e.message); }); }); + + // Get the web vault URL from the environment service + this.environmentService.environment$.pipe(takeUntilDestroyed()).subscribe((env) => { + this.webVaultUrl = env.getWebVaultUrl(); + this.deviceManagementUrl = `${this.webVaultUrl}/#/settings/security/device-management`; + }); } async ngOnInit(): Promise { // Get the authStatus early because we use it in both flows this.authStatus = await firstValueFrom(this.authService.activeAccountStatus$); + await this.loginViaAuthRequestCacheService.init(); const userHasAuthenticatedViaSSO = this.authStatus === AuthenticationStatus.Locked; @@ -123,7 +141,7 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { /** * The LoginViaAuthRequestComponent handles both the `login-with-device` and - * the `admin-approval-requested` routes. Therefore we check the route to determine + * the `admin-approval-requested` routes. Therefore, we check the route to determine * which flow to initialize. */ if (this.router.isActive("admin-approval-requested", matchOptions)) { @@ -149,7 +167,14 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { // We only allow a single admin approval request to be active at a time // so we must check state to see if we have an existing one or not - const userId = (await firstValueFrom(this.accountService.activeAccount$)).id; + const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; + if (!userId) { + this.logService.error( + "Not able to get a user id from the account service active account observable.", + ); + return; + } + const existingAdminAuthRequest = await this.authRequestService.getAdminAuthRequest(userId); if (existingAdminAuthRequest) { @@ -162,7 +187,7 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { private async initStandardAuthRequestFlow(): Promise { this.flow = Flow.StandardAuthRequest; - this.email = await firstValueFrom(this.loginEmailService.loginEmail$); + this.email = (await firstValueFrom(this.loginEmailService.loginEmail$)) || undefined; if (!this.email) { await this.handleMissingEmail(); @@ -175,7 +200,6 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { private async handleMissingEmail(): Promise { this.toastService.showToast({ variant: "error", - title: null, message: this.i18nService.t("userEmailMissing"), }); @@ -184,21 +208,41 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { async ngOnDestroy(): Promise { await this.anonymousHubService.stopHubConnection(); + + this.loginViaAuthRequestCacheService.clearCacheLoginView(); } private async startAdminAuthRequestLogin(): Promise { try { await this.buildAuthRequest(AuthRequestType.AdminApproval); + if (!this.authRequest) { + this.logService.error("Auth request failed to build."); + return; + } + + if (!this.authRequestKeyPair) { + this.logService.error("Key pairs failed to initialize from buildAuthRequest."); + return; + } + const authRequestResponse = await this.authRequestApiService.postAdminAuthRequest( - this.authRequest, + this.authRequest as AuthRequest, ); const adminAuthReqStorable = new AdminAuthRequestStorable({ id: authRequestResponse.id, privateKey: this.authRequestKeyPair.privateKey, }); - const userId = (await firstValueFrom(this.accountService.activeAccount$)).id; + const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; + + if (!userId) { + this.logService.error( + "Not able to get a user id from the account service active account observable.", + ); + return; + } + await this.authRequestService.setAdminAuthRequest(adminAuthReqStorable, userId); if (authRequestResponse.id) { @@ -209,21 +253,104 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { } } - protected async startStandardAuthRequestLogin(): Promise { + protected async startStandardAuthRequestLogin( + clearCachedRequest: boolean = false, + ): Promise { this.showResendNotification = false; - try { - await this.buildAuthRequest(AuthRequestType.AuthenticateAndUnlock); - - const authRequestResponse = await this.authRequestApiService.postAuthRequest( - this.authRequest, - ); - - if (authRequestResponse.id) { - await this.anonymousHubService.createHubConnection(authRequestResponse.id); + if (await this.configService.getFeatureFlag(FeatureFlag.PM9112_DeviceApprovalPersistence)) { + // Used for manually refreshing the auth request when clicking the resend auth request + // on the ui. + if (clearCachedRequest) { + this.loginViaAuthRequestCacheService.clearCacheLoginView(); + } + + try { + const loginAuthRequestView: LoginViaAuthRequestView | null = + this.loginViaAuthRequestCacheService.getCachedLoginViaAuthRequestView(); + + if (!loginAuthRequestView) { + await this.buildAuthRequest(AuthRequestType.AuthenticateAndUnlock); + + // I tried several ways to get the IDE/linter to play nice with checking for null values + // in less code / more efficiently, but it struggles to identify code paths that + // are more complicated than this. + if (!this.authRequest) { + this.logService.error("AuthRequest failed to initialize from buildAuthRequest."); + return; + } + + if (!this.fingerprintPhrase) { + this.logService.error("FingerprintPhrase failed to initialize from buildAuthRequest."); + return; + } + + if (!this.authRequestKeyPair) { + this.logService.error("KeyPair failed to initialize from buildAuthRequest."); + return; + } + + const authRequestResponse: AuthRequestResponse = + await this.authRequestApiService.postAuthRequest(this.authRequest); + + this.loginViaAuthRequestCacheService.cacheLoginView( + this.authRequest, + authRequestResponse, + this.fingerprintPhrase, + this.authRequestKeyPair, + ); + + if (authRequestResponse.id) { + await this.anonymousHubService.createHubConnection(authRequestResponse.id); + } + } else { + // Grab the cached information and store it back in component state. + // We don't need the public key for handling the authentication request because + // the verifyAndHandleApprovedAuthReq function will receive the public key back + // from the looked up auth request and all we need is to make sure that + // we can use the cached private key that is associated with it. + this.authRequest = loginAuthRequestView.authRequest; + this.fingerprintPhrase = loginAuthRequestView.fingerprintPhrase; + this.authRequestKeyPair = { + privateKey: loginAuthRequestView.privateKey + ? Utils.fromB64ToArray(loginAuthRequestView.privateKey) + : undefined, + publicKey: undefined, + }; + + if (!loginAuthRequestView.authRequestResponse) { + this.logService.error("No cached auth request response."); + return; + } + + if (loginAuthRequestView.authRequestResponse.id) { + await this.anonymousHubService.createHubConnection( + loginAuthRequestView.authRequestResponse.id, + ); + } + } + } catch (e) { + this.logService.error(e); + } + } else { + try { + await this.buildAuthRequest(AuthRequestType.AuthenticateAndUnlock); + + if (!this.authRequest) { + this.logService.error("No auth request found."); + return; + } + + const authRequestResponse = await this.authRequestApiService.postAuthRequest( + this.authRequest, + ); + + if (authRequestResponse.id) { + await this.anonymousHubService.createHubConnection(authRequestResponse.id); + } + } catch (e) { + this.logService.error(e); } - } catch (e) { - this.logService.error(e); } setTimeout(() => { @@ -240,12 +367,23 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { }; const deviceIdentifier = await this.appIdService.getAppId(); + + if (!this.authRequestKeyPair.publicKey) { + this.logService.error("AuthRequest public key not set to value in building auth request."); + return; + } + const publicKey = Utils.fromBufferToB64(this.authRequestKeyPair.publicKey); const accessCode = await this.passwordGenerationService.generatePassword({ type: "password", length: 25, }); + if (!this.email) { + this.logService.error("Email not defined when building auth request."); + return; + } + this.fingerprintPhrase = await this.authRequestService.getFingerprintPhrase( this.email, this.authRequestKeyPair.publicKey, @@ -278,6 +416,8 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { if (error instanceof ErrorResponse && error.statusCode === HttpStatusCode.NotFound) { return await this.handleExistingAdminAuthReqDeletedOrDenied(userId); } + this.logService.error(error); + return; } // Request doesn't exist anymore @@ -290,6 +430,12 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { const derivedPublicKeyArrayBuffer = await this.cryptoFunctionService.rsaExtractPublicKey( adminAuthRequestStorable.privateKey, ); + + if (!this.email) { + this.logService.error("Email not defined when handling an existing an admin auth request."); + return; + } + this.fingerprintPhrase = await this.authRequestService.getFingerprintPhrase( this.email, derivedPublicKeyArrayBuffer, @@ -309,9 +455,12 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { ); } - // Request still pending response from admin - // set keypair and create hub connection so that any approvals will be received via push notification - this.authRequestKeyPair = { privateKey: adminAuthRequestStorable.privateKey, publicKey: null }; + // Request still pending response from admin set keypair and create hub connection + // so that any approvals will be received via push notification + this.authRequestKeyPair = { + privateKey: adminAuthRequestStorable.privateKey, + publicKey: undefined, + }; await this.anonymousHubService.createHubConnection(adminAuthRequestStorable.id); } @@ -372,7 +521,7 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { * | Standard Flow 1 | unauthed | "Login with device" [/login] | /login-with-device | yes | * | Standard Flow 2 | unauthed | "Login with device" [/login] | /login-with-device | no | * | Standard Flow 3 | authed | "Approve from your other device" [/login-initiated] | /login-with-device | yes | - * | Standard Flow 4 | authed | "Approve from your other device" [/login-initiated] | /login-with-device | no | | + * | Standard Flow 4 | authed | "Approve from your other device" [/login-initiated] | /login-with-device | no | * | Admin Flow | authed | "Request admin approval" [/login-initiated] | /admin-approval-requested | NA - admin requests always send encrypted userKey | * |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------| * * Note 1: The phrase "in memory" here is important. It is possible for a user to have a master password for their account, but not have a masterKey IN MEMORY for @@ -393,6 +542,11 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { await this.handleAuthenticatedFlows(authRequestResponse); } } else { + if (!this.authRequest) { + this.logService.error("No auth request defined when handling approved auth request."); + return; + } + // Get the auth request from the server // User is unauthenticated, therefore the endpoint requires an access code for user verification. const authRequestResponse = await this.authRequestApiService.getAuthResponse( @@ -413,11 +567,26 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { } this.logService.error(error); + } finally { + // Manually clean out the cache to make sure sensitive + // data does not persist longer than it needs to. + this.loginViaAuthRequestCacheService.clearCacheLoginView(); } } private async handleAuthenticatedFlows(authRequestResponse: AuthRequestResponse) { - const userId = (await firstValueFrom(this.accountService.activeAccount$)).id; + const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; + if (!userId) { + this.logService.error( + "Not able to get a user id from the account service active account observable.", + ); + return; + } + + if (!this.authRequestKeyPair || !this.authRequestKeyPair.privateKey) { + this.logService.error("No private key set when handling the authenticated flows."); + return; + } await this.decryptViaApprovedAuthRequest( authRequestResponse, @@ -435,6 +604,11 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { authRequestResponse, ); + if (!authRequestLoginCredentials) { + this.logService.error("Didn't set up auth request login credentials properly."); + return; + } + // Note: keys are set by AuthRequestLoginStrategy success handling const authResult = await this.loginStrategyService.logIn(authRequestLoginCredentials); @@ -453,7 +627,6 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { * - If `masterPasswordHash` has a value, we receive the `key` as an authRequestPublicKey(masterKey) [plus we have authRequestPublicKey(masterPasswordHash)] * - If `masterPasswordHash` does not have a value, we receive the `key` as an authRequestPublicKey(userKey) */ - if (authRequestResponse.masterPasswordHash) { // ...in Standard Auth Request Flow 3 await this.authRequestService.setKeysAfterDecryptingSharedMasterKeyAndHash( @@ -476,13 +649,17 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { this.toastService.showToast({ variant: "success", - title: null, message: this.i18nService.t("loginApproved"), }); // Now that we have a decrypted user key in memory, we can check if we // need to establish trust on the current device const activeAccount = await firstValueFrom(this.accountService.activeAccount$); + if (!activeAccount) { + this.logService.error("No active account defined from the account service."); + return; + } + await this.deviceTrustService.trustDeviceIfRequired(activeAccount.id); await this.handleSuccessfulLoginNavigation(userId); @@ -498,7 +675,24 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { private async buildAuthRequestLoginCredentials( requestId: string, authRequestResponse: AuthRequestResponse, - ): Promise { + ): Promise { + if (!this.authRequestKeyPair || !this.authRequestKeyPair.privateKey) { + this.logService.error("No private key set when building auth request login credentials."); + return; + } + + if (!this.email) { + this.logService.error("Email not defined."); + return; + } + + if (!this.authRequest) { + this.logService.error( + "AuthRequest not defined when building auth request login credentials.", + ); + return; + } + /** * See verifyAndHandleApprovedAuthReq() for flow details. * @@ -506,7 +700,6 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { * - If `masterPasswordHash` has a value, we receive the `key` as an authRequestPublicKey(masterKey) [plus we have authRequestPublicKey(masterPasswordHash)] * - If `masterPasswordHash` does not have a value, we receive the `key` as an authRequestPublicKey(userKey) */ - if (authRequestResponse.masterPasswordHash) { // ...in Standard Auth Request Flow 1 const { masterKey, masterKeyHash } = diff --git a/libs/auth/src/angular/login/default-login-component.service.spec.ts b/libs/auth/src/angular/login/default-login-component.service.spec.ts index 05b24da56cc..28e0e3db479 100644 --- a/libs/auth/src/angular/login/default-login-component.service.spec.ts +++ b/libs/auth/src/angular/login/default-login-component.service.spec.ts @@ -56,13 +56,6 @@ describe("DefaultLoginComponentService", () => { expect(service).toBeTruthy(); }); - describe("getOrgPolicies", () => { - it("returns null", async () => { - const result = await service.getOrgPolicies(); - expect(result).toBeNull(); - }); - }); - describe("isLoginWithPasskeySupported", () => { it("returns true when clientType is Web", () => { service["clientType"] = ClientType.Web; @@ -75,50 +68,21 @@ describe("DefaultLoginComponentService", () => { }); }); - describe("launchSsoBrowserWindow", () => { - const email = "test@bitwarden.com"; - let state = "testState"; - const codeVerifier = "testCodeVerifier"; - const codeChallenge = "testCodeChallenge"; - const baseUrl = "https://webvault.bitwarden.com/#/sso"; - - beforeEach(() => { - state = "testState"; + describe("redirectToSsoLogin", () => { + it("sets the pre-SSO state", async () => { + const email = "test@bitwarden.com"; + const state = "testState"; + const codeVerifier = "testCodeVerifier"; + const codeChallenge = "testCodeChallenge"; passwordGenerationService.generatePassword.mockResolvedValueOnce(state); passwordGenerationService.generatePassword.mockResolvedValueOnce(codeVerifier); jest.spyOn(Utils, "fromBufferToUrlB64").mockReturnValue(codeChallenge); + + await service.redirectToSsoLogin(email); + expect(ssoLoginService.setSsoEmail).toHaveBeenCalledWith(email); + expect(ssoLoginService.setSsoState).toHaveBeenCalledWith(state); + expect(ssoLoginService.setCodeVerifier).toHaveBeenCalledWith(codeVerifier); }); - - it.each([ - { - clientType: ClientType.Browser, - clientId: "browser", - expectedRedirectUri: "https://webvault.bitwarden.com/sso-connector.html", - }, - { - clientType: ClientType.Desktop, - clientId: "desktop", - expectedRedirectUri: "bitwarden://sso-callback", - }, - ])( - "launches SSO browser window with correct URL for $clientId client", - async ({ clientType, clientId, expectedRedirectUri }) => { - service["clientType"] = clientType; - - await service.launchSsoBrowserWindow(email, clientId as "browser" | "desktop"); - - if (clientType === ClientType.Browser) { - state += ":clientId=browser"; - } - - const expectedUrl = `${baseUrl}?clientId=${clientId}&redirectUri=${encodeURIComponent(expectedRedirectUri)}&state=${state}&codeChallenge=${codeChallenge}&email=${encodeURIComponent(email)}`; - - expect(ssoLoginService.setSsoEmail).toHaveBeenCalledWith(email); - expect(ssoLoginService.setSsoState).toHaveBeenCalledWith(state); - expect(ssoLoginService.setCodeVerifier).toHaveBeenCalledWith(codeVerifier); - expect(platformUtilsService.launchUri).toHaveBeenCalledWith(expectedUrl); - }, - ); }); }); diff --git a/libs/auth/src/angular/login/default-login-component.service.ts b/libs/auth/src/angular/login/default-login-component.service.ts index 84a7d923d12..c70be33f9d4 100644 --- a/libs/auth/src/angular/login/default-login-component.service.ts +++ b/libs/auth/src/angular/login/default-login-component.service.ts @@ -1,8 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { firstValueFrom } from "rxjs"; - -import { LoginComponentService, PasswordPolicies } from "@bitwarden/auth/angular"; +import { LoginComponentService } from "@bitwarden/auth/angular"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { ClientType } from "@bitwarden/common/enums"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; @@ -21,23 +19,55 @@ export class DefaultLoginComponentService implements LoginComponentService { protected passwordGenerationService: PasswordGenerationServiceAbstraction, protected platformUtilsService: PlatformUtilsService, protected ssoLoginService: SsoLoginServiceAbstraction, - ) {} - - async getOrgPolicies(): Promise { - return null; + ) { + this.clientType = this.platformUtilsService.getClientType(); } isLoginWithPasskeySupported(): boolean { return this.clientType === ClientType.Web; } - async launchSsoBrowserWindow( - email: string, - clientId: "browser" | "desktop", - ): Promise { - // Save email for SSO + /** + * Redirects the user to the SSO login page, either via route or in a new browser window. + * @param email The email address of the user attempting to log in + */ + async redirectToSsoLogin(email: string): Promise { + // Set the state that we'll need to verify the SSO login when we get the code back + const [state, codeChallenge] = await this.setSsoPreLoginState(); + + // Set the email address in state. This is used in 2 places: + // 1. On the web client, on the SSO component we need the email address to look up + // the org SSO identifier. The email address is passed via query param for the other clients. + // 2. On all clients, after authentication on the originating client the SSO component + // will need to look up 2FA Remember token by email. await this.ssoLoginService.setSsoEmail(email); + // Finally, we redirect to the SSO login page. This will be handled by each client implementation of this service. + await this.redirectToSso(email, state, codeChallenge); + } + + /** + * No-op implementation of redirectToSso + */ + protected async redirectToSso( + email: string, + state: string, + codeChallenge: string, + ): Promise { + return; + } + + /** + * No-op implementation of showBackButton + */ + showBackButton(showBackButton: boolean): void { + return; + } + + /** + * Sets the state required for verifying SSO login after completion + */ + private async setSsoPreLoginState(): Promise<[string, string]> { // Generate SSO params const passwordOptions: any = { type: "password", @@ -50,8 +80,8 @@ export class DefaultLoginComponentService implements LoginComponentService { let state = await this.passwordGenerationService.generatePassword(passwordOptions); - if (clientId === "browser") { - // Need to persist the clientId in the state for the extension + // For the browser extension, we persist the clientId on state so that it will be included after SSO in the callback + if (this.clientType === ClientType.Browser) { state += ":clientId=browser"; } @@ -63,35 +93,6 @@ export class DefaultLoginComponentService implements LoginComponentService { await this.ssoLoginService.setSsoState(state); await this.ssoLoginService.setCodeVerifier(codeVerifier); - // Build URL - const env = await firstValueFrom(this.environmentService.environment$); - const webVaultUrl = env.getWebVaultUrl(); - - const redirectUri = - clientId === "browser" - ? webVaultUrl + "/sso-connector.html" // Browser - : "bitwarden://sso-callback"; // Desktop - - // Launch browser window with URL - this.platformUtilsService.launchUri( - webVaultUrl + - "/#/sso?clientId=" + - clientId + - "&redirectUri=" + - encodeURIComponent(redirectUri) + - "&state=" + - state + - "&codeChallenge=" + - codeChallenge + - "&email=" + - encodeURIComponent(email), - ); - } - - /** - * No-op implementation of showBackButton - */ - showBackButton(showBackButton: boolean): void { - return; + return [state, codeChallenge]; } } diff --git a/libs/auth/src/angular/login/login-component.service.ts b/libs/auth/src/angular/login/login-component.service.ts index 8ca857cef59..796a01c71c3 100644 --- a/libs/auth/src/angular/login/login-component.service.ts +++ b/libs/auth/src/angular/login/login-component.service.ts @@ -23,7 +23,7 @@ export abstract class LoginComponentService { * Gets the organization policies if there is an organization invite. * - Used by: Web */ - getOrgPolicies: () => Promise; + getOrgPoliciesFromOrgInvite?: () => Promise; /** * Indicates whether login with passkey is supported on the given client @@ -31,10 +31,9 @@ export abstract class LoginComponentService { isLoginWithPasskeySupported: () => boolean; /** - * Launches the SSO flow in a new browser window. - * - Used by: Browser, Desktop + * Redirects the user to the SSO login page, either via route or in a new browser window. */ - launchSsoBrowserWindow: (email: string, clientId: "browser" | "desktop") => Promise; + redirectToSsoLogin: (email: string) => Promise; /** * Shows the back button. diff --git a/libs/auth/src/angular/login/login-secondary-content.component.ts b/libs/auth/src/angular/login/login-secondary-content.component.ts index dbc9535e67a..b608542b375 100644 --- a/libs/auth/src/angular/login/login-secondary-content.component.ts +++ b/libs/auth/src/angular/login/login-secondary-content.component.ts @@ -3,7 +3,6 @@ import { Component, inject } from "@angular/core"; import { RouterModule } from "@angular/router"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { RegisterRouteService } from "@bitwarden/auth/common"; import { DefaultServerSettingsService } from "@bitwarden/common/platform/services/default-server-settings.service"; import { LinkModule } from "@bitwarden/components"; @@ -13,16 +12,12 @@ import { LinkModule } from "@bitwarden/components"; template: ` `, }) export class LoginSecondaryContentComponent { - registerRouteService = inject(RegisterRouteService); serverSettingsService = inject(DefaultServerSettingsService); - // TODO: remove when email verification flag is removed - protected registerRoute$ = this.registerRouteService.registerRoute$(); - protected isUserRegistrationDisabled$ = this.serverSettingsService.isUserRegistrationDisabled$; } diff --git a/libs/auth/src/angular/login/login.component.html b/libs/auth/src/angular/login/login.component.html index c7837db74f2..b04a54da425 100644 --- a/libs/auth/src/angular/login/login.component.html +++ b/libs/auth/src/angular/login/login.component.html @@ -11,7 +11,7 @@ -->
    -
    +
    {{ "emailAddress" | i18n }} @@ -61,7 +61,7 @@
    -
    +
    {{ "masterPass" | i18n }} diff --git a/libs/auth/src/angular/login/login.component.ts b/libs/auth/src/angular/login/login.component.ts index 40f85e6d75c..cc38ec5dfb3 100644 --- a/libs/auth/src/angular/login/login.component.ts +++ b/libs/auth/src/angular/login/login.component.ts @@ -2,7 +2,7 @@ import { CommonModule } from "@angular/common"; import { Component, ElementRef, NgZone, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from "@angular/forms"; import { ActivatedRoute, Router, RouterModule } from "@angular/router"; -import { firstValueFrom, Subject, take, takeUntil, tap } from "rxjs"; +import { firstValueFrom, Subject, take, takeUntil } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { @@ -12,17 +12,16 @@ import { PasswordLoginCredentials, } from "@bitwarden/auth/common"; import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { PolicyData } from "@bitwarden/common/admin-console/models/data/policy.data"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { ClientType, HttpStatusCode } from "@bitwarden/common/enums"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -30,6 +29,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; +import { UserId } from "@bitwarden/common/types/guid"; import { AsyncActionsModule, ButtonModule, @@ -43,7 +43,7 @@ import { import { AnonLayoutWrapperDataService } from "../anon-layout/anon-layout-wrapper-data.service"; import { VaultIcon, WaveIcon } from "../icons"; -import { LoginComponentService } from "./login-component.service"; +import { LoginComponentService, PasswordPolicies } from "./login-component.service"; const BroadcasterSubscriptionId = "LoginComponent"; @@ -72,7 +72,6 @@ export class LoginComponent implements OnInit, OnDestroy { @ViewChild("masterPasswordInputRef") masterPasswordInputRef: ElementRef | undefined; private destroy$ = new Subject(); - private enforcedMasterPasswordOptions: MasterPasswordPolicyOptions | undefined = undefined; readonly Icons = { WaveIcon, VaultIcon }; clientType: ClientType; @@ -97,11 +96,6 @@ export class LoginComponent implements OnInit, OnDestroy { return this.formGroup.controls.email; } - // Web properties - enforcedPasswordPolicyOptions: MasterPasswordPolicyOptions | undefined; - policies: Policy[] | undefined; - showResetPasswordAutoEnrollWarning = false; - // Desktop properties deferFocus: boolean | null = null; @@ -125,7 +119,6 @@ export class LoginComponent implements OnInit, OnDestroy { private toastService: ToastService, private logService: LogService, private validationService: ValidationService, - private configService: ConfigService, private loginSuccessHandlerService: LoginSuccessHandlerService, ) { this.clientType = this.platformUtilsService.getClientType(); @@ -135,9 +128,6 @@ export class LoginComponent implements OnInit, OnDestroy { // Add popstate listener to listen for browser back button clicks window.addEventListener("popstate", this.handlePopState); - // TODO: remove this when the UnauthenticatedExtensionUIRefresh feature flag is removed. - this.listenForUnauthUiRefreshFlagChanges(); - await this.defaultOnInit(); if (this.clientType === ClientType.Desktop) { @@ -158,29 +148,6 @@ export class LoginComponent implements OnInit, OnDestroy { this.destroy$.complete(); } - private listenForUnauthUiRefreshFlagChanges() { - this.configService - .getFeatureFlag$(FeatureFlag.UnauthenticatedExtensionUIRefresh) - .pipe( - tap(async (flag) => { - // If the flag is turned OFF, we must force a reload to ensure the correct UI is shown - if (!flag) { - const uniqueQueryParams = { - ...this.activatedRoute.queryParams, - // adding a unique timestamp to the query params to force a reload - t: new Date().getTime().toString(), // Adding a unique timestamp as a query parameter - }; - - await this.router.navigate(["/"], { - queryParams: uniqueQueryParams, - }); - } - }), - takeUntil(this.destroy$), - ) - .subscribe(); - } - submit = async (): Promise => { if (this.clientType === ClientType.Desktop) { if (this.loginUiState !== LoginUiState.MASTER_PASSWORD_ENTRY) { @@ -275,18 +242,45 @@ export class LoginComponent implements OnInit, OnDestroy { return; } - await this.loginSuccessHandlerService.run(authResult.userId); + // Redirect to device verification if this is an unknown device + if (authResult.requiresDeviceVerification) { + await this.router.navigate(["device-verification"]); + return; + } + // User logged in successfully so execute side effects + await this.loginSuccessHandlerService.run(authResult.userId); + this.loginEmailService.clearValues(); + + // Determine where to send the user next if (authResult.forcePasswordReset != ForceSetPasswordReason.None) { - this.loginEmailService.clearValues(); await this.router.navigate(["update-temp-password"]); return; } - // If none of the above cases are true, proceed with login... - await this.evaluatePassword(); + // TODO: PM-18269 - evaluate if we can combine this with the + // password evaluation done in the password login strategy. + // If there's an existing org invite, use it to get the org's password policies + // so we can evaluate the MP against the org policies + if (this.loginComponentService.getOrgPoliciesFromOrgInvite) { + const orgPolicies: PasswordPolicies | null = + await this.loginComponentService.getOrgPoliciesFromOrgInvite(); - this.loginEmailService.clearValues(); + if (orgPolicies) { + // Since we have retrieved the policies, we can go ahead and set them into state for future use + // e.g., the update-password page currently only references state for policy data and + // doesn't fallback to pulling them from the server like it should if they are null. + await this.setPoliciesIntoState(authResult.userId, orgPolicies.policies); + + const isPasswordChangeRequired = await this.isPasswordChangeRequiredByOrgPolicy( + orgPolicies.enforcedPasswordPolicyOptions, + ); + if (isPasswordChangeRequired) { + await this.router.navigate(["update-password"]); + return; + } + } + } if (this.clientType === ClientType.Browser) { await this.router.navigate(["/tabs/vault"]); @@ -295,63 +289,51 @@ export class LoginComponent implements OnInit, OnDestroy { } } - protected async launchSsoBrowserWindow(clientId: "browser" | "desktop"): Promise { - const email = this.emailFormControl.value; - if (!email) { - this.logService.error("Email is required for SSO login"); - return; - } - await this.loginComponentService.launchSsoBrowserWindow(email, clientId); - } - - protected async evaluatePassword(): Promise { + /** + * Checks if the master password meets the enforced policy requirements + * and if the user is required to change their password. + */ + private async isPasswordChangeRequiredByOrgPolicy( + enforcedPasswordPolicyOptions: MasterPasswordPolicyOptions, + ): Promise { try { - // If we do not have any saved policies, attempt to load them from the service - if (this.enforcedMasterPasswordOptions == undefined) { - this.enforcedMasterPasswordOptions = await firstValueFrom( - this.policyService.masterPasswordPolicyOptions$(), - ); + if (enforcedPasswordPolicyOptions == undefined) { + return false; } - if (this.requirePasswordChange()) { - await this.router.navigate(["update-password"]); - return; + // Note: we deliberately do not check enforcedPasswordPolicyOptions.enforceOnLogin + // as existing users who are logging in after getting an org invite should + // always be forced to set a password that meets the org's policy. + // Org Invite -> Registration also works this way for new BW users as well. + + const masterPassword = this.formGroup.controls.masterPassword.value; + + // Return false if masterPassword is null/undefined since this is only evaluated after successful login + if (!masterPassword) { + return false; } + + const passwordStrength = this.passwordStrengthService.getPasswordStrength( + masterPassword, + this.formGroup.value.email ?? undefined, + )?.score; + + return !this.policyService.evaluateMasterPassword( + passwordStrength, + masterPassword, + enforcedPasswordPolicyOptions, + ); } catch (e) { // Do not prevent unlock if there is an error evaluating policies this.logService.error(e); + return false; } } - /** - * Checks if the master password meets the enforced policy requirements - * If not, returns false - */ - private requirePasswordChange(): boolean { - if ( - this.enforcedMasterPasswordOptions == undefined || - !this.enforcedMasterPasswordOptions.enforceOnLogin - ) { - return false; - } - - const masterPassword = this.formGroup.controls.masterPassword.value; - - // Return false if masterPassword is null/undefined since this is only evaluated after successful login - if (!masterPassword) { - return false; - } - - const passwordStrength = this.passwordStrengthService.getPasswordStrength( - masterPassword, - this.formGroup.value.email ?? undefined, - )?.score; - - return !this.policyService.evaluateMasterPassword( - passwordStrength, - masterPassword, - this.enforcedMasterPasswordOptions, - ); + private async setPoliciesIntoState(userId: UserId, policies: Policy[]): Promise { + const policiesData: { [id: string]: PolicyData } = {}; + policies.map((p) => (policiesData[p.id] = PolicyData.fromPolicy(p))); + await this.policyService.replace(policiesData, userId); } protected async startAuthRequestLogin(): Promise { @@ -484,6 +466,8 @@ export class LoginComponent implements OnInit, OnDestroy { try { const deviceIdentifier = await this.appIdService.getAppId(); this.isKnownDevice = await this.devicesApiService.getKnownDevice(email, deviceIdentifier); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.isKnownDevice = false; } @@ -520,12 +504,6 @@ export class LoginComponent implements OnInit, OnDestroy { } private async defaultOnInit(): Promise { - // If there's an existing org invite, use it to get the password policies - const orgPolicies = await this.loginComponentService.getOrgPolicies(); - - this.policies = orgPolicies?.policies; - this.showResetPasswordAutoEnrollWarning = orgPolicies?.isPolicyAndAutoEnrollEnabled ?? false; - let paramEmailIsSet = false; const params = await firstValueFrom(this.activatedRoute.queryParams); @@ -620,26 +598,26 @@ export class LoginComponent implements OnInit, OnDestroy { /** * Handle the SSO button click. - * @param event - The event object. */ async handleSsoClick() { - const isEmailValid = await this.validateEmail(); + const email = this.formGroup.value.email; + // Make sure the email is valid + const isEmailValid = await this.validateEmail(); if (!isEmailValid) { return; } - await this.saveEmailSettings(); - - if (this.clientType === ClientType.Web) { - await this.router.navigate(["/sso"], { - queryParams: { email: this.formGroup.value.email }, - }); + // Make sure the email is not empty, for type safety + if (!email) { + this.logService.error("Email is required for SSO"); return; } - await this.launchSsoBrowserWindow( - this.clientType === ClientType.Browser ? "browser" : "desktop", - ); + // Save the email configuration for the login component + await this.saveEmailSettings(); + + // Send the user to SSO, either through routing or through redirecting to the web app + await this.loginComponentService.redirectToSsoLogin(email); } } diff --git a/libs/auth/src/angular/new-device-verification/new-device-verification.component.html b/libs/auth/src/angular/new-device-verification/new-device-verification.component.html new file mode 100644 index 00000000000..e731f3afcb6 --- /dev/null +++ b/libs/auth/src/angular/new-device-verification/new-device-verification.component.html @@ -0,0 +1,37 @@ + + + {{ "verificationCode" | i18n }} + + + + + +
    + +
    + diff --git a/libs/auth/src/angular/new-device-verification/new-device-verification.component.ts b/libs/auth/src/angular/new-device-verification/new-device-verification.component.ts new file mode 100644 index 00000000000..57583eb24d2 --- /dev/null +++ b/libs/auth/src/angular/new-device-verification/new-device-verification.component.ts @@ -0,0 +1,167 @@ +import { CommonModule } from "@angular/common"; +import { Component, OnDestroy, OnInit } from "@angular/core"; +import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms"; +import { Router } from "@angular/router"; +import { Subject, takeUntil } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; +import { + AsyncActionsModule, + ButtonModule, + FormFieldModule, + IconButtonModule, + LinkModule, +} from "@bitwarden/components"; + +import { LoginEmailServiceAbstraction } from "../../common/abstractions/login-email.service"; +import { LoginStrategyServiceAbstraction } from "../../common/abstractions/login-strategy.service"; + +/** + * Component for verifying a new device via a one-time password (OTP). + */ +@Component({ + standalone: true, + selector: "app-new-device-verification", + templateUrl: "./new-device-verification.component.html", + imports: [ + CommonModule, + ReactiveFormsModule, + AsyncActionsModule, + JslibModule, + ButtonModule, + FormFieldModule, + IconButtonModule, + LinkModule, + ], +}) +export class NewDeviceVerificationComponent implements OnInit, OnDestroy { + formGroup = this.formBuilder.group({ + code: [ + "", + { + validators: [Validators.required], + updateOn: "change", + }, + ], + }); + + protected disableRequestOTP = false; + private destroy$ = new Subject(); + protected authenticationSessionTimeoutRoute = "/authentication-timeout"; + + constructor( + private router: Router, + private formBuilder: FormBuilder, + private apiService: ApiService, + private loginStrategyService: LoginStrategyServiceAbstraction, + private logService: LogService, + private i18nService: I18nService, + private syncService: SyncService, + private loginEmailService: LoginEmailServiceAbstraction, + ) {} + + async ngOnInit() { + // Redirect to timeout route if session expires + this.loginStrategyService.authenticationSessionTimeout$ + .pipe(takeUntil(this.destroy$)) + .subscribe((expired) => { + if (!expired) { + return; + } + + try { + void this.router.navigate([this.authenticationSessionTimeoutRoute]); + } catch (err) { + this.logService.error( + `Failed to navigate to ${this.authenticationSessionTimeoutRoute} route`, + err, + ); + } + }); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + /** + * Resends the OTP for device verification. + */ + async resendOTP() { + this.disableRequestOTP = true; + try { + const email = await this.loginStrategyService.getEmail(); + const masterPasswordHash = await this.loginStrategyService.getMasterPasswordHash(); + + if (!email || !masterPasswordHash) { + throw new Error("Missing email or master password hash"); + } + + await this.apiService.send( + "POST", + "/accounts/resend-new-device-otp", + { + email: email, + masterPasswordHash: masterPasswordHash, + }, + false, + false, + ); + } catch (e) { + this.logService.error(e); + } finally { + this.disableRequestOTP = false; + } + } + + /** + * Submits the OTP for device verification. + */ + submit = async (): Promise => { + const codeControl = this.formGroup.get("code"); + if (!codeControl || !codeControl.value) { + return; + } + + try { + const authResult = await this.loginStrategyService.logInNewDeviceVerification( + codeControl.value, + ); + + if (authResult.requiresTwoFactor) { + await this.router.navigate(["/2fa"]); + return; + } + + if (authResult.forcePasswordReset) { + await this.router.navigate(["/update-temp-password"]); + return; + } + + this.loginEmailService.clearValues(); + + await this.syncService.fullSync(true); + + // If verification succeeds, navigate to vault + await this.router.navigate(["/vault"]); + } catch (e) { + this.logService.error(e); + let errorMessage = + ((e as any)?.response?.error_description as string) ?? this.i18nService.t("errorOccurred"); + + if (errorMessage.includes("Invalid New Device OTP")) { + errorMessage = this.i18nService.t("invalidVerificationCode"); + } + + codeControl.setErrors({ serverError: { message: errorMessage } }); + // For enter key press scenarios, we have to manually mark the control as touched + // to get the error message to display + codeControl.markAsTouched(); + } + }; +} diff --git a/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts b/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts index 31b3f7db92a..c419e1f427f 100644 --- a/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts +++ b/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts @@ -16,7 +16,11 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { ToastService } from "@bitwarden/components"; -import { LoginStrategyServiceAbstraction, PasswordLoginCredentials } from "../../../common"; +import { + LoginStrategyServiceAbstraction, + LoginSuccessHandlerService, + PasswordLoginCredentials, +} from "../../../common"; import { AnonLayoutWrapperDataService } from "../../anon-layout/anon-layout-wrapper-data.service"; import { InputPasswordComponent } from "../../input-password/input-password.component"; import { PasswordInputResult } from "../../input-password/password-input-result"; @@ -68,6 +72,7 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy { private loginStrategyService: LoginStrategyServiceAbstraction, private logService: LogService, private anonLayoutWrapperDataService: AnonLayoutWrapperDataService, + private loginSuccessHandlerService: LoginSuccessHandlerService, ) {} async ngOnInit() { @@ -189,6 +194,8 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy { message: this.i18nService.t("youHaveBeenLoggedIn"), }); + await this.loginSuccessHandlerService.run(authenticationResult.userId); + await this.router.navigate(["/vault"]); } catch (e) { // If login errors, redirect to login page per product. Don't show error diff --git a/libs/auth/src/angular/registration/registration-start/registration-start.stories.ts b/libs/auth/src/angular/registration/registration-start/registration-start.stories.ts index fa3ad2ae2b9..6047cc3d27a 100644 --- a/libs/auth/src/angular/registration/registration-start/registration-start.stories.ts +++ b/libs/auth/src/angular/registration/registration-start/registration-start.stories.ts @@ -28,7 +28,8 @@ import { } from "@bitwarden/components"; // FIXME: remove `/apps` import from `/libs` -// eslint-disable-next-line import/no-restricted-paths +// FIXME: remove `src` and fix import +// eslint-disable-next-line import/no-restricted-paths, no-restricted-imports import { PreloadedEnglishI18nModule } from "../../../../../../apps/web/src/app/core/tests"; import { LoginEmailService } from "../../../common"; import { AnonLayoutWrapperDataService } from "../../anon-layout/anon-layout-wrapper-data.service"; diff --git a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts index 7a2b334eb42..bd62092a4b6 100644 --- a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts +++ b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts @@ -9,10 +9,11 @@ import { import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationKeysResponse } from "@bitwarden/common/admin-console/models/response/organization-keys.response"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; +import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { KeysRequest } from "@bitwarden/common/models/request/keys.request"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; @@ -31,6 +32,7 @@ describe("DefaultSetPasswordJitService", () => { let sut: DefaultSetPasswordJitService; let apiService: MockProxy; + let masterPasswordApiService: MockProxy; let keyService: MockProxy; let encryptService: MockProxy; let i18nService: MockProxy; @@ -42,6 +44,7 @@ describe("DefaultSetPasswordJitService", () => { beforeEach(() => { apiService = mock(); + masterPasswordApiService = mock(); keyService = mock(); encryptService = mock(); i18nService = mock(); @@ -53,6 +56,7 @@ describe("DefaultSetPasswordJitService", () => { sut = new DefaultSetPasswordJitService( apiService, + masterPasswordApiService, keyService, encryptService, i18nService, @@ -148,7 +152,7 @@ describe("DefaultSetPasswordJitService", () => { keyService.makeKeyPair.mockResolvedValue(keyPair); - apiService.setPassword.mockResolvedValue(undefined); + masterPasswordApiService.setPassword.mockResolvedValue(undefined); masterPasswordService.setForceSetPasswordReason.mockResolvedValue(undefined); userDecryptionOptionsSubject.next(new UserDecryptionOptions({ hasMasterPassword: true })); @@ -185,7 +189,7 @@ describe("DefaultSetPasswordJitService", () => { await sut.setPassword(credentials); // Assert - expect(apiService.setPassword).toHaveBeenCalledWith(setPasswordRequest); + expect(masterPasswordApiService.setPassword).toHaveBeenCalledWith(setPasswordRequest); }); it("should set password successfully (given no user key)", async () => { @@ -196,7 +200,7 @@ describe("DefaultSetPasswordJitService", () => { await sut.setPassword(credentials); // Assert - expect(apiService.setPassword).toHaveBeenCalledWith(setPasswordRequest); + expect(masterPasswordApiService.setPassword).toHaveBeenCalledWith(setPasswordRequest); }); it("should handle reset password auto enroll", async () => { @@ -210,7 +214,7 @@ describe("DefaultSetPasswordJitService", () => { await sut.setPassword(credentials); // Assert - expect(apiService.setPassword).toHaveBeenCalledWith(setPasswordRequest); + expect(masterPasswordApiService.setPassword).toHaveBeenCalledWith(setPasswordRequest); expect(organizationApiService.getKeys).toHaveBeenCalledWith(orgId); expect(encryptService.rsaEncrypt).toHaveBeenCalledWith(userKey.key, orgPublicKey); expect( diff --git a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts index 8b5c69806db..22cbd46eaa2 100644 --- a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts +++ b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts @@ -9,11 +9,12 @@ import { import { InternalUserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; +import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { KeysRequest } from "@bitwarden/common/models/request/keys.request"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; @@ -29,6 +30,7 @@ import { export class DefaultSetPasswordJitService implements SetPasswordJitService { constructor( protected apiService: ApiService, + protected masterPasswordApiService: MasterPasswordApiService, protected keyService: KeyService, protected encryptService: EncryptService, protected i18nService: I18nService, @@ -77,7 +79,7 @@ export class DefaultSetPasswordJitService implements SetPasswordJitService { kdfConfig.iterations, ); - await this.apiService.setPassword(request); + await this.masterPasswordApiService.setPassword(request); // Clear force set password reason to allow navigation back to vault. await this.masterPasswordService.setForceSetPasswordReason(ForceSetPasswordReason.None, userId); diff --git a/libs/auth/src/angular/set-password-jit/set-password-jit.component.ts b/libs/auth/src/angular/set-password-jit/set-password-jit.component.ts index 0557227938f..b54529f6a2c 100644 --- a/libs/auth/src/angular/set-password-jit/set-password-jit.component.ts +++ b/libs/auth/src/angular/set-password-jit/set-password-jit.component.ts @@ -15,6 +15,8 @@ import { ValidationService } from "@bitwarden/common/platform/abstractions/valid import { UserId } from "@bitwarden/common/types/guid"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ToastService } from "../../../../components/src/toast"; import { InputPasswordComponent } from "../input-password/input-password.component"; import { PasswordInputResult } from "../input-password/password-input-result"; diff --git a/libs/auth/src/angular/sso/sso-component.service.ts b/libs/auth/src/angular/sso/sso-component.service.ts index b5712dfacc9..6b71b8f9903 100644 --- a/libs/auth/src/angular/sso/sso-component.service.ts +++ b/libs/auth/src/angular/sso/sso-component.service.ts @@ -1,6 +1,10 @@ import { ClientType } from "@bitwarden/common/enums"; -export type SsoClientType = ClientType.Web | ClientType.Browser | ClientType.Desktop; +export type SsoClientType = + | ClientType.Web + | ClientType.Browser + | ClientType.Desktop + | ClientType.Cli; /** * Abstract class for SSO component services. diff --git a/libs/auth/src/angular/sso/sso.component.ts b/libs/auth/src/angular/sso/sso.component.ts index 3bcc56ae4cd..5d3fd689dd6 100644 --- a/libs/auth/src/angular/sso/sso.component.ts +++ b/libs/auth/src/angular/sso/sso.component.ts @@ -19,13 +19,13 @@ import { OrgDomainApiServiceAbstraction } from "@bitwarden/common/admin-console/ import { OrganizationDomainSsoDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-domain/responses/organization-domain-sso-details.response"; import { VerifiedOrganizationDomainSsoDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { SsoPreValidateResponse } from "@bitwarden/common/auth/models/response/sso-pre-validate.response"; import { ClientType, HttpStatusCode } from "@bitwarden/common/enums"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -89,6 +89,7 @@ export class SsoComponent implements OnInit { protected state: string | undefined; protected codeChallenge: string | undefined; protected clientId: SsoClientType | undefined; + protected email: string | null | undefined; formPromise: Promise | undefined; initiateSsoFormPromise: Promise | undefined; @@ -129,38 +130,58 @@ export class SsoComponent implements OnInit { } } + /** + * Like several components in our app (e.g. our invite acceptance components), the SSO component is engaged both + * before and after the user authenticates. + * Flow 1: Initialize SSO state and redirect to IdP + * - We can get here several ways: + * - The user is on the web client and is routed here + * - The user is on a different client and is redirected by opening a new browser window, passing query params + * - A customer integration has been set up to direct users to the `/sso` route to initiate SSO with an identifier + * Flow 2: Handle callback from IdP and verify the state that was set pre-authentication + */ async ngOnInit() { const qParams: QueryParams = await firstValueFrom(this.route.queryParams); - // This if statement will pass on the second portion of the SSO flow + // SSO on web uses a service to provide the email via state that's set on login, + // but because we have clients that delegate SSO to web we have to accept the email in the query params as well. + // We also can't require the email, because it isn't provided in the CLI SSO flow. + this.email = qParams.email ?? (await this.ssoLoginService.getSsoEmail()); + + // Detect if we are on the second portion of the SSO flow, // where the user has already authenticated with the identity provider - if (this.hasCodeOrStateParams(qParams)) { - await this.handleCodeAndStateParams(qParams); - return; - } - - // This if statement will pass on the first portion of the SSO flow - if (this.hasRequiredSsoParams(qParams)) { - this.setRequiredSsoVariables(qParams); + if (this.userCompletedSsoAuthentication(qParams)) { + await this.handleTokenRequestForAuthenticatedUser(qParams); return; } + // Detect if we have landed here but only have an SSO identifier in the URL. + // This is used by integrations that want to "short-circuit" the login to send users + // directly to their IdP to simulate IdP-initiated SSO, so we submit automatically. if (qParams.identifier != null) { - // SSO Org Identifier in query params takes precedence over claimed domains this.identifierFormControl.setValue(qParams.identifier); this.loggingIn = true; await this.submit(); return; } - await this.initializeIdentifierFromEmailOrStorage(qParams); + // Detect if we are on the first portion of the SSO flow + // and have been sent here from another client with the info in query params. + // If so, we want to initialize the SSO flow with those values. + if (this.hasParametersFromOtherClientRedirect(qParams)) { + this.initializeFromRedirectFromOtherClient(qParams); + } + + // Try to determine the identifier using claimed domain or local state + // persisted from the user's last login attempt. + await this.initializeIdentifierFromEmailOrStorage(); } /** * Sets the required SSO variables from the query params * @param qParams - The query params */ - private setRequiredSsoVariables(qParams: QueryParams): void { + private initializeFromRedirectFromOtherClient(qParams: QueryParams): void { this.redirectUri = qParams.redirectUri ?? ""; this.state = qParams.state ?? ""; this.codeChallenge = qParams.codeChallenge ?? ""; @@ -178,15 +199,22 @@ export class SsoComponent implements OnInit { * @returns True if the value is a valid SSO client type, otherwise false */ private isValidSsoClientType(value: string): value is SsoClientType { - return [ClientType.Web, ClientType.Browser, ClientType.Desktop].includes(value as ClientType); + return [ClientType.Web, ClientType.Browser, ClientType.Desktop, ClientType.Cli].includes( + value as ClientType, + ); } /** - * Checks if the query params have the required SSO params + * Checks if the query params have the required SSO params to initiate SSO + * * The query params presented here are: + * - clientId: The client type (e.g. web, browser, desktop) + * - redirectUri: The URI to redirect to after authentication + * - state: The state to verify on the client after authentication + * - codeChallenge: The PKCE code challenge that is sent up when authenticating with the IdP * @param qParams - The query params * @returns True if the query params have the required SSO params, false otherwise */ - private hasRequiredSsoParams(qParams: QueryParams): boolean { + private hasParametersFromOtherClientRedirect(qParams: QueryParams): boolean { return ( qParams.clientId != null && qParams.redirectUri != null && @@ -196,12 +224,18 @@ export class SsoComponent implements OnInit { } /** - * Handles the code and state params + * Handles the case in which the user has completed SSO authentication, has a code + * and has been redirected back to the SSO component to exchange the code for a token. + * This will be on the client originating the SSO request, not always the web client, as that + * is where the state and verifier are stored. * @param qParams - The query params */ - private async handleCodeAndStateParams(qParams: QueryParams): Promise { + private async handleTokenRequestForAuthenticatedUser(qParams: QueryParams): Promise { + // We set these in state prior to starting SSO, so we can retrieve them here const codeVerifier = await this.ssoLoginService.getCodeVerifier(); - const state = await this.ssoLoginService.getSsoState(); + const stateFromPrelogin = await this.ssoLoginService.getSsoState(); + + // Reset the code verifier and state so we don't accidentally use them again await this.ssoLoginService.setCodeVerifier(""); await this.ssoLoginService.setSsoState(""); @@ -209,11 +243,13 @@ export class SsoComponent implements OnInit { this.redirectUri = qParams.redirectUri; } + // Verify that the state matches the state we set prior to starting SSO. + // If it does, we can proceed with exchanging the code for a token. if ( qParams.code != null && codeVerifier != null && - state != null && - this.checkState(state, qParams.state ?? "") + stateFromPrelogin != null && + this.verifyStateMatches(stateFromPrelogin, qParams.state ?? "") ) { const ssoOrganizationIdentifier = this.getOrgIdentifierFromState(qParams.state ?? ""); await this.logIn(qParams.code, codeVerifier, ssoOrganizationIdentifier); @@ -221,11 +257,12 @@ export class SsoComponent implements OnInit { } /** - * Checks if the query params have a code or state + * Checks if the query params have a code and state, indicating that we've completed SSO authentication + * and have been redirected back to the SSO component on the originating client to complete login. * @param qParams - The query params - * @returns True if the query params have a code or state, false otherwise + * @returns True if the query params have a code and state, false otherwise */ - private hasCodeOrStateParams(qParams: QueryParams): boolean { + private userCompletedSsoAuthentication(qParams: QueryParams): boolean { return qParams.code != null && qParams.state != null; } @@ -265,6 +302,11 @@ export class SsoComponent implements OnInit { } }; + /** + * Redirects the user to `/connect/authorize` on IdentityServer to begin SSO. + * @param returnUri - The URI to redirect to after authentication (used to link user to SSO) + * @param includeUserIdentifier - Whether to include the user identifier in the request (used to link user to SSO) + */ private async submitSso(returnUri?: string, includeUserIdentifier?: boolean) { if (this.identifier == null || this.identifier === "") { this.toastService.showToast({ @@ -307,6 +349,9 @@ export class SsoComponent implements OnInit { special: false, }; + // Initialize the challenge and state if they aren't passed in. If we're performing SSO initiated on a + // different client, they'll be passed in, as they will need to be verified on that client and not the web. + // If they're not passed in, then we need to set them here on the web client to be verified here after SSO. if (codeChallenge == null) { const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, "sha256"); @@ -316,15 +361,20 @@ export class SsoComponent implements OnInit { if (state == null) { state = await this.passwordGenerationService.generatePassword(passwordOptions); - if (returnUri) { - state += `_returnUri='${returnUri}'`; - } + } + + // If we have a returnUri, add it to the state parameter. This will be used after SSO + // is complete, on the sso-connector, in order to route the user somewhere other than the SSO component. + if (returnUri) { + state += `_returnUri='${returnUri}'`; } // Add Organization Identifier to state state += `_identifier=${this.identifier}`; - // Save state (regardless of new or existing) + // Save the pre-SSO state. + // We need to do this here as even if it was generated on the intiating client (e.g. browser, desktop), + // we need it on the web client to verify after the user authenticates with the identity provider and is redirected back. await this.ssoLoginService.setSsoState(state); const env = await firstValueFrom(this.environmentService.environment$); @@ -349,6 +399,8 @@ export class SsoComponent implements OnInit { "&ssoToken=" + encodeURIComponent(token ?? ""); + // If we're linking a user to SSO, we need to provide a user identifier that will be passed + // on to the SSO provider so that after SSO we can link the user to the SSO identity. if (includeUserIdentifier) { const userIdentifier = await this.apiService.getSsoUserIdentifier(); authorizeUrl += `&user_identifier=${encodeURIComponent(userIdentifier)}`; @@ -357,21 +409,26 @@ export class SsoComponent implements OnInit { return authorizeUrl; } + /** + * We are using the Auth Code + PKCE flow. + * We have received the code from IdentityServer, which we will now present with the code verifier to get a token. + */ private async logIn(code: string, codeVerifier: string, orgSsoIdentifier: string): Promise { this.loggingIn = true; try { - const email = await this.ssoLoginService.getSsoEmail(); + // The code verifier is used to ensure that the client presenting the code is the same one that initiated the authentication request. + // The redirect URI is also supplied on the request to the token endpoint, so the server can ensure it matches the original request + // for the code and prevent authorization code injection attacks. const redirectUri = this.redirectUri ?? ""; const credentials = new SsoLoginCredentials( code, codeVerifier, redirectUri, orgSsoIdentifier, - email, + this.email ?? undefined, ); this.formPromise = this.loginStrategyService.logIn(credentials); const authResult = await this.formPromise; - if (authResult.requiresTwoFactor) { return await this.handleTwoFactorRequired(orgSsoIdentifier); } @@ -384,14 +441,11 @@ export class SsoComponent implements OnInit { // - TDE login decryption options component // - Browser SSO on extension open // Note: you cannot set this in state before 2FA b/c there won't be an account in state. - await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier(orgSsoIdentifier); - // Users enrolled in admin acct recovery can be forced to set a new password after - // having the admin set a temp password for them (affects TDE & standard users) - if (authResult.forcePasswordReset == ForceSetPasswordReason.AdminForcePasswordReset) { - // Weak password is not a valid scenario here b/c we cannot have evaluated a MP yet - return await this.handleForcePasswordReset(orgSsoIdentifier); - } + await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier( + orgSsoIdentifier, + authResult.userId, + ); // must come after 2fa check since user decryption options aren't available if 2fa is required const userDecryptionOpts = await firstValueFrom( @@ -480,16 +534,7 @@ export class SsoComponent implements OnInit { } private async handleChangePasswordRequired(orgIdentifier: string) { - const emailVerification = await this.configService.getFeatureFlag( - FeatureFlag.EmailVerification, - ); - - let route = "set-password"; - if (emailVerification) { - route = "set-password-jit"; - } - - await this.router.navigate([route], { + await this.router.navigate(["set-password-jit"], { queryParams: { identifier: orgIdentifier, }, @@ -530,16 +575,22 @@ export class SsoComponent implements OnInit { return stateSplit.length > 1 ? stateSplit[1] : ""; } - private checkState(state: string, checkState: string): boolean { - if (state === null || state === undefined) { + /** + * Checks if the state matches the checkState + * @param originalStateValue - The state to check + * @param stateValueToCheck - The state to check against + * @returns True if the state matches the checkState, false otherwise + */ + private verifyStateMatches(originalStateValue: string, stateValueToCheck: string): boolean { + if (originalStateValue === null || originalStateValue === undefined) { return false; } - if (checkState === null || checkState === undefined) { + if (stateValueToCheck === null || stateValueToCheck === undefined) { return false; } - const stateSplit = state.split("_identifier="); - const checkStateSplit = checkState.split("_identifier="); + const stateSplit = originalStateValue.split("_identifier="); + const checkStateSplit = stateValueToCheck.split("_identifier="); return stateSplit[0] === checkStateSplit[0]; } @@ -547,17 +598,16 @@ export class SsoComponent implements OnInit { * Attempts to initialize the SSO identifier from email or storage. * Note: this flow is written for web but both browser and desktop * redirect here on SSO button click. - * @param qParams - The query params */ - private async initializeIdentifierFromEmailOrStorage(qParams: QueryParams): Promise { - // Check if email matches any claimed domains - if (qParams.email) { + private async initializeIdentifierFromEmailOrStorage(): Promise { + if (this.email) { // show loading spinner this.loggingIn = true; try { + // Check if email matches any claimed domains if (await this.configService.getFeatureFlag(FeatureFlag.VerifiedSsoDomainEndpoint)) { const response: ListResponse = - await this.orgDomainApiService.getVerifiedOrgDomainsByEmail(qParams.email); + await this.orgDomainApiService.getVerifiedOrgDomainsByEmail(this.email); if (response.data.length > 0) { this.identifierFormControl.setValue(response.data[0].organizationIdentifier); @@ -566,7 +616,7 @@ export class SsoComponent implements OnInit { } } else { const response: OrganizationDomainSsoDetailsResponse = - await this.orgDomainApiService.getClaimedOrgDomainByEmail(qParams.email); + await this.orgDomainApiService.getClaimedOrgDomainByEmail(this.email); if (response?.ssoAvailable && response?.verifiedDate) { this.identifierFormControl.setValue(response.organizationIdentifier); @@ -581,7 +631,8 @@ export class SsoComponent implements OnInit { this.loggingIn = false; } - // Fallback to state svc if domain is unclaimed + // If we don't find a claimed domain, check to see if we stored an identifier in state + // from their last attrempt to login via SSO. If so, we'll populate the field, but not submit. const storedIdentifier = await this.ssoLoginService.getOrganizationSsoIdentifier(); if (storedIdentifier != null) { this.identifierFormControl.setValue(storedIdentifier); diff --git a/libs/auth/src/angular/two-factor-auth/child-components/index.ts b/libs/auth/src/angular/two-factor-auth/child-components/index.ts new file mode 100644 index 00000000000..429da3f14b3 --- /dev/null +++ b/libs/auth/src/angular/two-factor-auth/child-components/index.ts @@ -0,0 +1,3 @@ +export * from "./two-factor-auth-email"; +export * from "./two-factor-auth-duo"; +export * from "./two-factor-auth-webauthn"; diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-authenticator.component.html b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-authenticator.component.html new file mode 100644 index 00000000000..af3a8569efa --- /dev/null +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-authenticator.component.html @@ -0,0 +1,6 @@ + + + {{ "verificationCode" | i18n }} + + + diff --git a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-authenticator.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-authenticator.component.ts similarity index 64% rename from libs/angular/src/auth/components/two-factor-auth/two-factor-auth-authenticator.component.ts rename to libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-authenticator.component.ts index bdf69f7420f..a4986d086b2 100644 --- a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-authenticator.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-authenticator.component.ts @@ -1,12 +1,9 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { DialogModule } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; -import { Component, EventEmitter, Output } from "@angular/core"; -import { ReactiveFormsModule, FormsModule } from "@angular/forms"; +import { Component, Input } from "@angular/core"; +import { ReactiveFormsModule, FormsModule, FormControl } from "@angular/forms"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { ButtonModule, LinkModule, @@ -31,9 +28,8 @@ import { AsyncActionsModule, FormsModule, ], - providers: [I18nPipe], + providers: [], }) export class TwoFactorAuthAuthenticatorComponent { - tokenValue: string; - @Output() token = new EventEmitter(); + @Input({ required: true }) tokenFormControl: FormControl | undefined = undefined; } diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/index.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/index.ts new file mode 100644 index 00000000000..c43325e0d0b --- /dev/null +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/index.ts @@ -0,0 +1 @@ +export * from "./two-factor-auth-duo-component.service"; diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo-component.service.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo-component.service.ts new file mode 100644 index 00000000000..73aac55135c --- /dev/null +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo-component.service.ts @@ -0,0 +1,32 @@ +import { Observable } from "rxjs"; + +export interface Duo2faResult { + code: string; + state: string; + /** + * The code and the state joined by a | character. + */ + token: string; +} + +/** + * A service which manages all the cross client logic for the duo 2FA component. + */ +export abstract class TwoFactorAuthDuoComponentService { + /** + * Retrieves the result of the duo two-factor authentication process. + * @returns {Observable} An observable that emits the result of the duo two-factor authentication process. + */ + abstract listenForDuo2faResult$(): Observable; + + /** + * Launches the client specific duo frameless 2FA flow. + */ + abstract launchDuoFrameless(duoFramelessUrl: string): Promise; + + /** + * Optionally launches the extension duo 2FA single action popout + * Only applies to the extension today. + */ + abstract openTwoFactorAuthDuoPopout?(): Promise; +} diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo.component.ts new file mode 100644 index 00000000000..80f84e872a0 --- /dev/null +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo.component.ts @@ -0,0 +1,96 @@ +import { DialogModule } from "@angular/cdk/dialog"; +import { CommonModule } from "@angular/common"; +import { Component, DestroyRef, EventEmitter, Input, OnInit, Output } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { ReactiveFormsModule, FormsModule } from "@angular/forms"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { + ButtonModule, + LinkModule, + TypographyModule, + FormFieldModule, + AsyncActionsModule, + ToastService, +} from "@bitwarden/components"; + +import { DuoLaunchAction } from "../../two-factor-auth-component.service"; + +import { + Duo2faResult, + TwoFactorAuthDuoComponentService, +} from "./two-factor-auth-duo-component.service"; + +@Component({ + standalone: true, + selector: "app-two-factor-auth-duo", + template: "", + imports: [ + CommonModule, + JslibModule, + DialogModule, + ButtonModule, + LinkModule, + TypographyModule, + ReactiveFormsModule, + FormFieldModule, + AsyncActionsModule, + FormsModule, + ], + providers: [], +}) +export class TwoFactorAuthDuoComponent implements OnInit { + @Output() tokenEmitter = new EventEmitter(); + @Input() providerData: any; + + duoFramelessUrl: string | undefined = undefined; + + constructor( + protected i18nService: I18nService, + protected platformUtilsService: PlatformUtilsService, + protected toastService: ToastService, + private twoFactorAuthDuoComponentService: TwoFactorAuthDuoComponentService, + private destroyRef: DestroyRef, + ) {} + + async ngOnInit(): Promise { + this.twoFactorAuthDuoComponentService + .listenForDuo2faResult$() + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((duo2faResult: Duo2faResult) => { + this.tokenEmitter.emit(duo2faResult.token); + }); + + // flow must be launched by user so they can choose to remember the device or not. + this.duoFramelessUrl = this.providerData.AuthUrl; + } + + // Called via parent two-factor-auth component. + async launchDuoFrameless(duoLaunchAction: DuoLaunchAction): Promise { + switch (duoLaunchAction) { + case DuoLaunchAction.DIRECT_LAUNCH: + await this.launchDuoFramelessDirectly(); + break; + case DuoLaunchAction.SINGLE_ACTION_POPOUT: + await this.twoFactorAuthDuoComponentService.openTwoFactorAuthDuoPopout?.(); + break; + default: + break; + } + } + + private async launchDuoFramelessDirectly(): Promise { + if (this.duoFramelessUrl === null || this.duoFramelessUrl === undefined) { + this.toastService.showToast({ + variant: "error", + title: "", + message: this.i18nService.t("duoHealthCheckResultsInNullAuthUrlError"), + }); + return; + } + + await this.twoFactorAuthDuoComponentService.launchDuoFrameless(this.duoFramelessUrl); + } +} diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/default-two-factor-auth-email-component.service.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/default-two-factor-auth-email-component.service.ts new file mode 100644 index 00000000000..caae13acc38 --- /dev/null +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/default-two-factor-auth-email-component.service.ts @@ -0,0 +1,6 @@ +import { TwoFactorAuthEmailComponentService } from "./two-factor-auth-email-component.service"; + +export class DefaultTwoFactorAuthEmailComponentService + implements TwoFactorAuthEmailComponentService { + // no default implementation +} diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/index.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/index.ts new file mode 100644 index 00000000000..91f11b0b7dd --- /dev/null +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/index.ts @@ -0,0 +1,2 @@ +export * from "./default-two-factor-auth-email-component.service"; +export * from "./two-factor-auth-email-component.service"; diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email-component.service.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email-component.service.ts new file mode 100644 index 00000000000..fa96b6b96c2 --- /dev/null +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email-component.service.ts @@ -0,0 +1,10 @@ +/** + * A service that manages all cross client functionality for the email 2FA component. + */ +export abstract class TwoFactorAuthEmailComponentService { + /** + * Optionally shows a warning to the user that they might need to popout the + * window to complete email 2FA. + */ + abstract openPopoutIfApprovedForEmail2fa?(): Promise; +} diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.html b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.html new file mode 100644 index 00000000000..41873c32ed0 --- /dev/null +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.html @@ -0,0 +1,17 @@ + + {{ "verificationCode" | i18n }} + + + + diff --git a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-email.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts similarity index 61% rename from libs/angular/src/auth/components/two-factor-auth/two-factor-auth-email.component.ts rename to libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts index 8f01403cdbb..1b6ed7e2bb4 100644 --- a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-email.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts @@ -1,12 +1,9 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { DialogModule } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; -import { Component, EventEmitter, OnInit, Output } from "@angular/core"; -import { ReactiveFormsModule, FormsModule } from "@angular/forms"; +import { Component, Input, OnInit } from "@angular/core"; +import { ReactiveFormsModule, FormsModule, FormControl } from "@angular/forms"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; @@ -25,6 +22,8 @@ import { ToastService, } from "@bitwarden/components"; +import { TwoFactorAuthEmailComponentService } from "./two-factor-auth-email-component.service"; + @Component({ standalone: true, selector: "app-two-factor-auth-email", @@ -41,13 +40,13 @@ import { AsyncActionsModule, FormsModule, ], - providers: [I18nPipe], + providers: [], }) export class TwoFactorAuthEmailComponent implements OnInit { - @Output() token = new EventEmitter(); + @Input({ required: true }) tokenFormControl: FormControl | undefined = undefined; - twoFactorEmail: string = null; - emailPromise: Promise; + twoFactorEmail: string | undefined = undefined; + emailPromise: Promise | undefined = undefined; tokenValue: string = ""; constructor( @@ -59,25 +58,41 @@ export class TwoFactorAuthEmailComponent implements OnInit { protected apiService: ApiService, protected appIdService: AppIdService, private toastService: ToastService, + private twoFactorAuthEmailComponentService: TwoFactorAuthEmailComponentService, ) {} async ngOnInit(): Promise { - const providerData = await this.twoFactorService.getProviders().then((providers) => { - return providers.get(TwoFactorProviderType.Email); - }); - this.twoFactorEmail = providerData.Email; + await this.twoFactorAuthEmailComponentService.openPopoutIfApprovedForEmail2fa?.(); - if ((await this.twoFactorService.getProviders()).size > 1) { + const providers = await this.twoFactorService.getProviders(); + + if (!providers) { + throw new Error("User has no 2FA Providers"); + } + + const email2faProviderData = providers.get(TwoFactorProviderType.Email); + + if (!email2faProviderData) { + throw new Error("Unable to retrieve email 2FA provider data"); + } + + this.twoFactorEmail = email2faProviderData.Email; + + if (providers.size > 1) { await this.sendEmail(false); } } async sendEmail(doToast: boolean) { - if (this.emailPromise != null) { + if (this.emailPromise !== undefined) { return; } - if ((await this.loginStrategyService.getEmail()) == null) { + // TODO: PM-17545 - consider building a method on the login strategy service to get a mostly + // initialized TwoFactorEmailRequest in 1 call instead of 5 like we do today. + const email = await this.loginStrategyService.getEmail(); + + if (email == null) { this.toastService.showToast({ variant: "error", title: this.i18nService.t("errorOccurred"), @@ -88,19 +103,20 @@ export class TwoFactorAuthEmailComponent implements OnInit { try { const request = new TwoFactorEmailRequest(); - request.email = await this.loginStrategyService.getEmail(); - request.masterPasswordHash = await this.loginStrategyService.getMasterPasswordHash(); + request.email = email; + + request.masterPasswordHash = (await this.loginStrategyService.getMasterPasswordHash()) ?? ""; request.ssoEmail2FaSessionToken = - await this.loginStrategyService.getSsoEmail2FaSessionToken(); + (await this.loginStrategyService.getSsoEmail2FaSessionToken()) ?? ""; request.deviceIdentifier = await this.appIdService.getAppId(); - request.authRequestAccessCode = await this.loginStrategyService.getAccessCode(); - request.authRequestId = await this.loginStrategyService.getAuthRequestId(); + request.authRequestAccessCode = (await this.loginStrategyService.getAccessCode()) ?? ""; + request.authRequestId = (await this.loginStrategyService.getAuthRequestId()) ?? ""; this.emailPromise = this.apiService.postTwoFactorEmail(request); await this.emailPromise; if (doToast) { this.toastService.showToast({ variant: "success", - title: null, + title: "", message: this.i18nService.t("verificationCodeEmailSent", this.twoFactorEmail), }); } @@ -108,6 +124,6 @@ export class TwoFactorAuthEmailComponent implements OnInit { this.logService.error(e); } - this.emailPromise = null; + this.emailPromise = undefined; } } diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/default-two-factor-auth-webauthn-component.service.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/default-two-factor-auth-webauthn-component.service.ts new file mode 100644 index 00000000000..3d3578c656e --- /dev/null +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/default-two-factor-auth-webauthn-component.service.ts @@ -0,0 +1,12 @@ +import { TwoFactorAuthWebAuthnComponentService } from "./two-factor-auth-webauthn-component.service"; + +export class DefaultTwoFactorAuthWebAuthnComponentService + implements TwoFactorAuthWebAuthnComponentService +{ + /** + * Default implementation is to not open in a new tab. + */ + shouldOpenWebAuthnInNewTab(): boolean { + return false; + } +} diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/index.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/index.ts new file mode 100644 index 00000000000..154566280c7 --- /dev/null +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/index.ts @@ -0,0 +1,2 @@ +export * from "./two-factor-auth-webauthn-component.service"; +export * from "./default-two-factor-auth-webauthn-component.service"; diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn-component.service.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn-component.service.ts new file mode 100644 index 00000000000..49842c53796 --- /dev/null +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn-component.service.ts @@ -0,0 +1,10 @@ +/** + * A service that manages all cross client functionality for the WebAuthn 2FA component. + */ +export abstract class TwoFactorAuthWebAuthnComponentService { + /** + * Determines if the WebAuthn 2FA should be opened in a new tab or can be completed in the current tab. + * In a browser extension context, we open WebAuthn in a new web client tab. + */ + abstract shouldOpenWebAuthnInNewTab(): boolean; +} diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.html b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.html new file mode 100644 index 00000000000..6f13b0a1fe2 --- /dev/null +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.html @@ -0,0 +1,24 @@ +
    +
    + +
    + + +
    + + + diff --git a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-webauthn.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts similarity index 60% rename from libs/angular/src/auth/components/two-factor-auth/two-factor-auth-webauthn.component.ts rename to libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts index ba3b645c68d..ae348c27a62 100644 --- a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-webauthn.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { DialogModule } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; import { Component, EventEmitter, Inject, OnDestroy, OnInit, Output } from "@angular/core"; @@ -8,14 +6,13 @@ import { ActivatedRoute } from "@angular/router"; import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { WebAuthnIFrame } from "@bitwarden/common/auth/webauthn-iframe"; -import { ClientType } from "@bitwarden/common/enums"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ButtonModule, @@ -26,6 +23,13 @@ import { ToastService, } from "@bitwarden/components"; +import { TwoFactorAuthWebAuthnComponentService } from "./two-factor-auth-webauthn-component.service"; + +export interface WebAuthnResult { + token: string; + remember?: boolean; +} + @Component({ standalone: true, selector: "app-two-factor-auth-webauthn", @@ -42,15 +46,16 @@ import { AsyncActionsModule, FormsModule, ], - providers: [I18nPipe], + providers: [], }) export class TwoFactorAuthWebAuthnComponent implements OnInit, OnDestroy { - @Output() token = new EventEmitter(); + @Output() webAuthnResultEmitter = new EventEmitter(); + @Output() webAuthnInNewTabEmitter = new EventEmitter(); webAuthnReady = false; webAuthnNewTab = false; webAuthnSupported = false; - webAuthn: WebAuthnIFrame = null; + webAuthnIframe: WebAuthnIFrame | undefined = undefined; constructor( protected i18nService: I18nService, @@ -60,35 +65,51 @@ export class TwoFactorAuthWebAuthnComponent implements OnInit, OnDestroy { protected twoFactorService: TwoFactorService, protected route: ActivatedRoute, private toastService: ToastService, + private twoFactorAuthWebAuthnComponentService: TwoFactorAuthWebAuthnComponentService, + private logService: LogService, ) { this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win); - - if (this.platformUtilsService.getClientType() == ClientType.Browser) { - // FIXME: Chromium 110 has broken WebAuthn support in extensions via an iframe - this.webAuthnNewTab = true; - } + this.webAuthnNewTab = this.twoFactorAuthWebAuthnComponentService.shouldOpenWebAuthnInNewTab(); } async ngOnInit(): Promise { - if (this.route.snapshot.paramMap.has("webAuthnResponse")) { - this.token.emit(this.route.snapshot.paramMap.get("webAuthnResponse")); + this.webAuthnInNewTabEmitter.emit(this.webAuthnNewTab); + + if (this.webAuthnNewTab && this.route.snapshot.paramMap.has("webAuthnResponse")) { + this.submitWebAuthnNewTabResponse(); + } else { + await this.buildWebAuthnIFrame(); } + } - this.cleanupWebAuthn(); + private submitWebAuthnNewTabResponse() { + const webAuthnNewTabResponse = this.route.snapshot.paramMap.get("webAuthnResponse"); + const remember = this.route.snapshot.paramMap.get("remember") === "true"; + if (webAuthnNewTabResponse != null) { + this.webAuthnResultEmitter.emit({ + token: webAuthnNewTabResponse, + remember, + }); + } + } + + private async buildWebAuthnIFrame() { if (this.win != null && this.webAuthnSupported) { const env = await firstValueFrom(this.environmentService.environment$); const webVaultUrl = env.getWebVaultUrl(); - this.webAuthn = new WebAuthnIFrame( + this.webAuthnIframe = new WebAuthnIFrame( this.win, webVaultUrl, this.webAuthnNewTab, this.platformUtilsService, this.i18nService, (token: string) => { - this.token.emit(token); + this.webAuthnResultEmitter.emit({ token }); }, (error: string) => { + this.logService.error("WebAuthn error: ", error); + this.toastService.showToast({ variant: "error", title: this.i18nService.t("errorOccurred"), @@ -111,25 +132,30 @@ export class TwoFactorAuthWebAuthnComponent implements OnInit, OnDestroy { } ngOnDestroy(): void { - this.cleanupWebAuthn(); + this.cleanupWebAuthnIframe(); } async authWebAuthn() { - const providerData = (await this.twoFactorService.getProviders()).get( - TwoFactorProviderType.WebAuthn, - ); + const providers = await this.twoFactorService.getProviders(); - if (!this.webAuthnSupported || this.webAuthn == null) { + if (providers == null) { + this.logService.error("No 2FA providers found. Unable to authenticate with WebAuthn."); return; } - this.webAuthn.init(providerData); + const providerData = providers?.get(TwoFactorProviderType.WebAuthn); + + if (!this.webAuthnSupported || this.webAuthnIframe == null) { + return; + } + + this.webAuthnIframe.init(providerData); } - private cleanupWebAuthn() { - if (this.webAuthn != null) { - this.webAuthn.stop(); - this.webAuthn.cleanup(); + private cleanupWebAuthnIframe() { + if (this.webAuthnIframe != null) { + this.webAuthnIframe.stop(); + this.webAuthnIframe.cleanup(); } } } diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-yubikey.component.html b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-yubikey.component.html new file mode 100644 index 00000000000..f0387bbcb52 --- /dev/null +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-yubikey.component.html @@ -0,0 +1,4 @@ + + {{ "verificationCode" | i18n }} + + diff --git a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-yubikey.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-yubikey.component.ts similarity index 69% rename from libs/angular/src/auth/components/two-factor-auth/two-factor-auth-yubikey.component.ts rename to libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-yubikey.component.ts index 71e8508f8ce..a1f4306b9a8 100644 --- a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-yubikey.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-yubikey.component.ts @@ -1,10 +1,9 @@ import { DialogModule } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; -import { Component, EventEmitter, Output } from "@angular/core"; -import { ReactiveFormsModule, FormsModule } from "@angular/forms"; +import { Component, Input } from "@angular/core"; +import { ReactiveFormsModule, FormsModule, FormControl } from "@angular/forms"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { ButtonModule, LinkModule, @@ -29,9 +28,8 @@ import { AsyncActionsModule, FormsModule, ], - providers: [I18nPipe], + providers: [], }) export class TwoFactorAuthYubikeyComponent { - tokenValue: string = ""; - @Output() token = new EventEmitter(); + @Input({ required: true }) tokenFormControl: FormControl | undefined = undefined; } diff --git a/libs/auth/src/angular/two-factor-auth/default-two-factor-auth-component.service.ts b/libs/auth/src/angular/two-factor-auth/default-two-factor-auth-component.service.ts new file mode 100644 index 00000000000..f68c1d34515 --- /dev/null +++ b/libs/auth/src/angular/two-factor-auth/default-two-factor-auth-component.service.ts @@ -0,0 +1,19 @@ +import { + DuoLaunchAction, + LegacyKeyMigrationAction, + TwoFactorAuthComponentService, +} from "./two-factor-auth-component.service"; + +export class DefaultTwoFactorAuthComponentService implements TwoFactorAuthComponentService { + shouldCheckForWebAuthnQueryParamResponse() { + return false; + } + + determineLegacyKeyMigrationAction() { + return LegacyKeyMigrationAction.PREVENT_LOGIN_AND_SHOW_REQUIRE_MIGRATION_WARNING; + } + + determineDuoLaunchAction(): DuoLaunchAction { + return DuoLaunchAction.DIRECT_LAUNCH; + } +} diff --git a/libs/auth/src/angular/two-factor-auth/index.ts b/libs/auth/src/angular/two-factor-auth/index.ts new file mode 100644 index 00000000000..c5dc7b1a59d --- /dev/null +++ b/libs/auth/src/angular/two-factor-auth/index.ts @@ -0,0 +1,6 @@ +export * from "./two-factor-auth-component.service"; +export * from "./default-two-factor-auth-component.service"; +export * from "./two-factor-auth.component"; +export * from "./two-factor-auth.guard"; + +export * from "./child-components"; diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth-component.service.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth-component.service.ts new file mode 100644 index 00000000000..2bb354a8cc3 --- /dev/null +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth-component.service.ts @@ -0,0 +1,66 @@ +import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; + +export enum LegacyKeyMigrationAction { + PREVENT_LOGIN_AND_SHOW_REQUIRE_MIGRATION_WARNING, + NAVIGATE_TO_MIGRATION_COMPONENT, +} + +export enum DuoLaunchAction { + DIRECT_LAUNCH, + SINGLE_ACTION_POPOUT, +} + +/** + * Manages all cross client functionality so we can have a single two factor auth component + * implementation for all clients. + */ +export abstract class TwoFactorAuthComponentService { + /** + * Determines if the client should check for a webauthn response on init. + * Currently, only the extension should check during component initialization. + */ + abstract shouldCheckForWebAuthnQueryParamResponse(): boolean; + + /** + * Extends the popup width if required. + * Some client specific situations require the popup to be wider than the default width. + */ + abstract extendPopupWidthIfRequired?( + selected2faProviderType: TwoFactorProviderType, + ): Promise; + + /** + * Removes the popup width extension. + */ + abstract removePopupWidthExtension?(): void; + + /** + * We used to use the user's master key to encrypt their data. We deprecated that approach + * and now use a user key. This method should be called if we detect that the user + * is still using the old master key encryption scheme (server sends down a flag to + * indicate this). This method then determines what action to take based on the client. + * + * We have two possible actions: + * 1. Prevent the user from logging in and show a warning that they need to migrate their key on the web client today. + * 2. Navigate the user to the key migration component on the web client. + */ + abstract determineLegacyKeyMigrationAction(): LegacyKeyMigrationAction; + + /** + * Optionally closes any single action popouts (extension only). + * @returns true if we are in a single action popout and it was closed, false otherwise. + */ + abstract closeSingleActionPopouts?(): Promise; + + /** + * Optionally refreshes any open windows (exempts current window). + * Only defined on the extension client for the goal of refreshing sidebars. + */ + abstract reloadOpenWindows?(): void; + + /** + * Determines the action to take when launching the Duo flow. + * The extension has to popout the flow, while other clients can launch it directly. + */ + abstract determineDuoLaunchAction(): DuoLaunchAction; +} diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.html b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.html new file mode 100644 index 00000000000..ec03944a954 --- /dev/null +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.html @@ -0,0 +1,108 @@ + +
    + +
    +
    + + +
    + + + + + + + + {{ "dontAskAgainOnThisDeviceFor30Days" | i18n }} + + + + + + +

    {{ "noTwoStepProviders" | i18n }}

    +

    {{ "noTwoStepProviders2" | i18n }}

    +
    + + +
    + + + + +

    {{ "or" | i18n }}

    + + + + +
    + +
    diff --git a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.spec.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts similarity index 62% rename from libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.spec.ts rename to libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts index 755813a677a..20e9aa73048 100644 --- a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.spec.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts @@ -1,10 +1,11 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component } from "@angular/core"; import { ComponentFixture, TestBed } from "@angular/core/testing"; import { ActivatedRoute, convertToParamMap, Router } from "@angular/router"; import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; -// eslint-disable-next-line no-restricted-imports import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; import { LoginStrategyServiceAbstraction, @@ -13,22 +14,20 @@ import { FakeTrustedDeviceUserDecryptionOption as TrustedDeviceUserDecryptionOption, FakeUserDecryptionOptions as UserDecryptionOptions, UserDecryptionOptionsServiceAbstraction, + LoginSuccessHandlerService, } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; +import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; -import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; +import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { - Environment, - EnvironmentService, -} from "@bitwarden/common/platform/abstractions/environment.service"; +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -37,22 +36,16 @@ import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/sp import { UserId } from "@bitwarden/common/types/guid"; import { DialogService, ToastService } from "@bitwarden/components"; +import { AnonLayoutWrapperDataService } from "../anon-layout/anon-layout-wrapper-data.service"; + +import { TwoFactorAuthComponentService } from "./two-factor-auth-component.service"; import { TwoFactorAuthComponent } from "./two-factor-auth.component"; -// test component that extends the TwoFactorAuthComponent @Component({}) class TestTwoFactorComponent extends TwoFactorAuthComponent {} -interface TwoFactorComponentProtected { - trustedDeviceEncRoute: string; - changePasswordRoute: string; - forcePasswordResetRoute: string; - successRoute: string; -} - -describe("TwoFactorComponent", () => { +describe("TwoFactorAuthComponent", () => { let component: TestTwoFactorComponent; - let _component: TwoFactorComponentProtected; let fixture: ComponentFixture; const userId = "userId" as UserId; @@ -64,7 +57,6 @@ describe("TwoFactorComponent", () => { let mockApiService: MockProxy; let mockPlatformUtilsService: MockProxy; let mockWin: MockProxy; - let mockEnvironmentService: MockProxy; let mockStateService: MockProxy; let mockLogService: MockProxy; let mockTwoFactorService: MockProxy; @@ -72,11 +64,14 @@ describe("TwoFactorComponent", () => { let mockLoginEmailService: MockProxy; let mockUserDecryptionOptionsService: MockProxy; let mockSsoLoginService: MockProxy; - let mockConfigService: MockProxy; let mockMasterPasswordService: FakeMasterPasswordService; let mockAccountService: FakeAccountService; let mockDialogService: MockProxy; let mockToastService: MockProxy; + let mockTwoFactorAuthCompService: MockProxy; + let anonLayoutWrapperDataService: MockProxy; + let mockEnvService: MockProxy; + let mockLoginSuccessHandlerService: MockProxy; let mockUserDecryptionOpts: { noMasterPassword: UserDecryptionOptions; @@ -98,10 +93,6 @@ describe("TwoFactorComponent", () => { mockApiService = mock(); mockPlatformUtilsService = mock(); mockWin = mock(); - const mockEnvironment = mock(); - mockEnvironment.getWebVaultUrl.mockReturnValue("http://example.com"); - mockEnvironmentService = mock(); - mockEnvironmentService.environment$ = new BehaviorSubject(mockEnvironment); mockStateService = mock(); mockLogService = mock(); @@ -110,11 +101,16 @@ describe("TwoFactorComponent", () => { mockLoginEmailService = mock(); mockUserDecryptionOptionsService = mock(); mockSsoLoginService = mock(); - mockConfigService = mock(); mockAccountService = mockAccountServiceWith(userId); mockMasterPasswordService = new FakeMasterPasswordService(); mockDialogService = mock(); mockToastService = mock(); + mockTwoFactorAuthCompService = mock(); + + mockEnvService = mock(); + mockLoginSuccessHandlerService = mock(); + + anonLayoutWrapperDataService = mock(); mockUserDecryptionOpts = { noMasterPassword: new UserDecryptionOptions({ @@ -159,7 +155,7 @@ describe("TwoFactorComponent", () => { }), }; - selectedUserDecryptionOptions = new BehaviorSubject(null); + selectedUserDecryptionOptions = new BehaviorSubject(undefined); mockUserDecryptionOptionsService.userDecryptionOptions$ = selectedUserDecryptionOptions; TestBed.configureTestingModule({ @@ -171,7 +167,6 @@ describe("TwoFactorComponent", () => { { provide: ApiService, useValue: mockApiService }, { provide: PlatformUtilsService, useValue: mockPlatformUtilsService }, { provide: WINDOW, useValue: mockWin }, - { provide: EnvironmentService, useValue: mockEnvironmentService }, { provide: StateService, useValue: mockStateService }, { provide: ActivatedRoute, @@ -191,17 +186,19 @@ describe("TwoFactorComponent", () => { useValue: mockUserDecryptionOptionsService, }, { provide: SsoLoginServiceAbstraction, useValue: mockSsoLoginService }, - { provide: ConfigService, useValue: mockConfigService }, { provide: InternalMasterPasswordServiceAbstraction, useValue: mockMasterPasswordService }, { provide: AccountService, useValue: mockAccountService }, { provide: DialogService, useValue: mockDialogService }, { provide: ToastService, useValue: mockToastService }, + { provide: TwoFactorAuthComponentService, useValue: mockTwoFactorAuthCompService }, + { provide: EnvironmentService, useValue: mockEnvService }, + { provide: AnonLayoutWrapperDataService, useValue: anonLayoutWrapperDataService }, + { provide: LoginSuccessHandlerService, useValue: mockLoginSuccessHandlerService }, ], }); fixture = TestBed.createComponent(TestTwoFactorComponent); component = fixture.componentInstance; - _component = component as any; }); afterEach(() => { @@ -217,27 +214,13 @@ describe("TwoFactorComponent", () => { const testChangePasswordOnSuccessfulLogin = () => { it("navigates to the component's defined change password route when user doesn't have a MP and key connector isn't enabled", async () => { // Act - await component.submit(); + await component.submit("testToken"); // Assert expect(mockRouter.navigate).toHaveBeenCalledTimes(1); - expect(mockRouter.navigate).toHaveBeenCalledWith([_component.changePasswordRoute], { + expect(mockRouter.navigate).toHaveBeenCalledWith(["set-password"], { queryParams: { - identifier: component.orgIdentifier, - }, - }); - }); - }; - - const testForceResetOnSuccessfulLogin = (reasonString: string) => { - it(`navigates to the component's defined forcePasswordResetRoute route when response.forcePasswordReset is ${reasonString}`, async () => { - // Act - await component.submit(); - - // expect(mockRouter.navigate).toHaveBeenCalledTimes(1); - expect(mockRouter.navigate).toHaveBeenCalledWith([_component.forcePasswordResetRoute], { - queryParams: { - identifier: component.orgIdentifier, + identifier: component.orgSsoIdentifier, }, }); }); @@ -247,14 +230,14 @@ describe("TwoFactorComponent", () => { describe("submit", () => { const token = "testToken"; const remember = false; - const captchaToken = "testCaptchaToken"; + const currentAuthTypeSubject = new BehaviorSubject( + AuthenticationType.Password, + ); beforeEach(() => { - component.token = token; - component.remember = remember; - component.captchaToken = captchaToken; - selectedUserDecryptionOptions.next(mockUserDecryptionOpts.withMasterPassword); + + mockLoginStrategyService.currentAuthType$ = currentAuthTypeSubject.asObservable(); }); it("calls authService.logInTwoFactor with correct parameters when form is submitted", async () => { @@ -262,50 +245,15 @@ describe("TwoFactorComponent", () => { mockLoginStrategyService.logInTwoFactor.mockResolvedValue(new AuthResult()); // Act - await component.submit(); + await component.submit(token, remember); // Assert expect(mockLoginStrategyService.logInTwoFactor).toHaveBeenCalledWith( new TokenTwoFactorRequest(component.selectedProviderType, token, remember), - captchaToken, + "", ); }); - it("should return when handleCaptchaRequired returns true", async () => { - // Arrange - const captchaSiteKey = "testCaptchaSiteKey"; - const authResult = new AuthResult(); - authResult.captchaSiteKey = captchaSiteKey; - - mockLoginStrategyService.logInTwoFactor.mockResolvedValue(authResult); - - // Note: the any casts are required b/c typescript cant recognize that - // handleCaptureRequired is a method on TwoFactorComponent b/c it is inherited - // from the CaptchaProtectedComponent - const handleCaptchaRequiredSpy = jest - .spyOn(component, "handleCaptchaRequired") - .mockReturnValue(true); - - // Act - const result = await component.submit(); - - // Assert - expect(handleCaptchaRequiredSpy).toHaveBeenCalled(); - expect(result).toBeUndefined(); - }); - - it("calls onSuccessfulLogin when defined", async () => { - // Arrange - component.onSuccessfulLogin = jest.fn().mockResolvedValue(undefined); - mockLoginStrategyService.logInTwoFactor.mockResolvedValue(new AuthResult()); - - // Act - await component.submit(); - - // Assert - expect(component.onSuccessfulLogin).toHaveBeenCalled(); - }); - it("calls loginEmailService.clearValues() when login is successful", async () => { // Arrange mockLoginStrategyService.logInTwoFactor.mockResolvedValue(new AuthResult()); @@ -313,7 +261,7 @@ describe("TwoFactorComponent", () => { const clearValuesSpy = jest.spyOn(mockLoginEmailService, "clearValues"); // Act - await component.submit(); + await component.submit(token, remember); // Assert expect(clearValuesSpy).toHaveBeenCalled(); @@ -339,60 +287,52 @@ describe("TwoFactorComponent", () => { mockUserDecryptionOpts.noMasterPasswordWithKeyConnector, ); - await component.submit(); + await component.submit(token, remember); - expect(mockRouter.navigate).not.toHaveBeenCalledWith([_component.changePasswordRoute], { + expect(mockRouter.navigate).not.toHaveBeenCalledWith(["set-password"], { queryParams: { - identifier: component.orgIdentifier, + identifier: component.orgSsoIdentifier, }, }); }); }); - describe("Force Master Password Reset scenarios", () => { - [ - ForceSetPasswordReason.AdminForcePasswordReset, - ForceSetPasswordReason.WeakMasterPassword, - ].forEach((forceResetPasswordReason) => { - const reasonString = ForceSetPasswordReason[forceResetPasswordReason]; + it("navigates to the component's defined success route (vault is default) when the login is successful", async () => { + mockLoginStrategyService.logInTwoFactor.mockResolvedValue(new AuthResult()); - beforeEach(() => { - // use standard user with MP because this test is not concerned with password reset. - selectedUserDecryptionOptions.next(mockUserDecryptionOpts.withMasterPassword); + // Act + await component.submit("testToken"); - const authResult = new AuthResult(); - authResult.forcePasswordReset = forceResetPasswordReason; - mockLoginStrategyService.logInTwoFactor.mockResolvedValue(authResult); - }); - - testForceResetOnSuccessfulLogin(reasonString); + // Assert + expect(mockRouter.navigate).toHaveBeenCalledTimes(1); + expect(mockRouter.navigate).toHaveBeenCalledWith(["vault"], { + queryParams: { + identifier: component.orgSsoIdentifier, + }, }); }); - it("calls onSuccessfulLoginNavigate when the callback is defined", async () => { - // Arrange - component.onSuccessfulLoginNavigate = jest.fn().mockResolvedValue(undefined); - mockLoginStrategyService.logInTwoFactor.mockResolvedValue(new AuthResult()); + it.each([ + [AuthenticationType.Sso, "lock"], + [AuthenticationType.UserApiKey, "lock"], + ])( + "navigates to the lock component when the authentication type is %s", + async (authType, expectedRoute) => { + mockLoginStrategyService.logInTwoFactor.mockResolvedValue(new AuthResult()); + currentAuthTypeSubject.next(authType); - // Act - await component.submit(); + // Act + await component.submit("testToken"); - // Assert - expect(component.onSuccessfulLoginNavigate).toHaveBeenCalled(); - }); - - it("navigates to the component's defined success route when the login is successful and onSuccessfulLoginNavigate is undefined", async () => { - mockLoginStrategyService.logInTwoFactor.mockResolvedValue(new AuthResult()); - - // Act - await component.submit(); - - // Assert - expect(component.onSuccessfulLoginNavigate).not.toBeDefined(); - - expect(mockRouter.navigate).toHaveBeenCalledTimes(1); - expect(mockRouter.navigate).toHaveBeenCalledWith([_component.successRoute], undefined); - }); + // Assert + expect(mockRouter.navigate).toHaveBeenCalledTimes(1); + expect(mockRouter.navigate).toHaveBeenCalledWith(["lock"], { + queryParams: { + identifier: component.orgSsoIdentifier, + }, + }); + }, + ); }); }); @@ -405,19 +345,8 @@ describe("TwoFactorComponent", () => { describe("submit", () => { const token = "testToken"; const remember = false; - const captchaToken = "testCaptchaToken"; - - beforeEach(() => { - component.token = token; - component.remember = remember; - component.captchaToken = captchaToken; - }); describe("Trusted Device Encryption scenarios", () => { - beforeEach(() => { - mockConfigService.getFeatureFlag.mockResolvedValue(true); - }); - describe("Given Trusted Device Encryption is enabled and user needs to set a master password", () => { beforeEach(() => { selectedUserDecryptionOptions.next( @@ -425,12 +354,13 @@ describe("TwoFactorComponent", () => { ); const authResult = new AuthResult(); + authResult.userId = userId; mockLoginStrategyService.logInTwoFactor.mockResolvedValue(authResult); }); - it("navigates to the component's defined trusted device encryption route and sets correct flag when user doesn't have a MP and key connector isn't enabled", async () => { + it("navigates to the login-initiated route and sets correct flag when user doesn't have a MP and key connector isn't enabled", async () => { // Act - await component.submit(); + await component.submit(token, remember); // Assert expect(mockMasterPasswordService.mock.setForceSetPasswordReason).toHaveBeenCalledWith( @@ -439,36 +369,11 @@ describe("TwoFactorComponent", () => { ); expect(mockRouter.navigate).toHaveBeenCalledTimes(1); - expect(mockRouter.navigate).toHaveBeenCalledWith( - [_component.trustedDeviceEncRoute], - undefined, - ); + expect(mockRouter.navigate).toHaveBeenCalledWith(["login-initiated"]); }); }); - describe("Given Trusted Device Encryption is enabled, user doesn't need to set a MP, and forcePasswordReset is required", () => { - [ - ForceSetPasswordReason.AdminForcePasswordReset, - ForceSetPasswordReason.WeakMasterPassword, - ].forEach((forceResetPasswordReason) => { - const reasonString = ForceSetPasswordReason[forceResetPasswordReason]; - - beforeEach(() => { - // use standard user with MP because this test is not concerned with password reset. - selectedUserDecryptionOptions.next( - mockUserDecryptionOpts.withMasterPasswordAndTrustedDevice, - ); - - const authResult = new AuthResult(); - authResult.forcePasswordReset = forceResetPasswordReason; - mockLoginStrategyService.logInTwoFactor.mockResolvedValue(authResult); - }); - - testForceResetOnSuccessfulLogin(reasonString); - }); - }); - - describe("Given Trusted Device Encryption is enabled, user doesn't need to set a MP, and forcePasswordReset is not required", () => { + describe("Given Trusted Device Encryption is enabled and user doesn't need to set a MP", () => { let authResult; beforeEach(() => { selectedUserDecryptionOptions.next( @@ -476,27 +381,14 @@ describe("TwoFactorComponent", () => { ); authResult = new AuthResult(); - authResult.forcePasswordReset = ForceSetPasswordReason.None; mockLoginStrategyService.logInTwoFactor.mockResolvedValue(authResult); }); - it("navigates to the component's defined trusted device encryption route when login is successful and onSuccessfulLoginTdeNavigate is undefined", async () => { - await component.submit(); + it("navigates to the login-initiated route when login is successful", async () => { + await component.submit(token, remember); expect(mockRouter.navigate).toHaveBeenCalledTimes(1); - expect(mockRouter.navigate).toHaveBeenCalledWith( - [_component.trustedDeviceEncRoute], - undefined, - ); - }); - - it("calls onSuccessfulLoginTdeNavigate instead of router.navigate when the callback is defined", async () => { - component.onSuccessfulLoginTdeNavigate = jest.fn().mockResolvedValue(undefined); - - await component.submit(); - - expect(mockRouter.navigate).not.toHaveBeenCalled(); - expect(component.onSuccessfulLoginTdeNavigate).toHaveBeenCalled(); + expect(mockRouter.navigate).toHaveBeenCalledWith(["login-initiated"]); }); }); }); diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts new file mode 100644 index 00000000000..74b5db634f5 --- /dev/null +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts @@ -0,0 +1,578 @@ +import { CommonModule } from "@angular/common"; +import { + Component, + DestroyRef, + ElementRef, + Inject, + OnDestroy, + OnInit, + ViewChild, +} from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms"; +import { ActivatedRoute, Router, RouterLink } from "@angular/router"; +import { lastValueFrom, firstValueFrom } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; +import { + LoginStrategyServiceAbstraction, + LoginEmailServiceAbstraction, + UserDecryptionOptionsServiceAbstraction, + TrustedDeviceUserDecryptionOption, + UserDecryptionOptions, + LoginSuccessHandlerService, +} from "@bitwarden/auth/common"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; +import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; +import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type"; +import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; +import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; +import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; +import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { UserId } from "@bitwarden/common/types/guid"; +import { + AsyncActionsModule, + ButtonModule, + CheckboxModule, + DialogService, + FormFieldModule, + ToastService, +} from "@bitwarden/components"; + +import { AnonLayoutWrapperDataService } from "../anon-layout/anon-layout-wrapper-data.service"; +import { + TwoFactorAuthAuthenticatorIcon, + TwoFactorAuthEmailIcon, + TwoFactorAuthWebAuthnIcon, + TwoFactorAuthSecurityKeyIcon, + TwoFactorAuthDuoIcon, +} from "../icons/two-factor-auth"; + +import { TwoFactorAuthAuthenticatorComponent } from "./child-components/two-factor-auth-authenticator.component"; +import { TwoFactorAuthDuoComponent } from "./child-components/two-factor-auth-duo/two-factor-auth-duo.component"; +import { TwoFactorAuthEmailComponent } from "./child-components/two-factor-auth-email/two-factor-auth-email.component"; +import { TwoFactorAuthWebAuthnComponent } from "./child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component"; +import { TwoFactorAuthYubikeyComponent } from "./child-components/two-factor-auth-yubikey.component"; +import { + DuoLaunchAction, + LegacyKeyMigrationAction, + TwoFactorAuthComponentService, +} from "./two-factor-auth-component.service"; +import { + TwoFactorOptionsComponent, + TwoFactorOptionsDialogResult, +} from "./two-factor-options.component"; + +@Component({ + standalone: true, + selector: "app-two-factor-auth", + templateUrl: "two-factor-auth.component.html", + imports: [ + CommonModule, + JslibModule, + ReactiveFormsModule, + FormFieldModule, + AsyncActionsModule, + RouterLink, + CheckboxModule, + ButtonModule, + TwoFactorOptionsComponent, // used as dialog + TwoFactorAuthAuthenticatorComponent, + TwoFactorAuthEmailComponent, + TwoFactorAuthDuoComponent, + TwoFactorAuthYubikeyComponent, + TwoFactorAuthWebAuthnComponent, + ], + providers: [], +}) +export class TwoFactorAuthComponent implements OnInit, OnDestroy { + @ViewChild("continueButton", { read: ElementRef, static: false }) continueButton: + | ElementRef + | undefined = undefined; + + loading = true; + + orgSsoIdentifier: string | undefined = undefined; + + providerType = TwoFactorProviderType; + selectedProviderType: TwoFactorProviderType = TwoFactorProviderType.Authenticator; + + // TODO: PM-17176 - build more specific type for 2FA metadata + twoFactorProviders: Map | null = null; + selectedProviderData: { [key: string]: string } | undefined; + + @ViewChild("duoComponent") duoComponent!: TwoFactorAuthDuoComponent; + + form = this.formBuilder.group({ + token: [ + "", + { + validators: [Validators.required], + updateOn: "submit", + }, + ], + remember: [false], + }); + + get tokenFormControl() { + return this.form.controls.token; + } + + get rememberFormControl() { + return this.form.controls.remember; + } + + formPromise: Promise | undefined; + + duoLaunchAction: DuoLaunchAction | undefined = undefined; + DuoLaunchAction = DuoLaunchAction; + + webAuthInNewTab = false; + + private authenticationSessionTimeoutRoute = "authentication-timeout"; + + constructor( + private loginStrategyService: LoginStrategyServiceAbstraction, + private router: Router, + private i18nService: I18nService, + private platformUtilsService: PlatformUtilsService, + private dialogService: DialogService, + private activatedRoute: ActivatedRoute, + private logService: LogService, + private twoFactorService: TwoFactorService, + private loginEmailService: LoginEmailServiceAbstraction, + private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, + private ssoLoginService: SsoLoginServiceAbstraction, + private masterPasswordService: InternalMasterPasswordServiceAbstraction, + private accountService: AccountService, + private formBuilder: FormBuilder, + @Inject(WINDOW) protected win: Window, + private toastService: ToastService, + private twoFactorAuthComponentService: TwoFactorAuthComponentService, + private destroyRef: DestroyRef, + private anonLayoutWrapperDataService: AnonLayoutWrapperDataService, + private environmentService: EnvironmentService, + private loginSuccessHandlerService: LoginSuccessHandlerService, + ) {} + + async ngOnInit() { + this.orgSsoIdentifier = + this.activatedRoute.snapshot.queryParamMap.get("identifier") ?? undefined; + + this.listenForAuthnSessionTimeout(); + + await this.setSelected2faProviderType(); + await this.set2faProvidersAndData(); + await this.setAnonLayoutDataByTwoFactorProviderType(); + + await this.twoFactorAuthComponentService.extendPopupWidthIfRequired?.( + this.selectedProviderType, + ); + + this.duoLaunchAction = this.twoFactorAuthComponentService.determineDuoLaunchAction(); + + this.loading = false; + } + + private async setSelected2faProviderType() { + const webAuthnSupported = this.platformUtilsService.supportsWebAuthn(this.win); + + if ( + this.twoFactorAuthComponentService.shouldCheckForWebAuthnQueryParamResponse() && + webAuthnSupported + ) { + const webAuthn2faResponse = this.activatedRoute.snapshot.paramMap.get("webAuthnResponse"); + if (webAuthn2faResponse) { + this.selectedProviderType = TwoFactorProviderType.WebAuthn; + return; + } + } + + this.selectedProviderType = await this.twoFactorService.getDefaultProvider(webAuthnSupported); + } + + private async set2faProvidersAndData() { + this.twoFactorProviders = await this.twoFactorService.getProviders(); + const providerData = this.twoFactorProviders?.get(this.selectedProviderType); + this.selectedProviderData = providerData; + } + + private listenForAuthnSessionTimeout() { + this.loginStrategyService.authenticationSessionTimeout$ + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(async (expired) => { + if (!expired) { + return; + } + + try { + await this.router.navigate([this.authenticationSessionTimeoutRoute]); + } catch (err) { + this.logService.error( + `Failed to navigate to ${this.authenticationSessionTimeoutRoute} route`, + err, + ); + } + }); + } + + submit = async (token?: string, remember?: boolean) => { + // 2FA submission either comes via programmatic submission for flows like + // WebAuthn or Duo, or via the form submission for other 2FA providers. + // So, we have to figure out whether we need to validate the form or not. + let tokenValue: string; + if (token !== undefined) { + if (token === "" || token === null) { + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: this.i18nService.t("verificationCodeRequired"), + }); + return; + } + + // Token has been passed in so no need to validate the form + tokenValue = token; + } else { + // We support programmatic submission via enter key press, but we only update on submit + // so we have to manually update the form here for the invalid check to be accurate. + this.tokenFormControl.markAsTouched(); + this.tokenFormControl.markAsDirty(); + this.tokenFormControl.updateValueAndValidity(); + + // Token has not been passed in ensure form is valid before proceeding. + if (this.form.invalid) { + // returning as form validation will show the relevant errors. + return; + } + + // This shouldn't be possible w/ the required form validation, but + // to satisfy strict TS checks, have to check for null here. + const tokenFormValue = this.tokenFormControl.value; + + if (!tokenFormValue) { + return; + } + + tokenValue = tokenFormValue.trim(); + } + + // In all flows but WebAuthn, the remember value is taken from the form. + const rememberValue = remember ?? this.rememberFormControl.value ?? false; + + try { + this.formPromise = this.loginStrategyService.logInTwoFactor( + new TokenTwoFactorRequest(this.selectedProviderType, tokenValue, rememberValue), + "", // TODO: PM-15162 - deprecate captchaResponse + ); + const authResult: AuthResult = await this.formPromise; + this.logService.info("Successfully submitted two factor token"); + await this.handleAuthResult(authResult); + } catch { + this.logService.error("Error submitting two factor token"); + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: this.i18nService.t("invalidVerificationCode"), + }); + } + }; + + async selectOtherTwoFactorMethod() { + const dialogRef = TwoFactorOptionsComponent.open(this.dialogService); + const response: TwoFactorOptionsDialogResult | string | undefined = await lastValueFrom( + dialogRef.closed, + ); + + if (response !== undefined && response !== null && typeof response !== "string") { + const providerData = await this.twoFactorService.getProviders().then((providers) => { + return providers?.get(response.type); + }); + this.selectedProviderData = providerData; + this.selectedProviderType = response.type; + await this.setAnonLayoutDataByTwoFactorProviderType(); + + this.form.reset(); + this.form.updateValueAndValidity(); + } + } + + async launchDuo() { + if (this.duoComponent != null && this.duoLaunchAction !== undefined) { + await this.duoComponent.launchDuoFrameless(this.duoLaunchAction); + } + } + + protected async handleMigrateEncryptionKey(result: AuthResult): Promise { + if (!result.requiresEncryptionKeyMigration) { + return false; + } + // Migration is forced so prevent login via return + const legacyKeyMigrationAction: LegacyKeyMigrationAction = + this.twoFactorAuthComponentService.determineLegacyKeyMigrationAction(); + + switch (legacyKeyMigrationAction) { + case LegacyKeyMigrationAction.NAVIGATE_TO_MIGRATION_COMPONENT: + await this.router.navigate(["migrate-legacy-encryption"]); + break; + case LegacyKeyMigrationAction.PREVENT_LOGIN_AND_SHOW_REQUIRE_MIGRATION_WARNING: + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccured"), + message: this.i18nService.t("encryptionKeyMigrationRequired"), + }); + break; + } + return true; + } + + async setAnonLayoutDataByTwoFactorProviderType() { + switch (this.selectedProviderType) { + case TwoFactorProviderType.Authenticator: + this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ + pageSubtitle: this.i18nService.t("enterTheCodeFromYourAuthenticatorApp"), + pageIcon: TwoFactorAuthAuthenticatorIcon, + }); + break; + case TwoFactorProviderType.Email: + this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ + pageSubtitle: this.i18nService.t("enterTheCodeSentToYourEmail"), + pageIcon: TwoFactorAuthEmailIcon, + }); + break; + case TwoFactorProviderType.Duo: + case TwoFactorProviderType.OrganizationDuo: + this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ + pageSubtitle: this.i18nService.t("duoTwoFactorRequiredPageSubtitle"), + pageIcon: TwoFactorAuthDuoIcon, + }); + break; + case TwoFactorProviderType.Yubikey: + this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ + pageSubtitle: this.i18nService.t("pressYourYubiKeyToAuthenticate"), + pageIcon: TwoFactorAuthSecurityKeyIcon, + }); + break; + case TwoFactorProviderType.WebAuthn: + this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ + pageSubtitle: this.i18nService.t("followTheStepsBelowToFinishLoggingIn"), + pageIcon: TwoFactorAuthWebAuthnIcon, + }); + break; + default: + this.logService.error( + "setAnonLayoutDataByTwoFactorProviderType: Unhandled 2FA provider type", + this.selectedProviderType, + ); + break; + } + } + + private async handleAuthResult(authResult: AuthResult) { + if (await this.handleMigrateEncryptionKey(authResult)) { + return; // stop login process + } + + // User is fully logged in so handle any post login logic before executing navigation + await this.loginSuccessHandlerService.run(authResult.userId); + this.loginEmailService.clearValues(); + + // Save off the OrgSsoIdentifier for use in the TDE flows + // - TDE login decryption options component + // - Browser SSO on extension open + if (this.orgSsoIdentifier !== undefined) { + const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; + await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier( + this.orgSsoIdentifier, + userId, + ); + } + + const userDecryptionOpts = await firstValueFrom( + this.userDecryptionOptionsService.userDecryptionOptions$, + ); + + const tdeEnabled = await this.isTrustedDeviceEncEnabled(userDecryptionOpts.trustedDeviceOption); + + if (tdeEnabled) { + return await this.handleTrustedDeviceEncryptionEnabled(authResult.userId, userDecryptionOpts); + } + + // User must set password if they don't have one and they aren't using either TDE or key connector. + const requireSetPassword = + !userDecryptionOpts.hasMasterPassword && userDecryptionOpts.keyConnectorOption === undefined; + + // New users without a master password must set a master password before advancing. + if (requireSetPassword || authResult.resetMasterPassword) { + // Change implies going no password -> password in this case + return await this.handleChangePasswordRequired(this.orgSsoIdentifier); + } + + this.twoFactorAuthComponentService.reloadOpenWindows?.(); + + const inSingleActionPopoutWhichWasClosed = + await this.twoFactorAuthComponentService.closeSingleActionPopouts?.(); + + if (inSingleActionPopoutWhichWasClosed) { + // No need to execute navigation as the single action popout was closed + return; + } + + const defaultSuccessRoute = await this.determineDefaultSuccessRoute(); + + await this.router.navigate([defaultSuccessRoute], { + queryParams: { + identifier: this.orgSsoIdentifier, + }, + }); + } + + private async determineDefaultSuccessRoute(): Promise { + const authType = await firstValueFrom(this.loginStrategyService.currentAuthType$); + if (authType == AuthenticationType.Sso || authType == AuthenticationType.UserApiKey) { + return "lock"; + } + + return "vault"; + } + + private async isTrustedDeviceEncEnabled( + trustedDeviceOption: TrustedDeviceUserDecryptionOption | undefined, + ): Promise { + const ssoTo2faFlowActive = this.activatedRoute.snapshot.queryParamMap.get("sso") === "true"; + + return ssoTo2faFlowActive && trustedDeviceOption !== undefined; + } + + private async handleTrustedDeviceEncryptionEnabled( + userId: UserId, + userDecryptionOpts: UserDecryptionOptions, + ): Promise { + // Tde offboarding takes precedence + if ( + !userDecryptionOpts.hasMasterPassword && + userDecryptionOpts.trustedDeviceOption?.isTdeOffboarding + ) { + await this.masterPasswordService.setForceSetPasswordReason( + ForceSetPasswordReason.TdeOffboarding, + userId, + ); + } else if ( + !userDecryptionOpts.hasMasterPassword && + userDecryptionOpts.trustedDeviceOption?.hasManageResetPasswordPermission + ) { + // If user doesn't have a MP, but has reset password permission, they must set a MP + + // Set flag so that auth guard can redirect to set password screen after decryption (trusted or untrusted device) + // Note: we cannot directly navigate to the set password screen in this scenario as we are in a pre-decryption state, and + // if you try to set a new MP before decrypting, you will invalidate the user's data by making a new user key. + await this.masterPasswordService.setForceSetPasswordReason( + ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission, + userId, + ); + } + + this.twoFactorAuthComponentService.reloadOpenWindows?.(); + + const inSingleActionPopoutWhichWasClosed = + await this.twoFactorAuthComponentService.closeSingleActionPopouts?.(); + + if (inSingleActionPopoutWhichWasClosed) { + // No need to execute navigation as the single action popout was closed + return; + } + + await this.router.navigate(["login-initiated"]); + } + + private async handleChangePasswordRequired(orgIdentifier: string | undefined) { + await this.router.navigate(["set-password"], { + queryParams: { + identifier: orgIdentifier, + }, + }); + } + + /** + * Determines if a user needs to reset their password based on certain conditions. + * Users can be forced to reset their password via an admin or org policy disallowing weak passwords. + * Note: this is different from the SSO component login flow as a user can + * login with MP and then have to pass 2FA to finish login and we can actually + * evaluate if they have a weak password at that time. + * + * @param {AuthResult} authResult - The authentication result. + * @returns {boolean} Returns true if a password reset is required, false otherwise. + */ + private isForcePasswordResetRequired(authResult: AuthResult): boolean { + const forceResetReasons = [ + ForceSetPasswordReason.AdminForcePasswordReset, + ForceSetPasswordReason.WeakMasterPassword, + ]; + + return forceResetReasons.includes(authResult.forcePasswordReset); + } + + showContinueButton() { + return ( + this.selectedProviderType != null && + this.selectedProviderType !== TwoFactorProviderType.WebAuthn && + this.selectedProviderType !== TwoFactorProviderType.Duo && + this.selectedProviderType !== TwoFactorProviderType.OrganizationDuo + ); + } + + hideRememberMe() { + // Don't show remember for me for scenarios where we have to popout the extension + return ( + ((this.selectedProviderType === TwoFactorProviderType.Duo || + this.selectedProviderType === TwoFactorProviderType.OrganizationDuo) && + this.duoLaunchAction === DuoLaunchAction.SINGLE_ACTION_POPOUT) || + (this.selectedProviderType === TwoFactorProviderType.WebAuthn && this.webAuthInNewTab) + ); + } + + async use2faRecoveryCode() { + // TODO: PM-17696 eventually we should have a consolidated recover-2fa component as a follow up + // so that we don't have to always open a new tab for non-web clients. + const env = await firstValueFrom(this.environmentService.environment$); + const webVault = env.getWebVaultUrl(); + this.platformUtilsService.launchUri(webVault + "/#/recover-2fa"); + } + + async handleEnterKeyPress() { + // Each 2FA provider has a different implementation. + // For example, email 2FA uses an input of type "text" for the token which does not automatically submit on enter. + // Yubikey, however, uses an input with type "password" which does automatically submit on enter. + // So we have to handle the enter key press differently for each provider. + switch (this.selectedProviderType) { + case TwoFactorProviderType.Authenticator: + case TwoFactorProviderType.Email: + // We must actually submit the form via click in order for the tokenFormControl value to be set. + this.continueButton?.nativeElement?.click(); + break; + case TwoFactorProviderType.Duo: + case TwoFactorProviderType.OrganizationDuo: + case TwoFactorProviderType.WebAuthn: + case TwoFactorProviderType.Yubikey: + // Do nothing + break; + default: + this.logService.error( + "handleEnterKeyPress: Unhandled 2FA provider type", + this.selectedProviderType, + ); + break; + } + } + + async ngOnDestroy() { + this.twoFactorAuthComponentService.removePopupWidthExtension?.(); + } +} diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.guard.spec.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.guard.spec.ts new file mode 100644 index 00000000000..22cfe0820ef --- /dev/null +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.guard.spec.ts @@ -0,0 +1,74 @@ +import { Component } from "@angular/core"; +import { TestBed } from "@angular/core/testing"; +import { provideRouter, Router } from "@angular/router"; +import { mock, MockProxy } from "jest-mock-extended"; +import { BehaviorSubject } from "rxjs"; + +import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; +import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type"; + +import { LoginStrategyServiceAbstraction } from "../../common"; + +import { TwoFactorAuthGuard } from "./two-factor-auth.guard"; + +@Component({ template: "" }) +export class EmptyComponent {} + +describe("TwoFactorAuthGuard", () => { + let loginStrategyService: MockProxy; + const currentAuthTypesSubject = new BehaviorSubject(null); + + let twoFactorService: MockProxy; + let router: Router; + + beforeEach(() => { + loginStrategyService = mock(); + loginStrategyService.currentAuthType$ = currentAuthTypesSubject.asObservable(); + + twoFactorService = mock(); + + TestBed.configureTestingModule({ + providers: [ + provideRouter([ + { path: "login", component: EmptyComponent }, + { path: "protected", component: EmptyComponent, canActivate: [TwoFactorAuthGuard] }, + ]), + { provide: LoginStrategyServiceAbstraction, useValue: loginStrategyService }, + { provide: TwoFactorService, useValue: twoFactorService }, + ], + }); + + router = TestBed.inject(Router); + }); + + it("should redirect to /login if the user is not authenticating", async () => { + // Arrange + currentAuthTypesSubject.next(null); + twoFactorService.getProviders.mockResolvedValue(null); + + // Act + await router.navigateByUrl("/protected"); + + // Assert + expect(router.url).toBe("/login"); + }); + + const authenticationTypes = Object.entries(AuthenticationType) + // filter out reverse mappings (e.g., "0": "Password") + .filter(([key, value]) => typeof value === "number") + .map(([key, value]) => [value, key]) as [AuthenticationType, string][]; + + authenticationTypes.forEach(([authType, authTypeName]) => { + it(`should redirect to /login if the user is authenticating with ${authTypeName} but no two-factor providers exist`, async () => { + // Arrange + currentAuthTypesSubject.next(authType); + twoFactorService.getProviders.mockResolvedValue(null); + + // Act + await router.navigateByUrl("/protected"); + + // Assert + expect(router.url).toBe("/login"); + }); + }); +}); diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.guard.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.guard.ts new file mode 100644 index 00000000000..2aec0bae441 --- /dev/null +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.guard.ts @@ -0,0 +1,33 @@ +import { inject } from "@angular/core"; +import { + ActivatedRouteSnapshot, + CanActivateFn, + Router, + RouterStateSnapshot, + UrlTree, +} from "@angular/router"; +import { firstValueFrom } from "rxjs"; + +import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; + +import { LoginStrategyServiceAbstraction } from "../../common"; + +export const TwoFactorAuthGuard: CanActivateFn = async ( + route: ActivatedRouteSnapshot, + routerState: RouterStateSnapshot, +): Promise => { + const loginStrategyService = inject(LoginStrategyServiceAbstraction); + const twoFactorService = inject(TwoFactorService); + const router = inject(Router); + + const currentAuthType = await firstValueFrom(loginStrategyService.currentAuthType$); + const userIsAuthenticating = currentAuthType !== null; + + const twoFactorProviders = await twoFactorService.getProviders(); + + if (!userIsAuthenticating || twoFactorProviders == null) { + return router.createUrlTree(["/login"]); + } + + return true; +}; diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-options.component.html b/libs/auth/src/angular/two-factor-auth/two-factor-options.component.html new file mode 100644 index 00000000000..277ba047add --- /dev/null +++ b/libs/auth/src/angular/two-factor-auth/two-factor-options.component.html @@ -0,0 +1,50 @@ + + + {{ "selectTwoStepLoginMethod" | i18n }} + + + + + + + + + + + + diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-options.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-options.component.ts new file mode 100644 index 00000000000..819a48d0d8c --- /dev/null +++ b/libs/auth/src/angular/two-factor-auth/two-factor-options.component.ts @@ -0,0 +1,81 @@ +import { DialogRef } from "@angular/cdk/dialog"; +import { CommonModule } from "@angular/common"; +import { Component, OnInit } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { + TwoFactorProviderDetails, + TwoFactorService, +} from "@bitwarden/common/auth/abstractions/two-factor.service"; +import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; +import { + ButtonModule, + DialogModule, + DialogService, + IconModule, + ItemModule, + TypographyModule, +} from "@bitwarden/components"; + +import { + TwoFactorAuthAuthenticatorIcon, + TwoFactorAuthDuoIcon, + TwoFactorAuthEmailIcon, + TwoFactorAuthWebAuthnIcon, + TwoFactorAuthYubicoIcon, +} from "../icons/two-factor-auth"; + +export type TwoFactorOptionsDialogResult = { + type: TwoFactorProviderType; +}; + +@Component({ + standalone: true, + selector: "app-two-factor-options", + templateUrl: "two-factor-options.component.html", + imports: [ + CommonModule, + JslibModule, + DialogModule, + ButtonModule, + TypographyModule, + ItemModule, + IconModule, + ], + providers: [], +}) +export class TwoFactorOptionsComponent implements OnInit { + providers: TwoFactorProviderDetails[] = []; + TwoFactorProviderType = TwoFactorProviderType; + + readonly Icons = { + TwoFactorAuthAuthenticatorIcon, + TwoFactorAuthEmailIcon, + TwoFactorAuthDuoIcon, + TwoFactorAuthYubicoIcon, + TwoFactorAuthWebAuthnIcon, + }; + + constructor( + private twoFactorService: TwoFactorService, + private dialogRef: DialogRef, + ) {} + + async ngOnInit() { + const providers = await this.twoFactorService.getSupportedProviders(window); + providers.sort((a: TwoFactorProviderDetails, b: TwoFactorProviderDetails) => a.sort - b.sort); + this.providers = providers; + } + + async choose(p: TwoFactorProviderDetails) { + this.dialogRef.close({ type: p.type }); + } + + static open(dialogService: DialogService) { + return dialogService.open(TwoFactorOptionsComponent); + } + + cancel() { + this.dialogRef.close(); + } +} diff --git a/libs/auth/src/angular/user-verification/user-verification-form-input.component.html b/libs/auth/src/angular/user-verification/user-verification-form-input.component.html index f532a3b23fd..56bce040d2f 100644 --- a/libs/auth/src/angular/user-verification/user-verification-form-input.component.html +++ b/libs/auth/src/angular/user-verification/user-verification-form-input.component.html @@ -120,7 +120,7 @@
    {{ "enterVerificationCodeSentToEmail" | i18n }} -

    +

    diff --git a/libs/auth/src/angular/user-verification/user-verification-form-input.component.ts b/libs/auth/src/angular/user-verification/user-verification-form-input.component.ts index 40c3106b188..ff4af51f732 100644 --- a/libs/auth/src/angular/user-verification/user-verification-form-input.component.ts +++ b/libs/auth/src/angular/user-verification/user-verification-form-input.component.ts @@ -68,7 +68,6 @@ import { ActiveClientVerificationOption } from "./active-client-verification-opt CalloutModule, ], }) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil export class UserVerificationFormInputComponent implements ControlValueAccessor, OnInit, OnDestroy { @Input() verificationType: "server" | "client" = "server"; // server represents original behavior private _invalidSecret = false; diff --git a/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.spec.ts b/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.spec.ts index 6b39c90fc9b..d510d671d69 100644 --- a/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.spec.ts +++ b/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.spec.ts @@ -1,10 +1,12 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { BehaviorSubject } from "rxjs"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { + VaultTimeoutSettingsService, + VaultTimeoutStringType, +} from "@bitwarden/common/key-management/vault-timeout"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type"; import { VaultTimeoutInputComponent } from "./vault-timeout-input.component"; diff --git a/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.ts b/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.ts index f7199d06499..91af1e8adbe 100644 --- a/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.ts +++ b/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.ts @@ -17,13 +17,16 @@ import { import { filter, map, Observable, Subject, takeUntil } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; -import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; +import { + VaultTimeout, + VaultTimeoutAction, + VaultTimeoutOption, + VaultTimeoutSettingsService, +} from "@bitwarden/common/key-management/vault-timeout"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { VaultTimeout, VaultTimeoutOption } from "@bitwarden/common/types/vault-timeout.type"; import { FormFieldModule, SelectModule } from "@bitwarden/components"; type VaultTimeoutForm = FormGroup<{ diff --git a/libs/auth/src/common/abstractions/auth-request.service.abstraction.ts b/libs/auth/src/common/abstractions/auth-request.service.abstraction.ts index 1829fe6b0c9..75bb8686163 100644 --- a/libs/auth/src/common/abstractions/auth-request.service.abstraction.ts +++ b/libs/auth/src/common/abstractions/auth-request.service.abstraction.ts @@ -12,6 +12,12 @@ export abstract class AuthRequestServiceAbstraction { /** Emits an auth request id when an auth request has been approved. */ authRequestPushNotification$: Observable; + /** + * Emits when a login has been approved by an admin. This emission is specifically for the + * purpose of notifying the consuming component to display a toast informing the user. + */ + adminLoginApproved$: Observable; + /** * Returns an admin auth request for the given user if it exists. * @param userId The user id. @@ -106,4 +112,13 @@ export abstract class AuthRequestServiceAbstraction { * @returns The dash-delimited fingerprint phrase. */ abstract getFingerprintPhrase(email: string, publicKey: Uint8Array): Promise; + + /** + * Passes a value to the adminLoginApprovedSubject via next(), which causes the + * adminLoginApproved$ observable to emit. + * + * The purpose is to notify consuming components (of adminLoginApproved$) to display + * a toast informing the user that a login has been approved by an admin. + */ + abstract emitAdminLoginApproved(): void; } diff --git a/libs/auth/src/common/abstractions/login-strategy.service.ts b/libs/auth/src/common/abstractions/login-strategy.service.ts index 1088d6de736..e9fa780b0fe 100644 --- a/libs/auth/src/common/abstractions/login-strategy.service.ts +++ b/libs/auth/src/common/abstractions/login-strategy.service.ts @@ -47,7 +47,6 @@ export abstract class LoginStrategyServiceAbstraction { * Auth Request. Otherwise, it will return null. */ getAuthRequestId: () => Promise; - /** * Sends a token request to the server using the provided credentials. */ @@ -67,6 +66,7 @@ export abstract class LoginStrategyServiceAbstraction { */ logInTwoFactor: ( twoFactor: TokenTwoFactorRequest, + // TODO: PM-15162 - deprecate captchaResponse captchaResponse: string, ) => Promise; /** @@ -74,7 +74,11 @@ export abstract class LoginStrategyServiceAbstraction { */ makePreloginKey: (masterPassword: string, email: string) => Promise; /** - * Emits true if the two factor session has expired. + * Emits true if the authentication session has expired. */ - twoFactorTimeout$: Observable; + authenticationSessionTimeout$: Observable; + /** + * Sends a token request to the server with the provided device verification OTP. + */ + logInNewDeviceVerification: (deviceVerificationOtp: string) => Promise; } diff --git a/libs/auth/src/common/abstractions/pin.service.abstraction.ts b/libs/auth/src/common/abstractions/pin.service.abstraction.ts index eb1cfaf0ec3..0d0f29dff40 100644 --- a/libs/auth/src/common/abstractions/pin.service.abstraction.ts +++ b/libs/auth/src/common/abstractions/pin.service.abstraction.ts @@ -30,7 +30,7 @@ export abstract class PinServiceAbstraction { /** * Gets the persistent (stored on disk) version of the UserKey, encrypted by the PinKey. */ - abstract getPinKeyEncryptedUserKeyPersistent: (userId: UserId) => Promise; + abstract getPinKeyEncryptedUserKeyPersistent: (userId: UserId) => Promise; /** * Clears the persistent (stored on disk) version of the UserKey, encrypted by the PinKey. @@ -40,7 +40,7 @@ export abstract class PinServiceAbstraction { /** * Gets the ephemeral (stored in memory) version of the UserKey, encrypted by the PinKey. */ - abstract getPinKeyEncryptedUserKeyEphemeral: (userId: UserId) => Promise; + abstract getPinKeyEncryptedUserKeyEphemeral: (userId: UserId) => Promise; /** * Clears the ephemeral (stored in memory) version of the UserKey, encrypted by the PinKey. @@ -70,7 +70,7 @@ export abstract class PinServiceAbstraction { /** * Gets the user's PIN, encrypted by the UserKey. */ - abstract getUserKeyEncryptedPin: (userId: UserId) => Promise; + abstract getUserKeyEncryptedPin: (userId: UserId) => Promise; /** * Sets the user's PIN, encrypted by the UserKey. @@ -94,7 +94,7 @@ export abstract class PinServiceAbstraction { * Gets the old MasterKey, encrypted by the PinKey (formerly called `pinProtected`). * Deprecated and used for migration purposes only. */ - abstract getOldPinKeyEncryptedMasterKey: (userId: UserId) => Promise; + abstract getOldPinKeyEncryptedMasterKey: (userId: UserId) => Promise; /** * Clears the old MasterKey, encrypted by the PinKey. diff --git a/libs/auth/src/common/index.ts b/libs/auth/src/common/index.ts index 43efd7c6387..97909bdc449 100644 --- a/libs/auth/src/common/index.ts +++ b/libs/auth/src/common/index.ts @@ -6,3 +6,4 @@ export * from "./models"; export * from "./types"; export * from "./services"; export * from "./utilities"; +export * from "./login-strategies"; diff --git a/libs/auth/src/common/login-strategies/auth-request-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/auth-request-login.strategy.spec.ts index cec4481cd8d..842dfb3f4e3 100644 --- a/libs/auth/src/common/login-strategies/auth-request-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/auth-request-login.strategy.spec.ts @@ -2,22 +2,25 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; -import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; +import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service"; +import { + VaultTimeoutAction, + VaultTimeoutSettingsService, +} from "@bitwarden/common/key-management/vault-timeout"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vault-timeout/vault-timeout-settings.service"; import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; import { CsprngArray } from "@bitwarden/common/types/csprng"; import { UserId } from "@bitwarden/common/types/guid"; @@ -51,6 +54,7 @@ describe("AuthRequestLoginStrategy", () => { let billingAccountProfileStateService: MockProxy; let vaultTimeoutSettingsService: MockProxy; let kdfConfigService: MockProxy; + let environmentService: MockProxy; const mockUserId = Utils.newGuid() as UserId; let accountService: FakeAccountService; @@ -86,6 +90,7 @@ describe("AuthRequestLoginStrategy", () => { billingAccountProfileStateService = mock(); vaultTimeoutSettingsService = mock(); kdfConfigService = mock(); + environmentService = mock(); accountService = mockAccountServiceWith(mockUserId); masterPasswordService = new FakeMasterPasswordService(); @@ -115,6 +120,7 @@ describe("AuthRequestLoginStrategy", () => { billingAccountProfileStateService, vaultTimeoutSettingsService, kdfConfigService, + environmentService, ); tokenResponse = identityTokenResponseFactory(); diff --git a/libs/auth/src/common/login-strategies/auth-request-login.strategy.ts b/libs/auth/src/common/login-strategies/auth-request-login.strategy.ts index e546f89032b..3d91adf35cf 100644 --- a/libs/auth/src/common/login-strategies/auth-request-login.strategy.ts +++ b/libs/auth/src/common/login-strategies/auth-request-login.strategy.ts @@ -3,11 +3,11 @@ import { firstValueFrom, Observable, map, BehaviorSubject } from "rxjs"; import { Jsonify } from "type-fest"; -import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { PasswordTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/password-token.request"; import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; import { UserId } from "@bitwarden/common/types/guid"; import { AuthRequestLoginCredentials } from "../models/domain/login-credentials"; diff --git a/libs/auth/src/common/login-strategies/index.ts b/libs/auth/src/common/login-strategies/index.ts new file mode 100644 index 00000000000..166ef935e08 --- /dev/null +++ b/libs/auth/src/common/login-strategies/index.ts @@ -0,0 +1 @@ +export { PasswordLoginStrategy, PasswordLoginStrategyData } from "./password-login.strategy"; diff --git a/libs/auth/src/common/login-strategies/login.strategy.spec.ts b/libs/auth/src/common/login-strategies/login.strategy.spec.ts index 50443bab0ea..fc3be61fe11 100644 --- a/libs/auth/src/common/login-strategies/login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/login.strategy.spec.ts @@ -2,8 +2,8 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; @@ -12,15 +12,20 @@ import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/for import { PasswordTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/password-token.request"; import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; import { IdentityCaptchaResponse } from "@bitwarden/common/auth/models/response/identity-captcha.response"; +import { IdentityDeviceVerificationResponse } from "@bitwarden/common/auth/models/response/identity-device-verification.response"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response"; import { MasterPasswordPolicyResponse } from "@bitwarden/common/auth/models/response/master-password-policy.response"; import { IUserDecryptionOptionsServerResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/user-decryption-options.response"; -import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service"; +import { + VaultTimeoutAction, + VaultTimeoutSettingsService, +} from "@bitwarden/common/key-management/vault-timeout"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.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"; @@ -76,8 +81,8 @@ const twoFactorToken = "TWO_FACTOR_TOKEN"; const twoFactorRemember = true; export function identityTokenResponseFactory( - masterPasswordPolicyResponse: MasterPasswordPolicyResponse = null, - userDecryptionOptions: IUserDecryptionOptionsServerResponse = null, + masterPasswordPolicyResponse: MasterPasswordPolicyResponse | undefined = undefined, + userDecryptionOptions: IUserDecryptionOptionsServerResponse | undefined = undefined, ) { return new IdentityTokenResponse({ ForcePasswordReset: false, @@ -119,6 +124,7 @@ describe("LoginStrategy", () => { let billingAccountProfileStateService: MockProxy; let vaultTimeoutSettingsService: MockProxy; let kdfConfigService: MockProxy; + let environmentService: MockProxy; let passwordLoginStrategy: PasswordLoginStrategy; let credentials: PasswordLoginCredentials; @@ -143,6 +149,7 @@ describe("LoginStrategy", () => { policyService = mock(); passwordStrengthService = mock(); billingAccountProfileStateService = mock(); + environmentService = mock(); vaultTimeoutSettingsService = mock(); @@ -155,7 +162,7 @@ describe("LoginStrategy", () => { passwordStrengthService, policyService, loginStrategyService, - accountService, + accountService as unknown as AccountService, masterPasswordService, keyService, encryptService, @@ -171,6 +178,7 @@ describe("LoginStrategy", () => { billingAccountProfileStateService, vaultTimeoutSettingsService, kdfConfigService, + environmentService, ); credentials = new PasswordLoginCredentials(email, masterPassword); }); @@ -286,13 +294,41 @@ describe("LoginStrategy", () => { const result = await passwordLoginStrategy.logIn(credentials); - expect(result).toEqual({ - userId: userId, - forcePasswordReset: ForceSetPasswordReason.AdminForcePasswordReset, - resetMasterPassword: true, - twoFactorProviders: null, - captchaSiteKey: "", - } as AuthResult); + const expected = new AuthResult(); + expected.userId = userId; + expected.forcePasswordReset = ForceSetPasswordReason.AdminForcePasswordReset; + expected.resetMasterPassword = true; + expected.twoFactorProviders = {} as Partial< + Record> + >; + expected.captchaSiteKey = ""; + expected.twoFactorProviders = null; + expect(result).toEqual(expected); + }); + + it("processes a forcePasswordReset response properly", async () => { + const tokenResponse = identityTokenResponseFactory(); + tokenResponse.forcePasswordReset = true; + + apiService.postIdentityToken.mockResolvedValue(tokenResponse); + + const result = await passwordLoginStrategy.logIn(credentials); + + const expected = new AuthResult(); + expected.userId = userId; + expected.forcePasswordReset = ForceSetPasswordReason.AdminForcePasswordReset; + expected.resetMasterPassword = false; + expected.twoFactorProviders = {} as Partial< + Record> + >; + expected.captchaSiteKey = ""; + expected.twoFactorProviders = null; + expect(result).toEqual(expected); + + expect(masterPasswordService.mock.setForceSetPasswordReason).toHaveBeenCalledWith( + ForceSetPasswordReason.AdminForcePasswordReset, + userId, + ); }); it("rejects login if CAPTCHA is required", async () => { @@ -377,10 +413,11 @@ describe("LoginStrategy", () => { expect(tokenService.clearTwoFactorToken).toHaveBeenCalled(); const expected = new AuthResult(); - expected.twoFactorProviders = { 0: null } as Record< - TwoFactorProviderType, - Record + expected.twoFactorProviders = { 0: null } as unknown as Partial< + Record> >; + expected.email = ""; + expected.ssoEmail2FaSessionToken = undefined; expect(result).toEqual(expected); }); @@ -460,14 +497,19 @@ describe("LoginStrategy", () => { it("sends 2FA token provided by user to server (two-step)", async () => { // Simulate a partially completed login cache = new PasswordLoginStrategyData(); - cache.tokenRequest = new PasswordTokenRequest(email, masterPasswordHash, null, null); + cache.tokenRequest = new PasswordTokenRequest( + email, + masterPasswordHash, + "", + new TokenTwoFactorRequest(), + ); passwordLoginStrategy = new PasswordLoginStrategy( cache, passwordStrengthService, policyService, loginStrategyService, - accountService, + accountService as AccountService, masterPasswordService, keyService, encryptService, @@ -483,13 +525,14 @@ describe("LoginStrategy", () => { billingAccountProfileStateService, vaultTimeoutSettingsService, kdfConfigService, + environmentService, ); apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory()); await passwordLoginStrategy.logInTwoFactor( new TokenTwoFactorRequest(twoFactorProviderType, twoFactorToken, twoFactorRemember), - null, + "", ); expect(apiService.postIdentityToken).toHaveBeenCalledWith( @@ -503,4 +546,55 @@ describe("LoginStrategy", () => { ); }); }); + + describe("Device verification", () => { + it("processes device verification response", async () => { + const captchaToken = "test-captcha-token"; + const deviceVerificationResponse = new IdentityDeviceVerificationResponse({ + error: "invalid_grant", + error_description: "Device verification required.", + email: "test@bitwarden.com", + deviceVerificationRequest: true, + captchaToken: captchaToken, + }); + + apiService.postIdentityToken.mockResolvedValue(deviceVerificationResponse); + + cache = new PasswordLoginStrategyData(); + cache.tokenRequest = new PasswordTokenRequest( + email, + masterPasswordHash, + "", + new TokenTwoFactorRequest(), + ); + + passwordLoginStrategy = new PasswordLoginStrategy( + cache, + passwordStrengthService, + policyService, + loginStrategyService, + accountService as AccountService, + masterPasswordService, + keyService, + encryptService, + apiService, + tokenService, + appIdService, + platformUtilsService, + messagingService, + logService, + stateService, + twoFactorService, + userDecryptionOptionsService, + billingAccountProfileStateService, + vaultTimeoutSettingsService, + kdfConfigService, + environmentService, + ); + + const result = await passwordLoginStrategy.logIn(credentials); + + expect(result.requiresDeviceVerification).toBe(true); + }); + }); }); diff --git a/libs/auth/src/common/login-strategies/login.strategy.ts b/libs/auth/src/common/login-strategies/login.strategy.ts index 25f99f47840..96d7b6b0f74 100644 --- a/libs/auth/src/common/login-strategies/login.strategy.ts +++ b/libs/auth/src/common/login-strategies/login.strategy.ts @@ -1,11 +1,7 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { BehaviorSubject, filter, firstValueFrom, timeout } from "rxjs"; +import { BehaviorSubject, filter, firstValueFrom, timeout, Observable } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; @@ -18,14 +14,20 @@ import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/ide import { UserApiTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/user-api-token.request"; import { WebAuthnLoginTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/webauthn-login-token.request"; import { IdentityCaptchaResponse } from "@bitwarden/common/auth/models/response/identity-captcha.response"; +import { IdentityDeviceVerificationResponse } from "@bitwarden/common/auth/models/response/identity-device-verification.response"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { ClientType } from "@bitwarden/common/enums"; -import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; +import { + VaultTimeoutAction, + VaultTimeoutSettingsService, +} from "@bitwarden/common/key-management/vault-timeout"; import { KeysRequest } from "@bitwarden/common/models/request/keys.request"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.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"; @@ -51,14 +53,19 @@ import { import { UserDecryptionOptions } from "../models/domain/user-decryption-options"; import { CacheData } from "../services/login-strategies/login-strategy.state"; -type IdentityResponse = IdentityTokenResponse | IdentityTwoFactorResponse | IdentityCaptchaResponse; +type IdentityResponse = + | IdentityTokenResponse + | IdentityTwoFactorResponse + | IdentityCaptchaResponse + | IdentityDeviceVerificationResponse; export abstract class LoginStrategyData { tokenRequest: | UserApiTokenRequest | PasswordTokenRequest | SsoTokenRequest - | WebAuthnLoginTokenRequest; + | WebAuthnLoginTokenRequest + | undefined; captchaBypassToken?: string; /** User's entered email obtained pre-login. */ @@ -67,6 +74,8 @@ export abstract class LoginStrategyData { export abstract class LoginStrategy { protected abstract cache: BehaviorSubject; + protected sessionTimeoutSubject = new BehaviorSubject(false); + sessionTimeout$: Observable = this.sessionTimeoutSubject.asObservable(); constructor( protected accountService: AccountService, @@ -85,6 +94,7 @@ export abstract class LoginStrategy { protected billingAccountProfileStateService: BillingAccountProfileStateService, protected vaultTimeoutSettingsService: VaultTimeoutSettingsService, protected KdfConfigService: KdfConfigService, + protected environmentService: EnvironmentService, ) {} abstract exportCache(): CacheData; @@ -100,9 +110,12 @@ export abstract class LoginStrategy { async logInTwoFactor( twoFactor: TokenTwoFactorRequest, - captchaResponse: string = null, + captchaResponse: string | null = null, ): Promise { const data = this.cache.value; + if (!data.tokenRequest) { + throw new Error("Token request is undefined"); + } data.tokenRequest.setTwoFactor(twoFactor); this.cache.next(data); const [authResult] = await this.startLogIn(); @@ -113,6 +126,9 @@ export abstract class LoginStrategy { await this.twoFactorService.clearSelectedProvider(); const tokenRequest = this.cache.value.tokenRequest; + if (!tokenRequest) { + throw new Error("Token request is undefined"); + } const response = await this.apiService.postIdentityToken(tokenRequest); if (response instanceof IdentityTwoFactorResponse) { @@ -121,6 +137,8 @@ export abstract class LoginStrategy { return [await this.processCaptchaResponse(response), response]; } else if (response instanceof IdentityTokenResponse) { return [await this.processTokenResponse(response), response]; + } else if (response instanceof IdentityDeviceVerificationResponse) { + return [await this.processDeviceVerificationResponse(response), response]; } throw new Error("Invalid response object."); @@ -176,10 +194,14 @@ export abstract class LoginStrategy { await this.accountService.addAccount(userId, { name: accountInformation.name, - email: accountInformation.email, - emailVerified: accountInformation.email_verified, + email: accountInformation.email ?? "", + emailVerified: accountInformation.email_verified ?? false, }); + // User env must be seeded from currently set env before switching to the account + // to avoid any incorrect emissions of the global default env. + await this.environmentService.seedUserEnvironment(userId); + await this.accountService.switchAccount(userId); await this.stateService.addAccount( @@ -230,7 +252,7 @@ export abstract class LoginStrategy { ); await this.billingAccountProfileStateService.setHasPremium( - accountInformation.premium, + accountInformation.premium ?? false, false, userId, ); @@ -249,17 +271,24 @@ export abstract class LoginStrategy { } } - result.resetMasterPassword = response.resetMasterPassword; - - // Convert boolean to enum - if (response.forcePasswordReset) { - result.forcePasswordReset = ForceSetPasswordReason.AdminForcePasswordReset; - } - - // Must come before setting keys, user key needs email to update additional keys + // Must come before setting keys, user key needs email to update additional keys. const userId = await this.saveAccountInformation(response); result.userId = userId; + result.resetMasterPassword = response.resetMasterPassword; + + // Convert boolean to enum and set the state for the master password service to + // so we know when we reach the auth guard that we need to guide them properly to admin + // password reset. + if (response.forcePasswordReset) { + result.forcePasswordReset = ForceSetPasswordReason.AdminForcePasswordReset; + + await this.masterPasswordService.setForceSetPasswordReason( + ForceSetPasswordReason.AdminForcePasswordReset, + userId, + ); + } + if (response.twoFactorToken != null) { // note: we can read email from access token b/c it was saved in saveAccountInformation const userEmail = await this.tokenService.getEmail(); @@ -278,7 +307,9 @@ export abstract class LoginStrategy { // The keys comes from different sources depending on the login strategy protected abstract setMasterKey(response: IdentityTokenResponse, userId: UserId): Promise; + protected abstract setUserKey(response: IdentityTokenResponse, userId: UserId): Promise; + protected abstract setPrivateKey(response: IdentityTokenResponse, userId: UserId): Promise; // Old accounts used master key for encryption. We are forcing migrations but only need to @@ -291,6 +322,9 @@ export abstract class LoginStrategy { try { const userKey = await this.keyService.getUserKeyWithLegacySupport(userId); const [publicKey, privateKey] = await this.keyService.makeKeyPair(userKey); + if (!privateKey.encryptedString) { + throw new Error("Failed to create encrypted private key"); + } await this.apiService.postAccountKeys(new KeysRequest(publicKey, privateKey.encryptedString)); return privateKey.encryptedString; } catch (e) { @@ -316,7 +350,8 @@ export abstract class LoginStrategy { await this.twoFactorService.setProviders(response); this.cache.next({ ...this.cache.value, captchaBypassToken: response.captchaToken ?? null }); result.ssoEmail2FaSessionToken = response.ssoEmail2faSessionToken; - result.email = response.email; + + result.email = response.email ?? ""; return result; } @@ -355,4 +390,22 @@ export abstract class LoginStrategy { ), ); } + + /** + * Handles the response from the server when a device verification is required. + * It sets the requiresDeviceVerification flag to true and caches the captcha token if it came back. + * + * @param {IdentityDeviceVerificationResponse} response - The response from the server indicating that device verification is required. + * @returns {Promise} - A promise that resolves to an AuthResult object + */ + protected async processDeviceVerificationResponse( + response: IdentityDeviceVerificationResponse, + ): Promise { + const result = new AuthResult(); + result.requiresDeviceVerification = true; + + // Extend cached data with captcha bypass token if it came back. + this.cache.next({ ...this.cache.value, captchaBypassToken: response.captchaToken ?? null }); + return result; + } } diff --git a/libs/auth/src/common/login-strategies/password-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/password-login.strategy.spec.ts index 4ee4fcaeb38..3752960fc47 100644 --- a/libs/auth/src/common/login-strategies/password-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/password-login.strategy.spec.ts @@ -10,11 +10,15 @@ import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/for import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response"; import { MasterPasswordPolicyResponse } from "@bitwarden/common/auth/models/response/master-password-policy.response"; -import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service"; +import { + VaultTimeoutAction, + VaultTimeoutSettingsService, +} from "@bitwarden/common/key-management/vault-timeout"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.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"; @@ -22,7 +26,6 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv import { HashPurpose } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vault-timeout/vault-timeout-settings.service"; import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; import { PasswordStrengthServiceAbstraction, @@ -78,6 +81,7 @@ describe("PasswordLoginStrategy", () => { let billingAccountProfileStateService: MockProxy; let vaultTimeoutSettingsService: MockProxy; let kdfConfigService: MockProxy; + let environmentService: MockProxy; let passwordLoginStrategy: PasswordLoginStrategy; let credentials: PasswordLoginCredentials; @@ -104,6 +108,7 @@ describe("PasswordLoginStrategy", () => { billingAccountProfileStateService = mock(); vaultTimeoutSettingsService = mock(); kdfConfigService = mock(); + environmentService = mock(); appIdService.getAppId.mockResolvedValue(deviceId); tokenService.decodeAccessToken.mockResolvedValue({ @@ -142,6 +147,7 @@ describe("PasswordLoginStrategy", () => { billingAccountProfileStateService, vaultTimeoutSettingsService, kdfConfigService, + environmentService, ); credentials = new PasswordLoginCredentials(email, masterPassword); tokenResponse = identityTokenResponseFactory(masterPasswordPolicy); @@ -276,4 +282,24 @@ describe("PasswordLoginStrategy", () => { ); expect(secondResult.forcePasswordReset).toEqual(ForceSetPasswordReason.WeakMasterPassword); }); + + it("handles new device verification login with OTP", async () => { + const deviceVerificationOtp = "123456"; + const tokenResponse = identityTokenResponseFactory(); + apiService.postIdentityToken.mockResolvedValueOnce(tokenResponse); + tokenService.decodeAccessToken.mockResolvedValue({ sub: userId }); + + await passwordLoginStrategy.logIn(credentials); + + const result = await passwordLoginStrategy.logInNewDeviceVerification(deviceVerificationOtp); + + expect(apiService.postIdentityToken).toHaveBeenCalledWith( + expect.objectContaining({ + newDeviceOtp: deviceVerificationOtp, + }), + ); + expect(result.forcePasswordReset).toBe(ForceSetPasswordReason.None); + expect(result.resetMasterPassword).toBe(false); + expect(result.userId).toBe(userId); + }); }); diff --git a/libs/auth/src/common/login-strategies/password-login.strategy.ts b/libs/auth/src/common/login-strategies/password-login.strategy.ts index c496b7c9674..f0a8d40f914 100644 --- a/libs/auth/src/common/login-strategies/password-login.strategy.ts +++ b/libs/auth/src/common/login-strategies/password-login.strategy.ts @@ -10,6 +10,7 @@ import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/for import { PasswordTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/password-token.request"; import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; import { IdentityCaptchaResponse } from "@bitwarden/common/auth/models/response/identity-captcha.response"; +import { IdentityDeviceVerificationResponse } from "@bitwarden/common/auth/models/response/identity-device-verification.response"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response"; import { HashPurpose } from "@bitwarden/common/platform/enums"; @@ -208,9 +209,12 @@ export class PasswordLoginStrategy extends LoginStrategy { } private getMasterPasswordPolicyOptionsFromResponse( - response: IdentityTokenResponse | IdentityTwoFactorResponse, + response: + | IdentityTokenResponse + | IdentityTwoFactorResponse + | IdentityDeviceVerificationResponse, ): MasterPasswordPolicyOptions { - if (response == null) { + if (response == null || response instanceof IdentityDeviceVerificationResponse) { return null; } return MasterPasswordPolicyOptions.fromResponse(response.masterPasswordPolicy); @@ -233,4 +237,13 @@ export class PasswordLoginStrategy extends LoginStrategy { password: this.cache.value, }; } + + async logInNewDeviceVerification(deviceVerificationOtp: string): Promise { + const data = this.cache.value; + data.tokenRequest.newDeviceOtp = deviceVerificationOtp; + this.cache.next(data); + + const [authResult] = await this.startLogIn(); + return authResult; + } } diff --git a/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts index ec3ec43134f..546fa0c5fa7 100644 --- a/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts @@ -2,20 +2,24 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; -import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { AdminAuthRequestStorable } from "@bitwarden/common/auth/models/domain/admin-auth-req-storable"; import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { IUserDecryptionOptionsServerResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/user-decryption-options.response"; -import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; +import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; +import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service"; +import { + VaultTimeoutAction, + VaultTimeoutSettingsService, +} from "@bitwarden/common/key-management/vault-timeout"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -23,7 +27,6 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vault-timeout/vault-timeout-settings.service"; import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; import { CsprngArray } from "@bitwarden/common/types/csprng"; import { UserId } from "@bitwarden/common/types/guid"; @@ -61,6 +64,7 @@ describe("SsoLoginStrategy", () => { let billingAccountProfileStateService: MockProxy; let vaultTimeoutSettingsService: MockProxy; let kdfConfigService: MockProxy; + let environmentService: MockProxy; let ssoLoginStrategy: SsoLoginStrategy; let credentials: SsoLoginCredentials; @@ -96,6 +100,7 @@ describe("SsoLoginStrategy", () => { billingAccountProfileStateService = mock(); vaultTimeoutSettingsService = mock(); kdfConfigService = mock(); + environmentService = mock(); tokenService.getTwoFactorToken.mockResolvedValue(null); appIdService.getAppId.mockResolvedValue(deviceId); @@ -140,6 +145,7 @@ describe("SsoLoginStrategy", () => { billingAccountProfileStateService, vaultTimeoutSettingsService, kdfConfigService, + environmentService, ); credentials = new SsoLoginCredentials(ssoCode, ssoCodeVerifier, ssoRedirectUrl, ssoOrgId); }); diff --git a/libs/auth/src/common/login-strategies/sso-login.strategy.ts b/libs/auth/src/common/login-strategies/sso-login.strategy.ts index e9d1e0a6c88..1dd01d6fc75 100644 --- a/libs/auth/src/common/login-strategies/sso-login.strategy.ts +++ b/libs/auth/src/common/login-strategies/sso-login.strategy.ts @@ -3,14 +3,13 @@ import { firstValueFrom, Observable, map, BehaviorSubject } from "rxjs"; import { Jsonify } from "type-fest"; -import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; -import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; -import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { SsoTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/sso-token.request"; import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { HttpStatusCode } from "@bitwarden/common/enums"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; +import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { UserId } from "@bitwarden/common/types/guid"; @@ -108,14 +107,6 @@ export class SsoLoginStrategy extends LoginStrategy { const email = ssoAuthResult.email; const ssoEmail2FaSessionToken = ssoAuthResult.ssoEmail2FaSessionToken; - // Auth guard currently handles redirects for this. - if (ssoAuthResult.forcePasswordReset == ForceSetPasswordReason.AdminForcePasswordReset) { - await this.masterPasswordService.setForceSetPasswordReason( - ssoAuthResult.forcePasswordReset, - ssoAuthResult.userId, - ); - } - this.cache.next({ ...this.cache.value, email, @@ -278,7 +269,8 @@ export class SsoLoginStrategy extends LoginStrategy { // TODO: eventually we post and clean up DB as well once consumed on client await this.authRequestService.clearAdminAuthRequest(userId); - this.platformUtilsService.showToast("success", null, this.i18nService.t("loginApproved")); + // This notification will be picked up by the SsoComponent to handle displaying a toast to the user + this.authRequestService.emitAdminLoginApproved(); } } } diff --git a/libs/auth/src/common/login-strategies/user-api-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/user-api-login.strategy.spec.ts index 2bb41faa0e1..ec017e58c3c 100644 --- a/libs/auth/src/common/login-strategies/user-api-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/user-api-login.strategy.spec.ts @@ -2,14 +2,17 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; -import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; +import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service"; +import { + VaultTimeoutAction, + VaultTimeoutSettingsService, +} from "@bitwarden/common/key-management/vault-timeout"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { Environment, EnvironmentService, @@ -20,7 +23,6 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vault-timeout/vault-timeout-settings.service"; import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; import { CsprngArray } from "@bitwarden/common/types/csprng"; import { UserId } from "@bitwarden/common/types/guid"; @@ -95,7 +97,6 @@ describe("UserApiLoginStrategy", () => { apiLogInStrategy = new UserApiLoginStrategy( cache, - environmentService, keyConnectorService, accountService, masterPasswordService, @@ -113,6 +114,7 @@ describe("UserApiLoginStrategy", () => { billingAccountProfileStateService, vaultTimeoutSettingsService, kdfConfigService, + environmentService, ); credentials = new UserApiLoginCredentials(apiClientId, apiClientSecret); diff --git a/libs/auth/src/common/login-strategies/user-api-login.strategy.ts b/libs/auth/src/common/login-strategies/user-api-login.strategy.ts index ef13e631300..32cd5ceaf40 100644 --- a/libs/auth/src/common/login-strategies/user-api-login.strategy.ts +++ b/libs/auth/src/common/login-strategies/user-api-login.strategy.ts @@ -3,11 +3,10 @@ import { firstValueFrom, BehaviorSubject } from "rxjs"; import { Jsonify } from "type-fest"; -import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { UserApiTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/user-api-token.request"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; -import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; +import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; +import { VaultTimeoutAction } from "@bitwarden/common/key-management/vault-timeout"; import { UserId } from "@bitwarden/common/types/guid"; import { UserApiLoginCredentials } from "../models/domain/login-credentials"; @@ -31,7 +30,6 @@ export class UserApiLoginStrategy extends LoginStrategy { constructor( data: UserApiLoginStrategyData, - private environmentService: EnvironmentService, private keyConnectorService: KeyConnectorService, ...sharedDeps: ConstructorParameters ) { diff --git a/libs/auth/src/common/login-strategies/webauthn-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/webauthn-login.strategy.spec.ts index 9dacce2cf00..aac4a36c24a 100644 --- a/libs/auth/src/common/login-strategies/webauthn-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/webauthn-login.strategy.spec.ts @@ -7,19 +7,22 @@ import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { IUserDecryptionOptionsServerResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/user-decryption-options.response"; -import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service"; import { WebAuthnLoginAssertionResponseRequest } from "@bitwarden/common/auth/services/webauthn-login/request/webauthn-login-assertion-response.request"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service"; +import { + VaultTimeoutAction, + VaultTimeoutSettingsService, +} from "@bitwarden/common/key-management/vault-timeout"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vault-timeout/vault-timeout-settings.service"; import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; import { PrfKey, UserKey } from "@bitwarden/common/types/key"; @@ -50,6 +53,7 @@ describe("WebAuthnLoginStrategy", () => { let billingAccountProfileStateService: MockProxy; let vaultTimeoutSettingsService: MockProxy; let kdfConfigService: MockProxy; + let environmentService: MockProxy; let webAuthnLoginStrategy!: WebAuthnLoginStrategy; @@ -93,6 +97,7 @@ describe("WebAuthnLoginStrategy", () => { billingAccountProfileStateService = mock(); vaultTimeoutSettingsService = mock(); kdfConfigService = mock(); + environmentService = mock(); tokenService.getTwoFactorToken.mockResolvedValue(null); appIdService.getAppId.mockResolvedValue(deviceId); @@ -118,6 +123,7 @@ describe("WebAuthnLoginStrategy", () => { billingAccountProfileStateService, vaultTimeoutSettingsService, kdfConfigService, + environmentService, ); // Create credentials diff --git a/libs/auth/src/common/models/domain/login-credentials.ts b/libs/auth/src/common/models/domain/login-credentials.ts index 72cc7413bec..cc21e5b2505 100644 --- a/libs/auth/src/common/models/domain/login-credentials.ts +++ b/libs/auth/src/common/models/domain/login-credentials.ts @@ -53,9 +53,9 @@ export class AuthRequestLoginCredentials { public email: string, public accessCode: string, public authRequestId: string, - public decryptedUserKey: UserKey, - public decryptedMasterKey: MasterKey, - public decryptedMasterKeyHash: string, + public decryptedUserKey: UserKey | null, + public decryptedMasterKey: MasterKey | null, + public decryptedMasterKeyHash: string | null, public twoFactor?: TokenTwoFactorRequest, ) {} diff --git a/libs/auth/src/common/models/domain/user-decryption-options.ts b/libs/auth/src/common/models/domain/user-decryption-options.ts index 95efe2b0077..00b78064d83 100644 --- a/libs/auth/src/common/models/domain/user-decryption-options.ts +++ b/libs/auth/src/common/models/domain/user-decryption-options.ts @@ -4,6 +4,8 @@ import { Jsonify } from "type-fest"; import { KeyConnectorUserDecryptionOptionResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/key-connector-user-decryption-option.response"; import { TrustedDeviceUserDecryptionOptionResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/trusted-device-user-decryption-option.response"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { IdentityTokenResponse } from "@bitwarden/common/src/auth/models/response/identity-token.response"; /** diff --git a/libs/auth/src/common/services/accounts/lock.service.ts b/libs/auth/src/common/services/accounts/lock.service.ts index 334a795f7bc..1f42873408d 100644 --- a/libs/auth/src/common/services/accounts/lock.service.ts +++ b/libs/auth/src/common/services/accounts/lock.service.ts @@ -1,7 +1,7 @@ import { combineLatest, firstValueFrom, map } from "rxjs"; -import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { VaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout"; import { UserId } from "@bitwarden/common/types/guid"; export abstract class LockService { diff --git a/libs/auth/src/common/services/accounts/lock.services.spec.ts b/libs/auth/src/common/services/accounts/lock.services.spec.ts index eecc3dd787f..27310298028 100644 --- a/libs/auth/src/common/services/accounts/lock.services.spec.ts +++ b/libs/auth/src/common/services/accounts/lock.services.spec.ts @@ -1,6 +1,6 @@ import { mock } from "jest-mock-extended"; -import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; +import { VaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout"; import { mockAccountServiceWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; diff --git a/libs/auth/src/common/services/auth-request/auth-request-api.service.ts b/libs/auth/src/common/services/auth-request/auth-request-api.service.ts index 180e0079396..c9fec1400c9 100644 --- a/libs/auth/src/common/services/auth-request/auth-request-api.service.ts +++ b/libs/auth/src/common/services/auth-request/auth-request-api.service.ts @@ -16,7 +16,7 @@ export class DefaultAuthRequestApiService implements AuthRequestApiService { const path = `/auth-requests/${requestId}`; const response = await this.apiService.send("GET", path, null, true, true); - return response; + return new AuthRequestResponse(response); } catch (e: unknown) { this.logService.error(e); throw e; @@ -28,7 +28,7 @@ export class DefaultAuthRequestApiService implements AuthRequestApiService { const path = `/auth-requests/${requestId}/response?code=${accessCode}`; const response = await this.apiService.send("GET", path, null, false, true); - return response; + return new AuthRequestResponse(response); } catch (e: unknown) { this.logService.error(e); throw e; @@ -45,7 +45,7 @@ export class DefaultAuthRequestApiService implements AuthRequestApiService { true, ); - return response; + return new AuthRequestResponse(response); } catch (e: unknown) { this.logService.error(e); throw e; @@ -54,9 +54,22 @@ export class DefaultAuthRequestApiService implements AuthRequestApiService { async postAuthRequest(request: AuthRequest): Promise { try { - const response = await this.apiService.send("POST", "/auth-requests/", request, false, true); + // Submit the current device identifier in the header as well as in the POST body. + // The value in the header will be used to build the request context and ensure that the resulting + // notifications have the current device as a source. + const response = await this.apiService.send( + "POST", + "/auth-requests/", + request, + false, + true, + null, + (headers) => { + headers.set("Device-Identifier", request.deviceIdentifier); + }, + ); - return response; + return new AuthRequestResponse(response); } catch (e: unknown) { this.logService.error(e); throw e; diff --git a/libs/auth/src/common/services/auth-request/auth-request.service.spec.ts b/libs/auth/src/common/services/auth-request/auth-request.service.spec.ts index 86b2a1dd3b6..1e9c46db0ee 100644 --- a/libs/auth/src/common/services/auth-request/auth-request.service.spec.ts +++ b/libs/auth/src/common/services/auth-request/auth-request.service.spec.ts @@ -2,10 +2,10 @@ import { mock } from "jest-mock-extended"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; -import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service"; import { AuthRequestPushNotification } from "@bitwarden/common/models/response/notification.response"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; diff --git a/libs/auth/src/common/services/auth-request/auth-request.service.ts b/libs/auth/src/common/services/auth-request/auth-request.service.ts index 4bc0397b43a..1da5d2f1882 100644 --- a/libs/auth/src/common/services/auth-request/auth-request.service.ts +++ b/libs/auth/src/common/services/auth-request/auth-request.service.ts @@ -5,13 +5,13 @@ import { Jsonify } from "type-fest"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { AdminAuthRequestStorable } from "@bitwarden/common/auth/models/domain/admin-auth-req-storable"; import { PasswordlessAuthRequest } from "@bitwarden/common/auth/models/request/passwordless-auth.request"; import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { AuthRequestPushNotification } from "@bitwarden/common/models/response/notification.response"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; @@ -43,6 +43,10 @@ export class AuthRequestService implements AuthRequestServiceAbstraction { private authRequestPushNotificationSubject = new Subject(); authRequestPushNotification$: Observable; + // Observable emission is used to trigger a toast in consuming components + private adminLoginApprovedSubject = new Subject(); + adminLoginApproved$: Observable; + constructor( private appIdService: AppIdService, private accountService: AccountService, @@ -53,6 +57,7 @@ export class AuthRequestService implements AuthRequestServiceAbstraction { private stateProvider: StateProvider, ) { this.authRequestPushNotification$ = this.authRequestPushNotificationSubject.asObservable(); + this.adminLoginApproved$ = this.adminLoginApprovedSubject.asObservable(); } async getAdminAuthRequest(userId: UserId): Promise { @@ -207,4 +212,8 @@ export class AuthRequestService implements AuthRequestServiceAbstraction { async getFingerprintPhrase(email: string, publicKey: Uint8Array): Promise { return (await this.keyService.getFingerprint(email.toLowerCase(), publicKey)).join("-"); } + + emitAdminLoginApproved(): void { + this.adminLoginApprovedSubject.next(); + } } diff --git a/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.spec.ts b/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.spec.ts new file mode 100644 index 00000000000..82ac0f1006d --- /dev/null +++ b/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.spec.ts @@ -0,0 +1,111 @@ +import { signal } from "@angular/core"; +import { TestBed } from "@angular/core/testing"; + +import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service"; +import { AuthRequestType } from "@bitwarden/common/auth/enums/auth-request-type"; +import { AuthRequest } from "@bitwarden/common/auth/models/request/auth.request"; +import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; +import { LoginViaAuthRequestView } from "@bitwarden/common/auth/models/view/login-via-auth-request.view"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; + +import { LoginViaAuthRequestCacheService } from "./default-login-via-auth-request-cache.service"; + +describe("LoginViaAuthRequestCache", () => { + let service: LoginViaAuthRequestCacheService; + let testBed: TestBed; + + const cacheSignal = signal(null); + const getCacheSignal = jest.fn().mockReturnValue(cacheSignal); + const getFeatureFlag = jest.fn().mockResolvedValue(false); + const cacheSetMock = jest.spyOn(cacheSignal, "set"); + + beforeEach(() => { + getCacheSignal.mockClear(); + getFeatureFlag.mockClear(); + cacheSetMock.mockClear(); + + testBed = TestBed.configureTestingModule({ + providers: [ + { provide: ViewCacheService, useValue: { signal: getCacheSignal } }, + { provide: ConfigService, useValue: { getFeatureFlag } }, + LoginViaAuthRequestCacheService, + ], + }); + }); + + describe("feature enabled", () => { + beforeEach(() => { + getFeatureFlag.mockResolvedValue(true); + }); + + it("`getCachedLoginViaAuthRequestView` returns the cached data", async () => { + cacheSignal.set({ ...buildAuthenticMockAuthView() }); + service = testBed.inject(LoginViaAuthRequestCacheService); + await service.init(); + + expect(service.getCachedLoginViaAuthRequestView()).toEqual({ + ...buildAuthenticMockAuthView(), + }); + }); + + it("updates the signal value", async () => { + service = testBed.inject(LoginViaAuthRequestCacheService); + await service.init(); + + const parameters = buildAuthenticMockAuthView(); + + service.cacheLoginView( + parameters.authRequest, + parameters.authRequestResponse, + parameters.fingerprintPhrase, + { publicKey: new Uint8Array(), privateKey: new Uint8Array() }, + ); + + expect(cacheSignal.set).toHaveBeenCalledWith(parameters); + }); + }); + + describe("feature disabled", () => { + beforeEach(async () => { + cacheSignal.set({ ...buildAuthenticMockAuthView() } as LoginViaAuthRequestView); + getFeatureFlag.mockResolvedValue(false); + cacheSetMock.mockClear(); + + service = testBed.inject(LoginViaAuthRequestCacheService); + await service.init(); + }); + + it("`getCachedCipherView` returns null", () => { + expect(service.getCachedLoginViaAuthRequestView()).toBeNull(); + }); + + it("does not update the signal value", () => { + const params = buildAuthenticMockAuthView(); + + service.cacheLoginView( + params.authRequest, + params.authRequestResponse, + params.fingerprintPhrase, + { publicKey: new Uint8Array(), privateKey: new Uint8Array() }, + ); + + expect(cacheSignal.set).not.toHaveBeenCalled(); + }); + }); + + const buildAuthenticMockAuthView = () => { + return { + fingerprintPhrase: "", + privateKey: "", + publicKey: "", + authRequest: new AuthRequest( + "test@gmail.com", + "deviceIdentifier", + "publicKey", + AuthRequestType.Unlock, + "accessCode", + ), + authRequestResponse: new AuthRequestResponse({}), + }; + }; +}); diff --git a/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.ts b/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.ts new file mode 100644 index 00000000000..30ba8879546 --- /dev/null +++ b/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.ts @@ -0,0 +1,88 @@ +import { inject, Injectable, WritableSignal } from "@angular/core"; + +import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service"; +import { AuthRequest } from "@bitwarden/common/auth/models/request/auth.request"; +import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; +import { LoginViaAuthRequestView } from "@bitwarden/common/auth/models/view/login-via-auth-request.view"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; + +const LOGIN_VIA_AUTH_CACHE_KEY = "login-via-auth-request-form-cache"; + +/** + * This is a cache service used for the login via auth request component. + * + * There is sensitive information stored temporarily here. Cache will be cleared + * after 2 minutes. + */ +@Injectable() +export class LoginViaAuthRequestCacheService { + private viewCacheService: ViewCacheService = inject(ViewCacheService); + private configService: ConfigService = inject(ConfigService); + + /** True when the `PM9112_DeviceApproval` flag is enabled */ + private featureEnabled: boolean = false; + + private defaultLoginViaAuthRequestCache: WritableSignal = + this.viewCacheService.signal({ + key: LOGIN_VIA_AUTH_CACHE_KEY, + initialValue: null, + deserializer: LoginViaAuthRequestView.fromJSON, + }); + + constructor() {} + + /** + * Must be called once before interacting with the cached data, otherwise methods will be noop. + */ + async init() { + this.featureEnabled = await this.configService.getFeatureFlag( + FeatureFlag.PM9112_DeviceApprovalPersistence, + ); + } + + /** + * Update the cache with the new LoginView. + */ + cacheLoginView( + authRequest: AuthRequest, + authRequestResponse: AuthRequestResponse, + fingerprintPhrase: string, + keys: { privateKey: Uint8Array | undefined; publicKey: Uint8Array | undefined }, + ): void { + if (!this.featureEnabled) { + return; + } + + // When the keys get stored they should be converted to a B64 string to ensure + // data can be properly formed when json-ified. If not done, they are not stored properly and + // will not be parsable by the cryptography library after coming out of storage. + this.defaultLoginViaAuthRequestCache.set({ + authRequest, + authRequestResponse, + fingerprintPhrase, + privateKey: keys.privateKey ? Utils.fromBufferToB64(keys.privateKey.buffer) : undefined, + publicKey: keys.publicKey ? Utils.fromBufferToB64(keys.publicKey.buffer) : undefined, + } as LoginViaAuthRequestView); + } + + clearCacheLoginView(): void { + if (!this.featureEnabled) { + return; + } + + this.defaultLoginViaAuthRequestCache.set(null); + } + + /** + * Returns the cached LoginViaAuthRequestView when available. + */ + getCachedLoginViaAuthRequestView(): LoginViaAuthRequestView | null { + if (!this.featureEnabled) { + return null; + } + + return this.defaultLoginViaAuthRequestCache(); + } +} diff --git a/libs/auth/src/common/services/index.ts b/libs/auth/src/common/services/index.ts index d1cedebcf36..73d31799b7e 100644 --- a/libs/auth/src/common/services/index.ts +++ b/libs/auth/src/common/services/index.ts @@ -4,6 +4,6 @@ export * from "./login-strategies/login-strategy.service"; export * from "./user-decryption-options/user-decryption-options.service"; export * from "./auth-request/auth-request.service"; export * from "./auth-request/auth-request-api.service"; -export * from "./register-route.service"; export * from "./accounts/lock.service"; export * from "./login-success-handler/default-login-success-handler.service"; +export * from "./sso-redirect/sso-url.service"; diff --git a/libs/auth/src/common/services/login-email/login-email.service.ts b/libs/auth/src/common/services/login-email/login-email.service.ts index feb0cba1bdc..aa13afd5004 100644 --- a/libs/auth/src/common/services/login-email/login-email.service.ts +++ b/libs/auth/src/common/services/login-email/login-email.service.ts @@ -6,6 +6,8 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { GlobalState, KeyDefinition, diff --git a/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts b/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts index 5fcbefbef2f..1dc05cafa00 100644 --- a/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts +++ b/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts @@ -2,10 +2,7 @@ import { MockProxy, mock } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; -import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; @@ -14,11 +11,16 @@ import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/ide import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response"; import { PreloginResponse } from "@bitwarden/common/auth/models/response/prelogin.response"; -import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; +import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; +import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service"; +import { + VaultTimeoutAction, + VaultTimeoutSettingsService, +} from "@bitwarden/common/key-management/vault-timeout"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -321,4 +323,67 @@ describe("LoginStrategyService", () => { `PBKDF2 iterations must be at least ${PBKDF2KdfConfig.PRELOGIN_ITERATIONS_MIN}, but was ${PBKDF2KdfConfig.PRELOGIN_ITERATIONS_MIN - 1}; possible pre-login downgrade attack detected.`, ); }); + + it("returns an AuthResult on successful new device verification", async () => { + const credentials = new PasswordLoginCredentials("EMAIL", "MASTER_PASSWORD"); + const deviceVerificationOtp = "123456"; + + // Setup initial login and device verification response + apiService.postPrelogin.mockResolvedValue( + new PreloginResponse({ + Kdf: KdfType.Argon2id, + KdfIterations: 2, + KdfMemory: 16, + KdfParallelism: 1, + }), + ); + + apiService.postIdentityToken.mockResolvedValueOnce( + new IdentityTwoFactorResponse({ + TwoFactorProviders: ["0"], + TwoFactorProviders2: { 0: null }, + error: "invalid_grant", + error_description: "Two factor required.", + email: undefined, + ssoEmail2faSessionToken: undefined, + }), + ); + + await sut.logIn(credentials); + + // Successful device verification login + apiService.postIdentityToken.mockResolvedValueOnce( + new IdentityTokenResponse({ + ForcePasswordReset: false, + Kdf: KdfType.Argon2id, + KdfIterations: 2, + KdfMemory: 16, + KdfParallelism: 1, + Key: "KEY", + PrivateKey: "PRIVATE_KEY", + ResetMasterPassword: false, + access_token: "ACCESS_TOKEN", + expires_in: 3600, + refresh_token: "REFRESH_TOKEN", + scope: "api offline_access", + token_type: "Bearer", + }), + ); + + tokenService.decodeAccessToken.calledWith("ACCESS_TOKEN").mockResolvedValue({ + sub: "USER_ID", + name: "NAME", + email: "EMAIL", + premium: false, + }); + + const result = await sut.logInNewDeviceVerification(deviceVerificationOtp); + + expect(result).toBeInstanceOf(AuthResult); + expect(apiService.postIdentityToken).toHaveBeenCalledWith( + expect.objectContaining({ + newDeviceOtp: deviceVerificationOtp, + }), + ); + }); }); diff --git a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts index 1f98a117c88..2adea85a9ec 100644 --- a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts +++ b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { combineLatestWith, distinctUntilChanged, @@ -12,21 +10,22 @@ import { } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; +import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.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 { PreloginRequest } from "@bitwarden/common/models/request/prelogin.request"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -35,7 +34,6 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { TaskSchedulerService, ScheduledTaskNames } from "@bitwarden/common/platform/scheduling"; import { GlobalState, GlobalStateProvider } from "@bitwarden/common/platform/state"; -import { DeviceTrustServiceAbstraction } from "@bitwarden/common/src/auth/abstractions/device-trust.service.abstraction"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; import { MasterKey } from "@bitwarden/common/types/key"; import { @@ -49,12 +47,24 @@ import { import { AuthRequestServiceAbstraction, LoginStrategyServiceAbstraction } from "../../abstractions"; import { InternalUserDecryptionOptionsServiceAbstraction } from "../../abstractions/user-decryption-options.service.abstraction"; -import { AuthRequestLoginStrategy } from "../../login-strategies/auth-request-login.strategy"; +import { + AuthRequestLoginStrategy, + AuthRequestLoginStrategyData, +} from "../../login-strategies/auth-request-login.strategy"; import { LoginStrategy } from "../../login-strategies/login.strategy"; -import { PasswordLoginStrategy } from "../../login-strategies/password-login.strategy"; -import { SsoLoginStrategy } from "../../login-strategies/sso-login.strategy"; -import { UserApiLoginStrategy } from "../../login-strategies/user-api-login.strategy"; -import { WebAuthnLoginStrategy } from "../../login-strategies/webauthn-login.strategy"; +import { + PasswordLoginStrategy, + PasswordLoginStrategyData, +} from "../../login-strategies/password-login.strategy"; +import { SsoLoginStrategy, SsoLoginStrategyData } from "../../login-strategies/sso-login.strategy"; +import { + UserApiLoginStrategy, + UserApiLoginStrategyData, +} from "../../login-strategies/user-api-login.strategy"; +import { + WebAuthnLoginStrategy, + WebAuthnLoginStrategyData, +} from "../../login-strategies/webauthn-login.strategy"; import { UserApiLoginCredentials, PasswordLoginCredentials, @@ -74,14 +84,15 @@ import { const sessionTimeoutLength = 5 * 60 * 1000; // 5 minutes export class LoginStrategyService implements LoginStrategyServiceAbstraction { - private sessionTimeoutSubscription: Subscription; + private sessionTimeoutSubscription: Subscription | undefined; private currentAuthnTypeState: GlobalState; private loginStrategyCacheState: GlobalState; private loginStrategyCacheExpirationState: GlobalState; - private authRequestPushNotificationState: GlobalState; - private twoFactorTimeoutSubject = new BehaviorSubject(false); + private authRequestPushNotificationState: GlobalState; + private authenticationTimeoutSubject = new BehaviorSubject(false); - twoFactorTimeout$: Observable = this.twoFactorTimeoutSubject.asObservable(); + authenticationSessionTimeout$: Observable = + this.authenticationTimeoutSubject.asObservable(); private loginStrategy$: Observable< | UserApiLoginStrategy @@ -130,7 +141,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { this.taskSchedulerService.registerTaskHandler( ScheduledTaskNames.loginStrategySessionTimeout, async () => { - this.twoFactorTimeoutSubject.next(true); + this.authenticationTimeoutSubject.next(true); try { await this.clearCache(); } catch (e) { @@ -151,7 +162,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { async getEmail(): Promise { const strategy = await firstValueFrom(this.loginStrategy$); - if ("email$" in strategy) { + if (strategy && "email$" in strategy) { return await firstValueFrom(strategy.email$); } return null; @@ -160,7 +171,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { async getMasterPasswordHash(): Promise { const strategy = await firstValueFrom(this.loginStrategy$); - if ("serverMasterKeyHash$" in strategy) { + if (strategy && "serverMasterKeyHash$" in strategy) { return await firstValueFrom(strategy.serverMasterKeyHash$); } return null; @@ -169,7 +180,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { async getSsoEmail2FaSessionToken(): Promise { const strategy = await firstValueFrom(this.loginStrategy$); - if ("ssoEmail2FaSessionToken$" in strategy) { + if (strategy && "ssoEmail2FaSessionToken$" in strategy) { return await firstValueFrom(strategy.ssoEmail2FaSessionToken$); } return null; @@ -178,7 +189,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { async getAccessCode(): Promise { const strategy = await firstValueFrom(this.loginStrategy$); - if ("accessCode$" in strategy) { + if (strategy && "accessCode$" in strategy) { return await firstValueFrom(strategy.accessCode$); } return null; @@ -187,7 +198,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { async getAuthRequestId(): Promise { const strategy = await firstValueFrom(this.loginStrategy$); - if ("authRequestId$" in strategy) { + if (strategy && "authRequestId$" in strategy) { return await firstValueFrom(strategy.authRequestId$); } return null; @@ -202,7 +213,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { | WebAuthnLoginCredentials, ): Promise { await this.clearCache(); - this.twoFactorTimeoutSubject.next(false); + this.authenticationTimeoutSubject.next(false); await this.currentAuthnTypeState.update((_) => credentials.type); @@ -215,16 +226,19 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { // If the popup uses its own instance of this service, this can be removed. const ownedCredentials = { ...credentials }; - const result = await strategy.logIn(ownedCredentials as any); + const result = await strategy?.logIn(ownedCredentials as any); - if (result != null && !result.requiresTwoFactor) { + if (result != null && !result.requiresTwoFactor && !result.requiresDeviceVerification) { await this.clearCache(); } else { - // Cache the strategy data so we can attempt again later with 2fa. Cache supports different contexts - await this.loginStrategyCacheState.update((_) => strategy.exportCache()); + // Cache the strategy data so we can attempt again later with 2fa or device verification + await this.loginStrategyCacheState.update((_) => strategy?.exportCache() ?? null); await this.startSessionTimeout(); } + if (!result) { + throw new Error("No auth result returned"); + } return result; } @@ -258,9 +272,46 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { } } + /** + * Sends a token request to the server with the provided device verification OTP. + * Returns an error if no session data is found or if the current login strategy does not support device verification. + * @param deviceVerificationOtp The OTP to send to the server for device verification. + * @returns The result of the token request. + */ + async logInNewDeviceVerification(deviceVerificationOtp: string): Promise { + if (!(await this.isSessionValid())) { + throw new Error(this.i18nService.t("sessionTimeout")); + } + + const strategy = await firstValueFrom(this.loginStrategy$); + if (strategy == null) { + throw new Error("No login strategy found."); + } + + if (!("logInNewDeviceVerification" in strategy)) { + throw new Error("Current login strategy does not support device verification."); + } + + try { + const result = await strategy.logInNewDeviceVerification(deviceVerificationOtp); + + // Only clear cache if device verification succeeds + if (result !== null && !result.requiresDeviceVerification) { + await this.clearCache(); + } + return result; + } catch (e) { + // Clear the cache if there is an unhandled client-side error + if (!(e instanceof ErrorResponse)) { + await this.clearCache(); + } + throw e; + } + } + async makePreloginKey(masterPassword: string, email: string): Promise { email = email.trim().toLowerCase(); - let kdfConfig: KdfConfig = null; + let kdfConfig: KdfConfig | undefined; try { const preloginResponse = await this.apiService.postPrelogin(new PreloginRequest(email)); if (preloginResponse != null) { @@ -273,12 +324,15 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { preloginResponse.kdfParallelism, ); } - } catch (e) { + } catch (e: any) { if (e == null || e.statusCode !== 404) { throw e; } } + if (!kdfConfig) { + throw new Error("KDF config is required"); + } kdfConfig.validateKdfConfigForPrelogin(); return await this.keyService.makeMasterKey(masterPassword, email, kdfConfig); @@ -287,7 +341,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { private async clearCache(): Promise { await this.currentAuthnTypeState.update((_) => null); await this.loginStrategyCacheState.update((_) => null); - this.twoFactorTimeoutSubject.next(false); + this.authenticationTimeoutSubject.next(false); await this.clearSessionTimeout(); } @@ -348,6 +402,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { this.billingAccountProfileStateService, this.vaultTimeoutSettingsService, this.kdfConfigService, + this.environmentService, ]; return source.pipe( @@ -358,7 +413,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { switch (strategy) { case AuthenticationType.Password: return new PasswordLoginStrategy( - data?.password, + data?.password ?? new PasswordLoginStrategyData(), this.passwordStrengthService, this.policyService, this, @@ -366,7 +421,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { ); case AuthenticationType.Sso: return new SsoLoginStrategy( - data?.sso, + data?.sso ?? new SsoLoginStrategyData(), this.keyConnectorService, this.deviceTrustService, this.authRequestService, @@ -375,19 +430,21 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { ); case AuthenticationType.UserApiKey: return new UserApiLoginStrategy( - data?.userApiKey, - this.environmentService, + data?.userApiKey ?? new UserApiLoginStrategyData(), this.keyConnectorService, ...sharedDeps, ); case AuthenticationType.AuthRequest: return new AuthRequestLoginStrategy( - data?.authRequest, + data?.authRequest ?? new AuthRequestLoginStrategyData(), this.deviceTrustService, ...sharedDeps, ); case AuthenticationType.WebAuthn: - return new WebAuthnLoginStrategy(data?.webAuthn, ...sharedDeps); + return new WebAuthnLoginStrategy( + data?.webAuthn ?? new WebAuthnLoginStrategyData(), + ...sharedDeps, + ); } }), ); diff --git a/libs/auth/src/common/services/pin/pin.service.implementation.ts b/libs/auth/src/common/services/pin/pin.service.implementation.ts index 01fc77e4a03..0f6ac05f381 100644 --- a/libs/auth/src/common/services/pin/pin.service.implementation.ts +++ b/libs/auth/src/common/services/pin/pin.service.implementation.ts @@ -3,9 +3,9 @@ import { firstValueFrom, map } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { MasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; @@ -99,7 +99,7 @@ export class PinService implements PinServiceAbstraction { private stateService: StateService, ) {} - async getPinKeyEncryptedUserKeyPersistent(userId: UserId): Promise { + async getPinKeyEncryptedUserKeyPersistent(userId: UserId): Promise { this.validateUserId(userId, "Cannot get pinKeyEncryptedUserKeyPersistent."); return EncString.fromJSON( @@ -137,7 +137,7 @@ export class PinService implements PinServiceAbstraction { await this.stateProvider.setUserState(PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT, null, userId); } - async getPinKeyEncryptedUserKeyEphemeral(userId: UserId): Promise { + async getPinKeyEncryptedUserKeyEphemeral(userId: UserId): Promise { this.validateUserId(userId, "Cannot get pinKeyEncryptedUserKeyEphemeral."); return EncString.fromJSON( @@ -210,7 +210,7 @@ export class PinService implements PinServiceAbstraction { } } - async getUserKeyEncryptedPin(userId: UserId): Promise { + async getUserKeyEncryptedPin(userId: UserId): Promise { this.validateUserId(userId, "Cannot get userKeyEncryptedPin."); return EncString.fromJSON( @@ -242,7 +242,7 @@ export class PinService implements PinServiceAbstraction { return await this.encryptService.encrypt(pin, userKey); } - async getOldPinKeyEncryptedMasterKey(userId: UserId): Promise { + async getOldPinKeyEncryptedMasterKey(userId: UserId): Promise { this.validateUserId(userId, "Cannot get oldPinKeyEncryptedMasterKey."); return await firstValueFrom( diff --git a/libs/auth/src/common/services/pin/pin.service.spec.ts b/libs/auth/src/common/services/pin/pin.service.spec.ts index d254be4e875..794d08b63b2 100644 --- a/libs/auth/src/common/services/pin/pin.service.spec.ts +++ b/libs/auth/src/common/services/pin/pin.service.spec.ts @@ -1,8 +1,8 @@ import { mock } from "jest-mock-extended"; -import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; diff --git a/libs/auth/src/common/services/register-route.service.ts b/libs/auth/src/common/services/register-route.service.ts deleted file mode 100644 index 5bc09db699e..00000000000 --- a/libs/auth/src/common/services/register-route.service.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Observable, map } from "rxjs"; - -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; - -// This is a temporary service to determine the correct route to use for registration based on the email verification feature flag. -export class RegisterRouteService { - constructor(private configService: ConfigService) {} - - registerRoute$(): Observable { - return this.configService.getFeatureFlag$(FeatureFlag.EmailVerification).pipe( - map((emailVerificationEnabled) => { - if (emailVerificationEnabled) { - return "/signup"; - } else { - return "/register"; - } - }), - ); - } -} diff --git a/libs/auth/src/common/services/sso-redirect/sso-url.service.spec.ts b/libs/auth/src/common/services/sso-redirect/sso-url.service.spec.ts new file mode 100644 index 00000000000..074c3a1e0b1 --- /dev/null +++ b/libs/auth/src/common/services/sso-redirect/sso-url.service.spec.ts @@ -0,0 +1,95 @@ +import { ClientType } from "@bitwarden/common/enums"; + +import { DESKTOP_SSO_CALLBACK, SsoUrlService } from "./sso-url.service"; + +describe("SsoUrlService", () => { + let service: SsoUrlService; + + beforeEach(() => { + service = new SsoUrlService(); + }); + + it("should build Desktop SSO URL correctly", () => { + const baseUrl = "https://web-vault.bitwarden.com"; + const clientType = ClientType.Desktop; + const redirectUri = DESKTOP_SSO_CALLBACK; + const state = "abc123"; + const codeChallenge = "xyz789"; + const email = "test@bitwarden.com"; + + const expectedUrl = `${baseUrl}/#/sso?clientId=desktop&redirectUri=${encodeURIComponent(redirectUri)}&state=${state}&codeChallenge=${codeChallenge}&email=${encodeURIComponent(email)}`; + + const result = service.buildSsoUrl( + baseUrl, + clientType, + redirectUri, + state, + codeChallenge, + email, + ); + expect(result).toBe(expectedUrl); + }); + + it("should build Desktop localhost callback SSO URL correctly", () => { + const baseUrl = "https://web-vault.bitwarden.com"; + const clientType = ClientType.Desktop; + const redirectUri = `https://localhost:1000`; + const state = "abc123"; + const codeChallenge = "xyz789"; + const email = "test@bitwarden.com"; + + const expectedUrl = `${baseUrl}/#/sso?clientId=desktop&redirectUri=${encodeURIComponent(redirectUri)}&state=${state}&codeChallenge=${codeChallenge}&email=${encodeURIComponent(email)}`; + + const result = service.buildSsoUrl( + baseUrl, + clientType, + redirectUri, + state, + codeChallenge, + email, + ); + expect(result).toBe(expectedUrl); + }); + + it("should build Extension SSO URL correctly", () => { + const baseUrl = "https://web-vault.bitwarden.com"; + const clientType = ClientType.Browser; + const redirectUri = baseUrl + "/sso-connector.html"; + const state = "abc123"; + const codeChallenge = "xyz789"; + const email = "test@bitwarden.com"; + + const expectedUrl = `${baseUrl}/#/sso?clientId=browser&redirectUri=${encodeURIComponent(redirectUri)}&state=${state}&codeChallenge=${codeChallenge}&email=${encodeURIComponent(email)}`; + + const result = service.buildSsoUrl( + baseUrl, + clientType, + redirectUri, + state, + codeChallenge, + email, + ); + expect(result).toBe(expectedUrl); + }); + + it("should build CLI SSO URL correctly", () => { + const baseUrl = "https://web-vault.bitwarden.com"; + const clientType = ClientType.Cli; + const redirectUri = "https://localhost:1000"; + const state = "abc123"; + const codeChallenge = "xyz789"; + const email = "test@bitwarden.com"; + + const expectedUrl = `${baseUrl}/#/sso?clientId=cli&redirectUri=${encodeURIComponent(redirectUri)}&state=${state}&codeChallenge=${codeChallenge}&email=${encodeURIComponent(email)}`; + + const result = service.buildSsoUrl( + baseUrl, + clientType, + redirectUri, + state, + codeChallenge, + email, + ); + expect(result).toBe(expectedUrl); + }); +}); diff --git a/libs/auth/src/common/services/sso-redirect/sso-url.service.ts b/libs/auth/src/common/services/sso-redirect/sso-url.service.ts new file mode 100644 index 00000000000..667a27ad598 --- /dev/null +++ b/libs/auth/src/common/services/sso-redirect/sso-url.service.ts @@ -0,0 +1,41 @@ +import { ClientType } from "@bitwarden/common/enums"; + +export const DESKTOP_SSO_CALLBACK: string = "bitwarden://sso-callback"; + +export class SsoUrlService { + /** + * Builds a URL for redirecting users to the web app SSO component to complete SSO + * @param webAppUrl The URL of the web app + * @param clientType The client type that is initiating SSO, which will drive how the response is handled + * @param redirectUri The redirect URI or callback that will receive the SSO code after authentication + * @param state A state value that will be peristed through the SSO flow + * @param codeChallenge A challenge value that will be used to verify the SSO code after authentication + * @param email The optional email adddress of the user initiating SSO, which will be used to look up the org SSO identifier + * @returns The URL for redirecting users to the web app SSO component + */ + buildSsoUrl( + webAppUrl: string, + clientType: ClientType, + redirectUri: string, + state: string, + codeChallenge: string, + email?: string, + ): string { + let url = + webAppUrl + + "/#/sso?clientId=" + + clientType + + "&redirectUri=" + + encodeURIComponent(redirectUri) + + "&state=" + + state + + "&codeChallenge=" + + codeChallenge; + + if (email) { + url += "&email=" + encodeURIComponent(email); + } + + return url; + } +} diff --git a/libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.ts b/libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.ts index ffb660e6a7f..7c44a6f1682 100644 --- a/libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.ts +++ b/libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.ts @@ -8,6 +8,8 @@ import { USER_DECRYPTION_OPTIONS_DISK, UserKeyDefinition, } from "@bitwarden/common/platform/state"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { UserId } from "@bitwarden/common/src/types/guid"; import { InternalUserDecryptionOptionsServiceAbstraction } from "../../abstractions/user-decryption-options.service.abstraction"; diff --git a/libs/auth/src/common/utilities/decode-jwt-token-to-json.utility.ts b/libs/auth/src/common/utilities/decode-jwt-token-to-json.utility.ts index 717e80b110d..24b3adacc21 100644 --- a/libs/auth/src/common/utilities/decode-jwt-token-to-json.utility.ts +++ b/libs/auth/src/common/utilities/decode-jwt-token-to-json.utility.ts @@ -18,6 +18,8 @@ export function decodeJwtTokenToJson(jwtToken: string): any { try { // Attempt to decode from URL-safe Base64 to UTF-8 decodedPayloadJSON = Utils.fromUrlB64ToUtf8(encodedPayload); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (decodingError) { throw new Error("Cannot decode the token"); } @@ -26,6 +28,8 @@ export function decodeJwtTokenToJson(jwtToken: string): any { // Attempt to parse the JSON payload const decodedToken = JSON.parse(decodedPayloadJSON); return decodedToken; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (jsonError) { throw new Error("Cannot parse the token's payload into JSON"); } diff --git a/libs/auth/test.setup.ts b/libs/auth/test.setup.ts index 6be6e7b8dd1..159c28d2be5 100644 --- a/libs/auth/test.setup.ts +++ b/libs/auth/test.setup.ts @@ -1,5 +1,5 @@ import { webcrypto } from "crypto"; -import "jest-preset-angular/setup-jest"; +import "@bitwarden/ui-common/setup-jest"; Object.defineProperty(window, "CSS", { value: null }); Object.defineProperty(window, "getComputedStyle", { diff --git a/libs/auth/tsconfig.json b/libs/auth/tsconfig.json index 9be942d38de..8d08522ffce 100644 --- a/libs/auth/tsconfig.json +++ b/libs/auth/tsconfig.json @@ -4,17 +4,18 @@ "resolveJsonModule": true, "paths": { "@bitwarden/admin-console/common": ["../admin-console/src/common"], - "@bitwarden/auth/common": ["../auth/src/common"], - "@bitwarden/auth/angular": ["../auth/src/angular"], "@bitwarden/angular/*": ["../angular/src/*"], + "@bitwarden/auth/angular": ["../auth/src/angular"], + "@bitwarden/auth/common": ["../auth/src/common"], "@bitwarden/common/*": ["../common/src/*"], "@bitwarden/components": ["../components/src"], - "@bitwarden/key-management": ["../key-management/src"], - "@bitwarden/platform": ["../platform/src"], "@bitwarden/generator-core": ["../tools/generator/core/src"], "@bitwarden/generator-history": ["../tools/generator/extensions/history/src"], "@bitwarden/generator-legacy": ["../tools/generator/extensions/legacy/src"], - "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"] + "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"], + "@bitwarden/key-management": ["../key-management/src"], + "@bitwarden/platform": ["../platform/src"], + "@bitwarden/ui-common": ["../ui/common/src"] } }, "include": ["src", "spec"], diff --git a/libs/billing/test.setup.ts b/libs/billing/test.setup.ts index 6be6e7b8dd1..159c28d2be5 100644 --- a/libs/billing/test.setup.ts +++ b/libs/billing/test.setup.ts @@ -1,5 +1,5 @@ import { webcrypto } from "crypto"; -import "jest-preset-angular/setup-jest"; +import "@bitwarden/ui-common/setup-jest"; Object.defineProperty(window, "CSS", { value: null }); Object.defineProperty(window, "getComputedStyle", { diff --git a/libs/common/package.json b/libs/common/package.json index 5e0f5ae20c6..ad2771e2fff 100644 --- a/libs/common/package.json +++ b/libs/common/package.json @@ -15,6 +15,7 @@ "scripts": { "clean": "rimraf dist", "build": "npm run clean && tsc", - "build:watch": "npm run clean && tsc -watch" + "build:watch": "npm run clean && tsc -watch", + "test": "jest" } } diff --git a/libs/common/spec/fake-account-service.ts b/libs/common/spec/fake-account-service.ts index 05e44d5db18..ba48181faa2 100644 --- a/libs/common/spec/fake-account-service.ts +++ b/libs/common/spec/fake-account-service.ts @@ -1,7 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { mock } from "jest-mock-extended"; -import { ReplaySubject, combineLatest, map } from "rxjs"; +import { ReplaySubject, combineLatest, map, Observable } from "rxjs"; import { Account, AccountInfo, AccountService } from "../src/auth/abstractions/account.service"; import { UserId } from "../src/types/guid"; @@ -35,6 +35,8 @@ export class FakeAccountService implements AccountService { activeAccountSubject = new ReplaySubject(1); // eslint-disable-next-line rxjs/no-exposed-subjects -- test class accountActivitySubject = new ReplaySubject>(1); + // eslint-disable-next-line rxjs/no-exposed-subjects -- test class + accountVerifyDevicesSubject = new ReplaySubject(1); private _activeUserId: UserId; get activeUserId() { return this._activeUserId; @@ -42,6 +44,7 @@ export class FakeAccountService implements AccountService { accounts$ = this.accountsSubject.asObservable(); activeAccount$ = this.activeAccountSubject.asObservable(); accountActivity$ = this.accountActivitySubject.asObservable(); + accountVerifyNewDeviceLogin$ = this.accountVerifyDevicesSubject.asObservable(); get sortedUserIds$() { return this.accountActivity$.pipe( map((activity) => { @@ -52,7 +55,7 @@ export class FakeAccountService implements AccountService { }), ); } - get nextUpAccount$() { + get nextUpAccount$(): Observable { return combineLatest([this.accounts$, this.activeAccount$, this.sortedUserIds$]).pipe( map(([accounts, activeAccount, sortedUserIds]) => { const nextId = sortedUserIds.find((id) => id !== activeAccount?.id && accounts[id] != null); @@ -67,6 +70,11 @@ export class FakeAccountService implements AccountService { this.activeAccountSubject.next(null); this.accountActivitySubject.next(accountActivity); } + + setAccountVerifyNewDeviceLogin(userId: UserId, verifyNewDeviceLogin: boolean): Promise { + return this.mock.setAccountVerifyNewDeviceLogin(userId, verifyNewDeviceLogin); + } + setAccountActivity(userId: UserId, lastActivity: Date): Promise { this.accountActivitySubject.next({ ...this.accountActivitySubject["_buffer"][0], diff --git a/libs/common/spec/fake-state-provider.ts b/libs/common/spec/fake-state-provider.ts index b5105bb24ba..9f72ccada55 100644 --- a/libs/common/spec/fake-state-provider.ts +++ b/libs/common/spec/fake-state-provider.ts @@ -225,9 +225,9 @@ export class FakeStateProvider implements StateProvider { async setUserState( userKeyDefinition: UserKeyDefinition, - value: T, + value: T | null, userId?: UserId, - ): Promise<[UserId, T]> { + ): Promise<[UserId, T | null]> { await this.mock.setUserState(userKeyDefinition, value, userId); if (userId) { return [userId, await this.getUser(userId, userKeyDefinition).update(() => value)]; diff --git a/libs/common/spec/fake-state.ts b/libs/common/spec/fake-state.ts index e4b42e357b6..d4fbd108475 100644 --- a/libs/common/spec/fake-state.ts +++ b/libs/common/spec/fake-state.ts @@ -123,7 +123,7 @@ export class FakeSingleUserState implements SingleUserState { this.state$ = this.combinedState$.pipe(map(([_userId, state]) => state)); } - nextState(state: T, { syncValue }: { syncValue: boolean } = { syncValue: true }) { + nextState(state: T | null, { syncValue }: { syncValue: boolean } = { syncValue: true }) { this.stateSubject.next({ syncValue, combinedState: [this.userId, state], @@ -131,9 +131,9 @@ export class FakeSingleUserState implements SingleUserState { } async update( - configureState: (state: T, dependency: TCombine) => T, + configureState: (state: T | null, dependency: TCombine) => T | null, options?: StateUpdateOptions, - ): Promise { + ): Promise { options = populateOptionsWithDefault(options); const current = await firstValueFrom(this.state$.pipe(timeout(options.msTimeout))); const combinedDependencies = @@ -198,7 +198,7 @@ export class FakeActiveUserState implements ActiveUserState { return this.accountService.activeUserId; } - nextState(state: T, { syncValue }: { syncValue: boolean } = { syncValue: true }) { + nextState(state: T | null, { syncValue }: { syncValue: boolean } = { syncValue: true }) { this.stateSubject.next({ syncValue, combinedState: [this.userId, state], @@ -206,9 +206,9 @@ export class FakeActiveUserState implements ActiveUserState { } async update( - configureState: (state: T, dependency: TCombine) => T, + configureState: (state: T | null, dependency: TCombine) => T | null, options?: StateUpdateOptions, - ): Promise<[UserId, T]> { + ): Promise<[UserId, T | null]> { options = populateOptionsWithDefault(options); const current = await firstValueFrom(this.state$.pipe(timeout(options.msTimeout))); const combinedDependencies = diff --git a/libs/common/spec/matchers/index.ts b/libs/common/spec/matchers/index.ts index 44440be5b54..b2e09cc8e92 100644 --- a/libs/common/spec/matchers/index.ts +++ b/libs/common/spec/matchers/index.ts @@ -1,16 +1,12 @@ -import * as matchers from "jest-extended"; - import { toBeFulfilled, toBeResolved, toBeRejected } from "./promise-fulfilled"; import { toAlmostEqual } from "./to-almost-equal"; +import { toContainPartialObjects } from "./to-contain-partial-objects"; import { toEqualBuffer } from "./to-equal-buffer"; export * from "./to-equal-buffer"; export * from "./to-almost-equal"; export * from "./promise-fulfilled"; -// add all jest-extended matchers -expect.extend(matchers); - export function addCustomMatchers() { expect.extend({ toEqualBuffer: toEqualBuffer, @@ -18,6 +14,7 @@ export function addCustomMatchers() { toBeFulfilled: toBeFulfilled, toBeResolved: toBeResolved, toBeRejected: toBeRejected, + toContainPartialObjects, }); } @@ -59,4 +56,9 @@ export interface CustomMatchers { * @returns CustomMatcherResult indicating whether or not the test passed */ toBeRejected(withinMs?: number): Promise; + /** + * Matches if the received array contains all the expected objects using partial matching (expect.objectContaining). + * @param expected An array of partial objects that should be contained in the received array. + */ + toContainPartialObjects(expected: Array): R; } diff --git a/libs/common/spec/matchers/to-contain-partial-objects.spec.ts b/libs/common/spec/matchers/to-contain-partial-objects.spec.ts new file mode 100644 index 00000000000..ab6f90adf17 --- /dev/null +++ b/libs/common/spec/matchers/to-contain-partial-objects.spec.ts @@ -0,0 +1,77 @@ +describe("toContainPartialObjects", () => { + describe("matches", () => { + it("if the array only contains the partial objects", () => { + const actual = [ + { + id: 1, + name: "foo", + }, + { + id: 2, + name: "bar", + }, + ]; + + const expected = [{ id: 1 }, { id: 2 }]; + + expect(actual).toContainPartialObjects(expected); + }); + + it("if the array contains the partial objects and other objects", () => { + const actual = [ + { + id: 1, + name: "foo", + }, + { + id: 2, + name: "bar", + }, + { + id: 3, + name: "baz", + }, + ]; + + const expected = [{ id: 1 }, { id: 2 }]; + + expect(actual).toContainPartialObjects(expected); + }); + }); + + describe("doesn't match", () => { + it("if the array does not contain any partial objects", () => { + const actual = [ + { + id: 1, + name: "foo", + }, + { + id: 2, + name: "bar", + }, + ]; + + const expected = [{ id: 1, name: "Foo" }]; + + expect(actual).not.toContainPartialObjects(expected); + }); + + it("if the array contains some but not all partial objects", () => { + const actual = [ + { + id: 1, + name: "foo", + }, + { + id: 2, + name: "bar", + }, + ]; + + const expected = [{ id: 2 }, { id: 3 }]; + + expect(actual).not.toContainPartialObjects(expected); + }); + }); +}); diff --git a/libs/common/spec/matchers/to-contain-partial-objects.ts b/libs/common/spec/matchers/to-contain-partial-objects.ts new file mode 100644 index 00000000000..f072ca6fba6 --- /dev/null +++ b/libs/common/spec/matchers/to-contain-partial-objects.ts @@ -0,0 +1,31 @@ +import { EOL } from "os"; + +import { diff } from "jest-diff"; + +export const toContainPartialObjects: jest.CustomMatcher = function ( + received: Array, + expected: Array, +) { + const matched = this.equals( + received, + expect.arrayContaining(expected.map((e) => expect.objectContaining(e))), + ); + + if (matched) { + return { + message: () => + "Expected the received array NOT to include partial matches for all expected objects." + + EOL + + diff(expected, received), + pass: true, + }; + } + + return { + message: () => + "Expected the received array to contain partial matches for all expected objects." + + EOL + + diff(expected, received), + pass: false, + }; +}; diff --git a/libs/common/spec/matrix.spec.ts b/libs/common/spec/matrix.spec.ts new file mode 100644 index 00000000000..b1a5e7a9644 --- /dev/null +++ b/libs/common/spec/matrix.spec.ts @@ -0,0 +1,76 @@ +import { Matrix } from "./matrix"; + +class TestObject { + value: number = 0; + + constructor() {} + + increment() { + this.value++; + } +} + +describe("matrix", () => { + it("caches entries in a matrix properly with a single argument", () => { + const mockFunction = jest.fn(); + const getter = Matrix.autoMockMethod(mockFunction, () => new TestObject()); + + const obj = getter("test1"); + expect(obj.value).toBe(0); + + // Change the state of the object + obj.increment(); + + // Should return the same instance the second time this is called + expect(getter("test1").value).toBe(1); + + // Using the getter should not call the mock function + expect(mockFunction).not.toHaveBeenCalled(); + + const mockedFunctionReturn1 = mockFunction("test1"); + expect(mockedFunctionReturn1.value).toBe(1); + + // Totally new value + const mockedFunctionReturn2 = mockFunction("test2"); + expect(mockedFunctionReturn2.value).toBe(0); + + expect(mockFunction).toHaveBeenCalledTimes(2); + }); + + it("caches entries in matrix properly with multiple arguments", () => { + const mockFunction = jest.fn(); + + const getter = Matrix.autoMockMethod(mockFunction, () => { + return new TestObject(); + }); + + const obj = getter("test1", 4); + expect(obj.value).toBe(0); + + obj.increment(); + + expect(getter("test1", 4).value).toBe(1); + + expect(mockFunction("test1", 3).value).toBe(0); + }); + + it("should give original args in creator even if it has multiple key layers", () => { + const mockFunction = jest.fn(); + + let invoked = false; + + const getter = Matrix.autoMockMethod(mockFunction, (args) => { + expect(args).toHaveLength(3); + expect(args[0]).toBe("test"); + expect(args[1]).toBe(42); + expect(args[2]).toBe(true); + + invoked = true; + + return new TestObject(); + }); + + getter("test", 42, true); + expect(invoked).toBe(true); + }); +}); diff --git a/libs/common/spec/matrix.ts b/libs/common/spec/matrix.ts new file mode 100644 index 00000000000..e4ac9f5537f --- /dev/null +++ b/libs/common/spec/matrix.ts @@ -0,0 +1,115 @@ +type PickFirst = Array extends [infer First, ...unknown[]] ? First : never; + +type MatrixOrValue = Array extends [] + ? Value + : Matrix; + +type RemoveFirst = T extends [unknown, ...infer Rest] ? Rest : never; + +/** + * A matrix is intended to manage cached values for a set of method arguments. + */ +export class Matrix { + private map: Map, MatrixOrValue, TValue>> = new Map(); + + /** + * This is especially useful for methods on a service that take inputs but return Observables. + * Generally when interacting with observables in tests, you want to use a simple SubjectLike + * type to back it instead, so that you can easily `next` values to simulate an emission. + * + * @param mockFunction The function to have a Matrix based implementation added to it. + * @param creator The function to use to create the underlying value to return for the given arguments. + * @returns A "getter" function that allows you to retrieve the backing value that is used for the given arguments. + * + * @example + * ```ts + * interface MyService { + * event$(userId: UserId) => Observable + * } + * + * // Test + * const myService = mock(); + * const eventGetter = Matrix.autoMockMethod(myService.event$, (userId) => BehaviorSubject()); + * + * eventGetter("userOne").next(new UserEvent()); + * eventGetter("userTwo").next(new UserEvent()); + * ``` + * + * This replaces a more manual way of doing things like: + * + * ```ts + * const myService = mock(); + * const userOneSubject = new BehaviorSubject(); + * const userTwoSubject = new BehaviorSubject(); + * myService.event$.mockImplementation((userId) => { + * if (userId === "userOne") { + * return userOneSubject; + * } else if (userId === "userTwo") { + * return userTwoSubject; + * } + * return new BehaviorSubject(); + * }); + * + * userOneSubject.next(new UserEvent()); + * userTwoSubject.next(new UserEvent()); + * ``` + */ + static autoMockMethod( + mockFunction: jest.Mock, + creator: (args: TArgs) => TActualReturn, + ): (...args: TArgs) => TActualReturn { + const matrix = new Matrix(); + + const getter = (...args: TArgs) => { + return matrix.getOrCreateEntry(args, creator); + }; + + mockFunction.mockImplementation(getter); + + return getter; + } + + /** + * Gives the ability to get or create an entry in the matrix via the given args. + * + * @note The args are evaulated using Javascript equality so primivites work best. + * + * @param args The arguments to use to evaluate if an entry in the matrix exists already, + * or a value should be created and stored with those arguments. + * @param creator The function to call with the arguments to build a value. + * @returns The existing entry if one already exists or a new value created with the creator param. + */ + getOrCreateEntry(args: TKeys, creator: (args: TKeys) => TValue): TValue { + if (args.length === 0) { + throw new Error("Matrix is not for you."); + } + + if (args.length === 1) { + const arg = args[0] as PickFirst; + if (this.map.has(arg)) { + // Get the cached value + return this.map.get(arg) as TValue; + } else { + const value = creator(args); + // Save the value for the next time + this.map.set(arg, value as MatrixOrValue, TValue>); + return value; + } + } + + // There are for sure 2 or more args + const [first, ...rest] = args as unknown as [PickFirst, ...RemoveFirst]; + + let matrix: Matrix, TValue> | null = null; + + if (this.map.has(first)) { + // We've already created a map for this argument + matrix = this.map.get(first) as Matrix, TValue>; + } else { + matrix = new Matrix, TValue>(); + this.map.set(first, matrix as MatrixOrValue, TValue>); + } + + return matrix.getOrCreateEntry(rest, () => creator(args)); + } +} diff --git a/libs/common/spec/observable-tracker.ts b/libs/common/spec/observable-tracker.ts index 0ec4aa812f8..adefabb7472 100644 --- a/libs/common/spec/observable-tracker.ts +++ b/libs/common/spec/observable-tracker.ts @@ -1,13 +1,33 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { firstValueFrom, Observable, Subject, Subscription, throwError, timeout } from "rxjs"; +import { + filter, + firstValueFrom, + lastValueFrom, + Observable, + Subject, + Subscription, + throwError, + timeout, +} from "rxjs"; /** Test class to enable async awaiting of observable emissions */ export class ObservableTracker { private subscription: Subscription; private emissionReceived = new Subject(); emissions: T[] = []; - constructor(observable: Observable) { + + /** + * Creates a new ObservableTracker and instantly subscribes to the given observable. + * @param observable The observable to track + * @param clone Whether to clone tracked emissions or not, defaults to true. + * Cloning can be necessary if the observable emits objects that are mutated after emission. Cloning makes it + * harder to compare the original and the tracked emission using reference equality (e.g. `expect().toBe()`). + */ + constructor( + observable: Observable, + private clone = true, + ) { this.emissions = this.trackEmissions(observable); } @@ -33,6 +53,19 @@ export class ObservableTracker { ); } + async expectCompletion(msTimeout = 50): Promise { + return await lastValueFrom( + this.emissionReceived.pipe( + filter(() => false), + timeout({ + first: msTimeout, + with: () => throwError(() => new Error("Timeout exceeded waiting for completion.")), + }), + ), + { defaultValue: undefined }, + ); + } + /** Awaits until the total number of emissions observed by this tracker equals or exceeds {@link count} * @param count The number of emissions to wait for */ @@ -48,26 +81,31 @@ export class ObservableTracker { this.emissionReceived.subscribe((value) => { emissions.push(value); }); - this.subscription = observable.subscribe((value) => { - if (value == null) { - this.emissionReceived.next(null); - return; - } - - switch (typeof value) { - case "string": - case "number": - case "boolean": - this.emissionReceived.next(value); - break; - case "symbol": - // Cheating types to make symbols work at all - this.emissionReceived.next(value as T); - break; - default: { - this.emissionReceived.next(clone(value)); + this.subscription = observable.subscribe({ + next: (value) => { + if (value == null) { + this.emissionReceived.next(null); + return; } - } + + switch (typeof value) { + case "string": + case "number": + case "boolean": + this.emissionReceived.next(value); + break; + case "symbol": + // Cheating types to make symbols work at all + this.emissionReceived.next(value as T); + break; + default: { + this.emissionReceived.next(this.clone ? clone(value) : value); + } + } + }, + complete: () => { + this.emissionReceived.complete(); + }, }); return emissions; diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index 997974e0581..f186787f7c0 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -1,9 +1,9 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { - CollectionRequest, CollectionAccessDetailsResponse, CollectionDetailsResponse, + CollectionRequest, CollectionResponse, } from "@bitwarden/admin-console/common"; @@ -38,7 +38,6 @@ import { ProviderUserUserDetailsResponse, } from "../admin-console/models/response/provider/provider-user.response"; import { SelectionReadOnlyResponse } from "../admin-console/models/response/selection-read-only.response"; -import { AuthRequest } from "../auth/models/request/auth.request"; import { DeviceVerificationRequest } from "../auth/models/request/device-verification.request"; import { DisableTwoFactorAuthenticatorRequest } from "../auth/models/request/disable-two-factor-authenticator.request"; import { EmailTokenRequest } from "../auth/models/request/email-token.request"; @@ -47,19 +46,12 @@ import { PasswordTokenRequest } from "../auth/models/request/identity-token/pass import { SsoTokenRequest } from "../auth/models/request/identity-token/sso-token.request"; import { UserApiTokenRequest } from "../auth/models/request/identity-token/user-api-token.request"; import { WebAuthnLoginTokenRequest } from "../auth/models/request/identity-token/webauthn-login-token.request"; -import { KeyConnectorUserKeyRequest } from "../auth/models/request/key-connector-user-key.request"; import { PasswordHintRequest } from "../auth/models/request/password-hint.request"; -import { PasswordRequest } from "../auth/models/request/password.request"; import { PasswordlessAuthRequest } from "../auth/models/request/passwordless-auth.request"; import { SecretVerificationRequest } from "../auth/models/request/secret-verification.request"; -import { SetKeyConnectorKeyRequest } from "../auth/models/request/set-key-connector-key.request"; -import { SetPasswordRequest } from "../auth/models/request/set-password.request"; import { TwoFactorEmailRequest } from "../auth/models/request/two-factor-email.request"; import { TwoFactorProviderRequest } from "../auth/models/request/two-factor-provider.request"; -import { TwoFactorRecoveryRequest } from "../auth/models/request/two-factor-recovery.request"; import { UpdateProfileRequest } from "../auth/models/request/update-profile.request"; -import { UpdateTdeOffboardingPasswordRequest } from "../auth/models/request/update-tde-offboarding-password.request"; -import { UpdateTempPasswordRequest } from "../auth/models/request/update-temp-password.request"; import { UpdateTwoFactorAuthenticatorRequest } from "../auth/models/request/update-two-factor-authenticator.request"; import { UpdateTwoFactorDuoRequest } from "../auth/models/request/update-two-factor-duo.request"; import { UpdateTwoFactorEmailRequest } from "../auth/models/request/update-two-factor-email.request"; @@ -70,6 +62,7 @@ import { ApiKeyResponse } from "../auth/models/response/api-key.response"; import { AuthRequestResponse } from "../auth/models/response/auth-request.response"; import { DeviceVerificationResponse } from "../auth/models/response/device-verification.response"; import { IdentityCaptchaResponse } from "../auth/models/response/identity-captcha.response"; +import { IdentityDeviceVerificationResponse } from "../auth/models/response/identity-device-verification.response"; import { IdentityTokenResponse } from "../auth/models/response/identity-token.response"; import { IdentityTwoFactorResponse } from "../auth/models/response/identity-two-factor.response"; import { KeyConnectorUserKeyResponse } from "../auth/models/response/key-connector-user-key.response"; @@ -95,7 +88,8 @@ import { PaymentResponse } from "../billing/models/response/payment.response"; import { PlanResponse } from "../billing/models/response/plan.response"; import { SubscriptionResponse } from "../billing/models/response/subscription.response"; import { TaxInfoResponse } from "../billing/models/response/tax-info.response"; -import { TaxRateResponse } from "../billing/models/response/tax-rate.response"; +import { KeyConnectorUserKeyRequest } from "../key-management/key-connector/models/key-connector-user-key.request"; +import { SetKeyConnectorKeyRequest } from "../key-management/key-connector/models/set-key-connector-key.request"; import { DeleteRecoverRequest } from "../models/request/delete-recover.request"; import { EventRequest } from "../models/request/event.request"; import { KdfRequest } from "../models/request/kdf.request"; @@ -137,12 +131,12 @@ import { OptionalCipherResponse } from "../vault/models/response/optional-cipher */ export abstract class ApiService { send: ( - method: "GET" | "POST" | "PUT" | "DELETE", + method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", path: string, body: any, authed: boolean, hasResponse: boolean, - apiUrl?: string, + apiUrl?: string | null, alterHeaders?: (headers: Headers) => void, ) => Promise; @@ -152,7 +146,12 @@ export abstract class ApiService { | SsoTokenRequest | UserApiTokenRequest | WebAuthnLoginTokenRequest, - ) => Promise; + ) => Promise< + | IdentityTokenResponse + | IdentityTwoFactorResponse + | IdentityCaptchaResponse + | IdentityDeviceVerificationResponse + >; refreshIdentityToken: () => Promise; getProfile: () => Promise; @@ -164,8 +163,6 @@ export abstract class ApiService { postPrelogin: (request: PreloginRequest) => Promise; postEmailToken: (request: EmailTokenRequest) => Promise; postEmail: (request: EmailRequest) => Promise; - postPassword: (request: PasswordRequest) => Promise; - setPassword: (request: SetPasswordRequest) => Promise; postSetKeyConnectorKey: (request: SetKeyConnectorKeyRequest) => Promise; postSecurityStamp: (request: SecretVerificationRequest) => Promise; getAccountRevisionDate: () => Promise; @@ -184,13 +181,8 @@ export abstract class ApiService { postAccountKdf: (request: KdfRequest) => Promise; postUserApiKey: (id: string, request: SecretVerificationRequest) => Promise; postUserRotateApiKey: (id: string, request: SecretVerificationRequest) => Promise; - putUpdateTempPassword: (request: UpdateTempPasswordRequest) => Promise; - putUpdateTdeOffboardingPassword: (request: UpdateTdeOffboardingPasswordRequest) => Promise; postConvertToKeyConnector: () => Promise; //passwordless - postAuthRequest: (request: AuthRequest) => Promise; - postAdminAuthRequest: (request: AuthRequest) => Promise; - getAuthResponse: (id: string, accessCode: string) => Promise; getAuthRequest: (id: string) => Promise; putAuthRequest: (id: string, request: PasswordlessAuthRequest) => Promise; getAuthRequests: () => Promise>; @@ -351,7 +343,6 @@ export abstract class ApiService { organizationId: string, request: TwoFactorProviderRequest, ) => Promise; - postTwoFactorRecover: (request: TwoFactorRecoveryRequest) => Promise; postTwoFactorEmailSetup: (request: TwoFactorEmailRequest) => Promise; postTwoFactorEmail: (request: TwoFactorEmailRequest) => Promise; getDeviceVerificationSettings: () => Promise; @@ -376,7 +367,6 @@ export abstract class ApiService { ): Promise>; deleteOrganizationConnection: (id: string) => Promise; getPlans: () => Promise>; - getTaxRates: () => Promise>; getProviderUsers: (providerId: string) => Promise>; getProviderUser: (providerId: string, id: string) => Promise; diff --git a/libs/common/src/abstractions/notifications.service.ts b/libs/common/src/abstractions/notifications.service.ts deleted file mode 100644 index 2234a5588a6..00000000000 --- a/libs/common/src/abstractions/notifications.service.ts +++ /dev/null @@ -1,8 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -export abstract class NotificationsService { - init: () => Promise; - updateConnection: (sync?: boolean) => Promise; - reconnectFromActivity: () => Promise; - disconnectFromInactivity: () => Promise; -} diff --git a/libs/common/src/abstractions/search.service.ts b/libs/common/src/abstractions/search.service.ts index ae53266cc21..2bff33bf2db 100644 --- a/libs/common/src/abstractions/search.service.ts +++ b/libs/common/src/abstractions/search.service.ts @@ -3,16 +3,21 @@ import { Observable } from "rxjs"; import { SendView } from "../tools/send/models/view/send.view"; -import { IndexedEntityId } from "../types/guid"; +import { IndexedEntityId, UserId } from "../types/guid"; import { CipherView } from "../vault/models/view/cipher.view"; export abstract class SearchService { - indexedEntityId$: Observable; + indexedEntityId$: (userId: UserId) => Observable; - clearIndex: () => Promise; - isSearchable: (query: string) => Promise; - indexCiphers: (ciphersToIndex: CipherView[], indexedEntityGuid?: string) => Promise; + clearIndex: (userId: UserId) => Promise; + isSearchable: (userId: UserId, query: string) => Promise; + indexCiphers: ( + userId: UserId, + ciphersToIndex: CipherView[], + indexedEntityGuid?: string, + ) => Promise; searchCiphers: ( + userId: UserId, query: string, filter?: ((cipher: CipherView) => boolean) | ((cipher: CipherView) => boolean)[], ciphers?: CipherView[], diff --git a/libs/common/src/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction.ts b/libs/common/src/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction.ts index 5ecfca5ab84..5a393ed1996 100644 --- a/libs/common/src/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction.ts +++ b/libs/common/src/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction.ts @@ -1,7 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { ListResponse } from "@bitwarden/common/models/response/list.response"; - +import { ListResponse } from "../../../models/response/list.response"; import { OrganizationDomainRequest } from "../../services/organization-domain/requests/organization-domain.request"; import { OrganizationDomainSsoDetailsResponse } from "./responses/organization-domain-sso-details.response"; diff --git a/libs/common/src/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response.ts b/libs/common/src/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response.ts index c4817306a63..066d2d381e7 100644 --- a/libs/common/src/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response.ts +++ b/libs/common/src/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "@bitwarden/common/models/response/base.response"; +import { BaseResponse } from "../../../../models/response/base.response"; export class VerifiedOrganizationDomainSsoDetailsResponse extends BaseResponse { organizationName: string; diff --git a/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts b/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts index 0c45c919e95..000d1655416 100644 --- a/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts +++ b/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts @@ -1,7 +1,5 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { BillingHistoryResponse } from "@bitwarden/common/billing/models/response/billing-history.response"; - import { OrganizationApiKeyRequest } from "../../../admin-console/models/request/organization-api-key.request"; import { OrganizationSsoRequest } from "../../../auth/models/request/organization-sso.request"; import { SecretVerificationRequest } from "../../../auth/models/request/secret-verification.request"; @@ -13,6 +11,7 @@ import { OrganizationSmSubscriptionUpdateRequest } from "../../../billing/models import { OrganizationSubscriptionUpdateRequest } from "../../../billing/models/request/organization-subscription-update.request"; import { PaymentRequest } from "../../../billing/models/request/payment.request"; import { SecretsManagerSubscribeRequest } from "../../../billing/models/request/sm-subscribe.request"; +import { BillingHistoryResponse } from "../../../billing/models/response/billing-history.response"; import { BillingResponse } from "../../../billing/models/response/billing.response"; import { OrganizationSubscriptionResponse } from "../../../billing/models/response/organization-subscription.response"; import { PaymentResponse } from "../../../billing/models/response/payment.response"; diff --git a/libs/common/src/admin-console/abstractions/organization/organization.service.abstraction.ts b/libs/common/src/admin-console/abstractions/organization/organization.service.abstraction.ts index 2161feb516e..05c214ece13 100644 --- a/libs/common/src/admin-console/abstractions/organization/organization.service.abstraction.ts +++ b/libs/common/src/admin-console/abstractions/organization/organization.service.abstraction.ts @@ -17,7 +17,7 @@ export function canAccessSettingsTab(org: Organization): boolean { org.canManageSso || org.canManageScim || org.canAccessImport || - org.canAccessExport(false) || // Feature flag value doesn't matter here, providers will have access to this group anyway + org.canAccessExport || org.canManageDeviceApprovals ); } @@ -57,14 +57,6 @@ export function getOrganizationById(id: string) { return map((orgs) => orgs.find((o) => o.id === id)); } -/** - * Returns `true` if a user is a member of an organization (rather than only being a ProviderUser) - * @deprecated Use organizationService.organizations$ with a filter instead - */ -export function isMember(org: Organization): boolean { - return org.isMember; -} - /** * Publishes an observable stream of organizations. This service is meant to * be used widely across Bitwarden as the primary way of fetching organizations. @@ -73,41 +65,23 @@ export function isMember(org: Organization): boolean { */ export abstract class OrganizationService { /** - * Publishes state for all organizations under the active user. + * Publishes state for all organizations under the specified user. * @returns An observable list of organizations */ - organizations$: Observable; + organizations$: (userId: UserId) => Observable; // @todo Clean these up. Continuing to expand them is not recommended. // @see https://bitwarden.atlassian.net/browse/AC-2252 - memberOrganizations$: Observable; - /** - * @deprecated This is currently only used in the CLI, and should not be - * used in any new calls. Use get$ instead for the time being, and we'll be - * removing this method soon. See Jira for details: - * https://bitwarden.atlassian.net/browse/AC-2252. - */ - getFromState: (id: string) => Promise; + memberOrganizations$: (userId: UserId) => Observable; /** * Emits true if the user can create or manage a Free Bitwarden Families sponsorship. */ - canManageSponsorships$: Observable; + canManageSponsorships$: (userId: UserId) => Observable; /** * Emits true if any of the user's organizations have a Free Bitwarden Families sponsorship available. */ - familySponsorshipAvailable$: Observable; - hasOrganizations: () => Promise; - get$: (id: string) => Observable; - get: (id: string) => Promise; - /** - * @deprecated This method is only used in key connector and will be removed soon as part of https://bitwarden.atlassian.net/browse/AC-2252. - */ - getAll: (userId?: string) => Promise; - - /** - * Publishes state for all organizations for the given user id or the active user. - */ - getAll$: (userId?: UserId) => Observable; + familySponsorshipAvailable$: (userId: UserId) => Observable; + hasOrganizations: (userId: UserId) => Observable; } /** @@ -120,20 +94,18 @@ export abstract class InternalOrganizationServiceAbstraction extends Organizatio /** * Replaces state for the provided organization, or creates it if not found. * @param organization The organization state being saved. - * @param userId The userId to replace state for. Defaults to the active - * user. + * @param userId The userId to replace state for. */ - upsert: (OrganizationData: OrganizationData) => Promise; + upsert: (OrganizationData: OrganizationData, userId: UserId) => Promise; /** - * Replaces state for the entire registered organization list for the active user. + * Replaces state for the entire registered organization list for the specified user. * You probably don't want this unless you're calling from a full sync * operation or a logout. See `upsert` for creating & updating a single * organization in the state. - * @param organizations A complete list of all organization state for the active - * user. - * @param userId The userId to replace state for. Defaults to the active + * @param organizations A complete list of all organization state for the provided * user. + * @param userId The userId to replace state for. */ - replace: (organizations: { [id: string]: OrganizationData }, userId?: UserId) => Promise; + replace: (organizations: { [id: string]: OrganizationData }, userId: UserId) => Promise; } diff --git a/libs/common/src/admin-console/abstractions/organization/vnext.organization.service.ts b/libs/common/src/admin-console/abstractions/organization/vnext.organization.service.ts deleted file mode 100644 index b5c0f6291fc..00000000000 --- a/libs/common/src/admin-console/abstractions/organization/vnext.organization.service.ts +++ /dev/null @@ -1,111 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { map, Observable } from "rxjs"; - -import { UserId } from "../../../types/guid"; -import { OrganizationData } from "../../models/data/organization.data"; -import { Organization } from "../../models/domain/organization"; - -export function canAccessVaultTab(org: Organization): boolean { - return org.canViewAllCollections; -} - -export function canAccessSettingsTab(org: Organization): boolean { - return ( - org.isOwner || - org.canManagePolicies || - org.canManageSso || - org.canManageScim || - org.canAccessImport || - org.canAccessExport(false) || // Feature flag value doesn't matter here, providers will have access to this group anyway - org.canManageDeviceApprovals - ); -} - -export function canAccessMembersTab(org: Organization): boolean { - return org.canManageUsers || org.canManageUsersPassword; -} - -export function canAccessGroupsTab(org: Organization): boolean { - return org.canManageGroups; -} - -export function canAccessReportingTab(org: Organization): boolean { - return org.canAccessReports || org.canAccessEventLogs; -} - -export function canAccessBillingTab(org: Organization): boolean { - return org.isOwner; -} - -export function canAccessOrgAdmin(org: Organization): boolean { - // Admin console can only be accessed by Owners for disabled organizations - if (!org.enabled && !org.isOwner) { - return false; - } - return ( - canAccessMembersTab(org) || - canAccessGroupsTab(org) || - canAccessReportingTab(org) || - canAccessBillingTab(org) || - canAccessSettingsTab(org) || - canAccessVaultTab(org) - ); -} - -export function getOrganizationById(id: string) { - return map((orgs) => orgs.find((o) => o.id === id)); -} - -/** - * Publishes an observable stream of organizations. This service is meant to - * be used widely across Bitwarden as the primary way of fetching organizations. - * Risky operations like updates are isolated to the - * internal extension `InternalOrganizationServiceAbstraction`. - */ -export abstract class vNextOrganizationService { - /** - * Publishes state for all organizations under the specified user. - * @returns An observable list of organizations - */ - organizations$: (userId: UserId) => Observable; - - // @todo Clean these up. Continuing to expand them is not recommended. - // @see https://bitwarden.atlassian.net/browse/AC-2252 - memberOrganizations$: (userId: UserId) => Observable; - /** - * Emits true if the user can create or manage a Free Bitwarden Families sponsorship. - */ - canManageSponsorships$: (userId: UserId) => Observable; - /** - * Emits true if any of the user's organizations have a Free Bitwarden Families sponsorship available. - */ - familySponsorshipAvailable$: (userId: UserId) => Observable; - hasOrganizations: (userId: UserId) => Observable; -} - -/** - * Big scary buttons that **update** organization state. These should only be - * called from within admin-console scoped code. Extends the base - * `OrganizationService` for easy access to `get` calls. - * @internal - */ -export abstract class vNextInternalOrganizationServiceAbstraction extends vNextOrganizationService { - /** - * Replaces state for the provided organization, or creates it if not found. - * @param organization The organization state being saved. - * @param userId The userId to replace state for. - */ - upsert: (OrganizationData: OrganizationData, userId: UserId) => Promise; - - /** - * Replaces state for the entire registered organization list for the specified user. - * You probably don't want this unless you're calling from a full sync - * operation or a logout. See `upsert` for creating & updating a single - * organization in the state. - * @param organizations A complete list of all organization state for the provided - * user. - * @param userId The userId to replace state for. - */ - replace: (organizations: { [id: string]: OrganizationData }, userId: UserId) => Promise; -} diff --git a/libs/common/src/admin-console/abstractions/policy/policy.service.abstraction.ts b/libs/common/src/admin-console/abstractions/policy/policy.service.abstraction.ts index bed341115ee..4280756326c 100644 --- a/libs/common/src/admin-console/abstractions/policy/policy.service.abstraction.ts +++ b/libs/common/src/admin-console/abstractions/policy/policy.service.abstraction.ts @@ -30,7 +30,7 @@ export abstract class PolicyService { * A policy "applies" if it is enabled and the user is not exempt (e.g. because they are an Owner). * @param policyType the {@link PolicyType} to search for */ - getAll$: (policyType: PolicyType, userId?: UserId) => Observable; + getAll$: (policyType: PolicyType, userId: UserId) => Observable; /** * All {@link Policy} objects for the specified user (from sync data). diff --git a/libs/common/src/admin-console/abstractions/policy/vnext-policy.service.ts b/libs/common/src/admin-console/abstractions/policy/vnext-policy.service.ts new file mode 100644 index 00000000000..1e96d6b8d00 --- /dev/null +++ b/libs/common/src/admin-console/abstractions/policy/vnext-policy.service.ts @@ -0,0 +1,68 @@ +import { Observable } from "rxjs"; + +import { UserId } from "../../../types/guid"; +import { PolicyType } from "../../enums"; +import { PolicyData } from "../../models/data/policy.data"; +import { MasterPasswordPolicyOptions } from "../../models/domain/master-password-policy-options"; +import { Policy } from "../../models/domain/policy"; +import { ResetPasswordPolicyOptions } from "../../models/domain/reset-password-policy-options"; + +export abstract class vNextPolicyService { + /** + * All policies for the provided user from sync data. + * May include policies that are disabled or otherwise do not apply to the user. Be careful using this! + * Consider {@link policiesByType$} instead, which will only return policies that should be enforced against the user. + */ + abstract policies$: (userId: UserId) => Observable; + + /** + * @returns all {@link Policy} objects of a given type that apply to the specified user. + * A policy "applies" if it is enabled and the user is not exempt (e.g. because they are an Owner). + * @param policyType the {@link PolicyType} to search for + * @param userId the {@link UserId} to search against + */ + abstract policiesByType$: (policyType: PolicyType, userId: UserId) => Observable; + + /** + * @returns true if a policy of the specified type applies to the specified user, otherwise false. + * A policy "applies" if it is enabled and the user is not exempt (e.g. because they are an Owner). + * This does not take into account the policy's configuration - if that is important, use {@link policiesByType$} to get the + * {@link Policy} objects and then filter by Policy.data. + */ + abstract policyAppliesToUser$: (policyType: PolicyType, userId: UserId) => Observable; + + // Policy specific interfaces + + /** + * Combines all Master Password policies that apply to the user. + * @returns a set of options which represent the minimum Master Password settings that the user must + * comply with in order to comply with **all** Master Password policies. + */ + abstract masterPasswordPolicyOptions$: ( + userId: UserId, + policies?: Policy[], + ) => Observable; + + /** + * Evaluates whether a proposed Master Password complies with all Master Password policies that apply to the user. + */ + abstract evaluateMasterPassword: ( + passwordStrength: number, + newPassword: string, + enforcedPolicyOptions?: MasterPasswordPolicyOptions, + ) => boolean; + + /** + * @returns {@link ResetPasswordPolicyOptions} for the specified organization and a boolean indicating whether the policy + * is enabled + */ + abstract getResetPasswordPolicyOptions: ( + policies: Policy[], + orgId: string, + ) => [ResetPasswordPolicyOptions, boolean]; +} + +export abstract class vNextInternalPolicyService extends vNextPolicyService { + abstract upsert: (policy: PolicyData, userId: UserId) => Promise; + abstract replace: (policies: { [id: string]: PolicyData }, userId: UserId) => Promise; +} diff --git a/libs/common/src/admin-console/abstractions/provider/provider-api.service.abstraction.ts b/libs/common/src/admin-console/abstractions/provider/provider-api.service.abstraction.ts index f348e7487de..ffe79f0ad3b 100644 --- a/libs/common/src/admin-console/abstractions/provider/provider-api.service.abstraction.ts +++ b/libs/common/src/admin-console/abstractions/provider/provider-api.service.abstraction.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { AddableOrganizationResponse } from "@bitwarden/common/admin-console/models/response/addable-organization.response"; + import { ProviderSetupRequest } from "../../models/request/provider/provider-setup.request"; import { ProviderUpdateRequest } from "../../models/request/provider/provider-update.request"; import { ProviderVerifyRecoverDeleteRequest } from "../../models/request/provider/provider-verify-recover-delete.request"; @@ -14,4 +16,12 @@ export class ProviderApiServiceAbstraction { request: ProviderVerifyRecoverDeleteRequest, ) => Promise; deleteProvider: (id: string) => Promise; + getProviderAddableOrganizations: (providerId: string) => Promise; + addOrganizationToProvider: ( + providerId: string, + request: { + key: string; + organizationId: string; + }, + ) => Promise; } diff --git a/libs/common/src/admin-console/enums/policy-type.enum.spec.ts b/libs/common/src/admin-console/enums/policy-type.enum.spec.ts new file mode 100644 index 00000000000..51e1a75ad3c --- /dev/null +++ b/libs/common/src/admin-console/enums/policy-type.enum.spec.ts @@ -0,0 +1,7 @@ +import { PolicyType } from "@bitwarden/common/admin-console/enums/policy-type.enum"; + +describe("PolicyType", () => { + it("RemoveUnlockWithPin should be 14", () => { + expect(PolicyType.RemoveUnlockWithPin).toBe(14); + }); +}); diff --git a/libs/common/src/admin-console/enums/policy-type.enum.ts b/libs/common/src/admin-console/enums/policy-type.enum.ts index 2ca9fbef8db..336b834ca56 100644 --- a/libs/common/src/admin-console/enums/policy-type.enum.ts +++ b/libs/common/src/admin-console/enums/policy-type.enum.ts @@ -13,4 +13,5 @@ export enum PolicyType { ActivateAutofill = 11, // Activates autofill with page load on the browser extension AutomaticAppLogIn = 12, // Enables automatic log in of apps from configured identity provider FreeFamiliesSponsorshipPolicy = 13, // Disables free families plan for organization + RemoveUnlockWithPin = 14, // Do not allow members to unlock their account with a PIN. } diff --git a/libs/common/src/admin-console/models/data/organization.data.spec.ts b/libs/common/src/admin-console/models/data/organization.data.spec.ts index da9a82e7c5c..5f487e1f898 100644 --- a/libs/common/src/admin-console/models/data/organization.data.spec.ts +++ b/libs/common/src/admin-console/models/data/organization.data.spec.ts @@ -1,6 +1,6 @@ import { ProductTierType } from "../../../billing/enums/product-tier-type.enum"; import { OrganizationUserStatusType, OrganizationUserType } from "../../enums"; -import { ORGANIZATIONS } from "../../services/organization/organization.service"; +import { ORGANIZATIONS } from "../../services/organization/organization.state"; import { OrganizationData } from "./organization.data"; @@ -53,6 +53,7 @@ describe("ORGANIZATIONS state", () => { accessSecretsManager: false, limitCollectionCreation: false, limitCollectionDeletion: false, + limitItemDeletion: false, allowAdminAccessToAllCollectionItems: false, familySponsorshipLastSyncDate: new Date(), userIsManagedByOrganization: false, diff --git a/libs/common/src/admin-console/models/data/organization.data.ts b/libs/common/src/admin-console/models/data/organization.data.ts index 8ec84b5fd09..b81d06e6367 100644 --- a/libs/common/src/admin-console/models/data/organization.data.ts +++ b/libs/common/src/admin-console/models/data/organization.data.ts @@ -56,6 +56,7 @@ export class OrganizationData { accessSecretsManager: boolean; limitCollectionCreation: boolean; limitCollectionDeletion: boolean; + limitItemDeletion: boolean; allowAdminAccessToAllCollectionItems: boolean; userIsManagedByOrganization: boolean; useRiskInsights: boolean; @@ -117,6 +118,7 @@ export class OrganizationData { this.accessSecretsManager = response.accessSecretsManager; this.limitCollectionCreation = response.limitCollectionCreation; this.limitCollectionDeletion = response.limitCollectionDeletion; + this.limitItemDeletion = response.limitItemDeletion; this.allowAdminAccessToAllCollectionItems = response.allowAdminAccessToAllCollectionItems; this.userIsManagedByOrganization = response.userIsManagedByOrganization; this.useRiskInsights = response.useRiskInsights; diff --git a/libs/common/src/admin-console/models/domain/encrypted-organization-key.ts b/libs/common/src/admin-console/models/domain/encrypted-organization-key.ts index 1f8c4e8c42d..67a5b0dd123 100644 --- a/libs/common/src/admin-console/models/domain/encrypted-organization-key.ts +++ b/libs/common/src/admin-console/models/domain/encrypted-organization-key.ts @@ -1,4 +1,4 @@ -import { EncryptService } from "../../../platform/abstractions/encrypt.service"; +import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service"; import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { OrgKey, UserPrivateKey } from "../../../types/key"; @@ -58,6 +58,9 @@ export class ProviderEncryptedOrganizationKey implements BaseEncryptedOrganizati new EncString(this.key), providerKeys[this.providerId], ); + if (decValue == null) { + throw new Error("Failed to decrypt organization key"); + } return new SymmetricCryptoKey(decValue) as OrgKey; } diff --git a/libs/common/src/admin-console/models/domain/organization.ts b/libs/common/src/admin-console/models/domain/organization.ts index 8441298bbff..6f7ff561f04 100644 --- a/libs/common/src/admin-console/models/domain/organization.ts +++ b/libs/common/src/admin-console/models/domain/organization.ts @@ -76,6 +76,12 @@ export class Organization { /** * Refers to the ability for an owner/admin to access all collection items, regardless of assigned collections */ + limitItemDeletion: boolean; + /** + * Refers to the ability to limit delete permission of collection items. + * If set to true, members can only delete items when they have a Can Manage permission over the collection. + * If set to false, members can delete items when they have a Can Manage OR Can Edit permission over the collection. + */ allowAdminAccessToAllCollectionItems: boolean; /** * Indicates if this organization manages the user. @@ -138,6 +144,7 @@ export class Organization { this.accessSecretsManager = obj.accessSecretsManager; this.limitCollectionCreation = obj.limitCollectionCreation; this.limitCollectionDeletion = obj.limitCollectionDeletion; + this.limitItemDeletion = obj.limitItemDeletion; this.allowAdminAccessToAllCollectionItems = obj.allowAdminAccessToAllCollectionItems; this.userIsManagedByOrganization = obj.userIsManagedByOrganization; this.useRiskInsights = obj.useRiskInsights; @@ -182,11 +189,7 @@ export class Organization { ); } - canAccessExport(removeProviderExport: boolean) { - if (!removeProviderExport && this.isProviderUser) { - return true; - } - + get canAccessExport() { return ( this.isMember && (this.type === OrganizationUserType.Owner || diff --git a/libs/common/src/models/request/collection-bulk-delete.request.ts b/libs/common/src/admin-console/models/request/collection-bulk-delete.request.ts similarity index 100% rename from libs/common/src/models/request/collection-bulk-delete.request.ts rename to libs/common/src/admin-console/models/request/collection-bulk-delete.request.ts diff --git a/libs/common/src/admin-console/models/request/organization-collection-management-update.request.ts b/libs/common/src/admin-console/models/request/organization-collection-management-update.request.ts index 23c39376d71..2545a725598 100644 --- a/libs/common/src/admin-console/models/request/organization-collection-management-update.request.ts +++ b/libs/common/src/admin-console/models/request/organization-collection-management-update.request.ts @@ -3,5 +3,6 @@ export class OrganizationCollectionManagementUpdateRequest { limitCollectionCreation: boolean; limitCollectionDeletion: boolean; + limitItemDeletion: boolean; allowAdminAccessToAllCollectionItems: boolean; } diff --git a/libs/common/src/admin-console/models/response/addable-organization.response.ts b/libs/common/src/admin-console/models/response/addable-organization.response.ts new file mode 100644 index 00000000000..74ae5f45690 --- /dev/null +++ b/libs/common/src/admin-console/models/response/addable-organization.response.ts @@ -0,0 +1,18 @@ +import { BaseResponse } from "@bitwarden/common/models/response/base.response"; + +export class AddableOrganizationResponse extends BaseResponse { + id: string; + plan: string; + name: string; + seats: number; + disabled: boolean; + + constructor(response: any) { + super(response); + this.id = this.getResponseProperty("id"); + this.plan = this.getResponseProperty("plan"); + this.name = this.getResponseProperty("name"); + this.seats = this.getResponseProperty("seats"); + this.disabled = this.getResponseProperty("disabled"); + } +} diff --git a/libs/common/src/admin-console/models/response/organization.response.ts b/libs/common/src/admin-console/models/response/organization.response.ts index fd54ff128b6..235ea2f8d96 100644 --- a/libs/common/src/admin-console/models/response/organization.response.ts +++ b/libs/common/src/admin-console/models/response/organization.response.ts @@ -36,6 +36,7 @@ export class OrganizationResponse extends BaseResponse { maxAutoscaleSmServiceAccounts?: number; limitCollectionCreation: boolean; limitCollectionDeletion: boolean; + limitItemDeletion: boolean; allowAdminAccessToAllCollectionItems: boolean; useRiskInsights: boolean; @@ -75,6 +76,7 @@ export class OrganizationResponse extends BaseResponse { this.maxAutoscaleSmServiceAccounts = this.getResponseProperty("MaxAutoscaleSmServiceAccounts"); this.limitCollectionCreation = this.getResponseProperty("LimitCollectionCreation"); this.limitCollectionDeletion = this.getResponseProperty("LimitCollectionDeletion"); + this.limitItemDeletion = this.getResponseProperty("LimitItemDeletion"); this.allowAdminAccessToAllCollectionItems = this.getResponseProperty( "AllowAdminAccessToAllCollectionItems", ); diff --git a/libs/common/src/admin-console/models/response/profile-organization.response.ts b/libs/common/src/admin-console/models/response/profile-organization.response.ts index 9c4b8885ab8..5e37cfc4c5c 100644 --- a/libs/common/src/admin-console/models/response/profile-organization.response.ts +++ b/libs/common/src/admin-console/models/response/profile-organization.response.ts @@ -51,6 +51,7 @@ export class ProfileOrganizationResponse extends BaseResponse { accessSecretsManager: boolean; limitCollectionCreation: boolean; limitCollectionDeletion: boolean; + limitItemDeletion: boolean; allowAdminAccessToAllCollectionItems: boolean; userIsManagedByOrganization: boolean; useRiskInsights: boolean; @@ -114,6 +115,7 @@ export class ProfileOrganizationResponse extends BaseResponse { this.accessSecretsManager = this.getResponseProperty("AccessSecretsManager"); this.limitCollectionCreation = this.getResponseProperty("LimitCollectionCreation"); this.limitCollectionDeletion = this.getResponseProperty("LimitCollectionDeletion"); + this.limitItemDeletion = this.getResponseProperty("LimitItemDeletion"); this.allowAdminAccessToAllCollectionItems = this.getResponseProperty( "AllowAdminAccessToAllCollectionItems", ); diff --git a/libs/common/src/admin-console/services/organization-domain/org-domain-api.service.spec.ts b/libs/common/src/admin-console/services/organization-domain/org-domain-api.service.spec.ts index 7497a77e6f2..1052fe504d4 100644 --- a/libs/common/src/admin-console/services/organization-domain/org-domain-api.service.spec.ts +++ b/libs/common/src/admin-console/services/organization-domain/org-domain-api.service.spec.ts @@ -1,14 +1,13 @@ import { mock } from "jest-mock-extended"; import { lastValueFrom } from "rxjs"; -import { VerifiedOrganizationDomainSsoDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response"; -import { ListResponse } from "@bitwarden/common/models/response/list.response"; - import { ApiService } from "../../../abstractions/api.service"; +import { ListResponse } from "../../../models/response/list.response"; import { I18nService } from "../../../platform/abstractions/i18n.service"; import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service"; import { OrganizationDomainSsoDetailsResponse } from "../../abstractions/organization-domain/responses/organization-domain-sso-details.response"; import { OrganizationDomainResponse } from "../../abstractions/organization-domain/responses/organization-domain.response"; +import { VerifiedOrganizationDomainSsoDetailsResponse } from "../../abstractions/organization-domain/responses/verified-organization-domain-sso-details.response"; import { OrgDomainApiService } from "./org-domain-api.service"; import { OrgDomainService } from "./org-domain.service"; diff --git a/libs/common/src/admin-console/services/organization/default-vnext-organization.service.spec.ts b/libs/common/src/admin-console/services/organization/default-organization.service.spec.ts similarity index 96% rename from libs/common/src/admin-console/services/organization/default-vnext-organization.service.spec.ts rename to libs/common/src/admin-console/services/organization/default-organization.service.spec.ts index 9e2ea3a4599..41c89c0e41a 100644 --- a/libs/common/src/admin-console/services/organization/default-vnext-organization.service.spec.ts +++ b/libs/common/src/admin-console/services/organization/default-organization.service.spec.ts @@ -6,11 +6,11 @@ import { OrganizationId, UserId } from "../../../types/guid"; import { OrganizationData } from "../../models/data/organization.data"; import { Organization } from "../../models/domain/organization"; -import { DefaultvNextOrganizationService } from "./default-vnext-organization.service"; -import { ORGANIZATIONS } from "./vnext-organization.state"; +import { DefaultOrganizationService } from "./default-organization.service"; +import { ORGANIZATIONS } from "./organization.state"; describe("OrganizationService", () => { - let organizationService: DefaultvNextOrganizationService; + let organizationService: DefaultOrganizationService; const fakeUserId = Utils.newGuid() as UserId; let fakeStateProvider: FakeStateProvider; @@ -86,7 +86,7 @@ describe("OrganizationService", () => { beforeEach(async () => { fakeStateProvider = new FakeStateProvider(mockAccountServiceWith(fakeUserId)); - organizationService = new DefaultvNextOrganizationService(fakeStateProvider); + organizationService = new DefaultOrganizationService(fakeStateProvider); }); describe("canManageSponsorships", () => { diff --git a/libs/common/src/admin-console/services/organization/default-vnext-organization.service.ts b/libs/common/src/admin-console/services/organization/default-organization.service.ts similarity index 92% rename from libs/common/src/admin-console/services/organization/default-vnext-organization.service.ts rename to libs/common/src/admin-console/services/organization/default-organization.service.ts index 8b73c271daf..e78136455fd 100644 --- a/libs/common/src/admin-console/services/organization/default-vnext-organization.service.ts +++ b/libs/common/src/admin-console/services/organization/default-organization.service.ts @@ -4,11 +4,11 @@ import { map, Observable } from "rxjs"; import { StateProvider } from "../../../platform/state"; import { UserId } from "../../../types/guid"; -import { vNextInternalOrganizationServiceAbstraction } from "../../abstractions/organization/vnext.organization.service"; +import { InternalOrganizationServiceAbstraction } from "../../abstractions/organization/organization.service.abstraction"; import { OrganizationData } from "../../models/data/organization.data"; import { Organization } from "../../models/domain/organization"; -import { ORGANIZATIONS } from "./vnext-organization.state"; +import { ORGANIZATIONS } from "./organization.state"; /** * Filter out organizations from an observable that __do not__ offer a @@ -41,9 +41,7 @@ function mapToBooleanHasAnyOrganizations() { return map((orgs) => orgs.length > 0); } -export class DefaultvNextOrganizationService - implements vNextInternalOrganizationServiceAbstraction -{ +export class DefaultOrganizationService implements InternalOrganizationServiceAbstraction { memberOrganizations$(userId: UserId): Observable { return this.organizations$(userId).pipe(mapToExcludeProviderOrganizations()); } diff --git a/libs/common/src/admin-console/services/organization/organization-api.service.ts b/libs/common/src/admin-console/services/organization/organization-api.service.ts index b3fd11982b8..598bb2a29db 100644 --- a/libs/common/src/admin-console/services/organization/organization-api.service.ts +++ b/libs/common/src/admin-console/services/organization/organization-api.service.ts @@ -1,6 +1,5 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { BillingHistoryResponse } from "@bitwarden/common/billing/models/response/billing-history.response"; import { ApiService } from "../../../abstractions/api.service"; import { OrganizationApiKeyRequest } from "../../../admin-console/models/request/organization-api-key.request"; @@ -14,6 +13,7 @@ import { OrganizationSmSubscriptionUpdateRequest } from "../../../billing/models import { OrganizationSubscriptionUpdateRequest } from "../../../billing/models/request/organization-subscription-update.request"; import { PaymentRequest } from "../../../billing/models/request/payment.request"; import { SecretsManagerSubscribeRequest } from "../../../billing/models/request/sm-subscribe.request"; +import { BillingHistoryResponse } from "../../../billing/models/response/billing-history.response"; import { BillingResponse } from "../../../billing/models/response/billing.response"; import { OrganizationSubscriptionResponse } from "../../../billing/models/response/organization-subscription.response"; import { PaymentResponse } from "../../../billing/models/response/payment.response"; diff --git a/libs/common/src/admin-console/services/organization/organization.service.spec.ts b/libs/common/src/admin-console/services/organization/organization.service.spec.ts deleted file mode 100644 index 6d2525966bc..00000000000 --- a/libs/common/src/admin-console/services/organization/organization.service.spec.ts +++ /dev/null @@ -1,324 +0,0 @@ -import { firstValueFrom } from "rxjs"; - -import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from "../../../../spec"; -import { FakeActiveUserState } from "../../../../spec/fake-state"; -import { Utils } from "../../../platform/misc/utils"; -import { OrganizationId, UserId } from "../../../types/guid"; -import { OrganizationData } from "../../models/data/organization.data"; -import { Organization } from "../../models/domain/organization"; - -import { OrganizationService, ORGANIZATIONS } from "./organization.service"; - -describe("OrganizationService", () => { - let organizationService: OrganizationService; - - const fakeUserId = Utils.newGuid() as UserId; - let fakeAccountService: FakeAccountService; - let fakeStateProvider: FakeStateProvider; - let fakeActiveUserState: FakeActiveUserState>; - - /** - * It is easier to read arrays than records in code, but we store a record - * in state. This helper methods lets us build organization arrays in tests - * and easily map them to records before storing them in state. - */ - function arrayToRecord(input: OrganizationData[]): Record { - if (input == null) { - return undefined; - } - return Object.fromEntries(input?.map((i) => [i.id, i])); - } - - /** - * There are a few assertions in this spec that check for array equality - * but want to ignore a specific index that _should_ be different. This - * function takes two arrays, and an index. It checks for equality of the - * arrays, but splices out the specified index from both arrays first. - */ - function expectIsEqualExceptForIndex(x: any[], y: any[], indexToExclude: number) { - // Clone the arrays to avoid modifying the reference values - const a = [...x]; - const b = [...y]; - delete a[indexToExclude]; - delete b[indexToExclude]; - expect(a).toEqual(b); - } - - /** - * Builds a simple mock `OrganizationData[]` array that can be used in tests - * to populate state. - * @param count The number of organizations to populate the list with. The - * function returns undefined if this is less than 1. The default value is 1. - * @param suffix A string to append to data fields on each organization. - * This defaults to the index of the organization in the list. - * @returns an `OrganizationData[]` array that can be used to populate - * stateProvider. - */ - function buildMockOrganizations(count = 1, suffix?: string): OrganizationData[] { - if (count < 1) { - return undefined; - } - - function buildMockOrganization(id: OrganizationId, name: string, identifier: string) { - const data = new OrganizationData({} as any, {} as any); - data.id = id; - data.name = name; - data.identifier = identifier; - - return data; - } - - const mockOrganizations = []; - for (let i = 0; i < count; i++) { - const s = suffix ? suffix + i.toString() : i.toString(); - mockOrganizations.push( - buildMockOrganization(("org" + s) as OrganizationId, "org" + s, "orgIdentifier" + s), - ); - } - - return mockOrganizations; - } - - /** - * `OrganizationService` deals with multiple accounts at times. This helper - * function can be used to add a new non-active account to the test data. - * This function is **not** needed to handle creation of the first account, - * as that is handled by the `FakeAccountService` in `mockAccountServiceWith()` - * @returns The `UserId` of the newly created state account and the mock data - * created for them as an `Organization[]`. - */ - async function addNonActiveAccountToStateProvider(): Promise<[UserId, OrganizationData[]]> { - const nonActiveUserId = Utils.newGuid() as UserId; - - const mockOrganizations = buildMockOrganizations(10); - const fakeNonActiveUserState = fakeStateProvider.singleUser.getFake( - nonActiveUserId, - ORGANIZATIONS, - ); - fakeNonActiveUserState.nextState(arrayToRecord(mockOrganizations)); - - return [nonActiveUserId, mockOrganizations]; - } - - beforeEach(async () => { - fakeAccountService = mockAccountServiceWith(fakeUserId); - fakeStateProvider = new FakeStateProvider(fakeAccountService); - fakeActiveUserState = fakeStateProvider.activeUser.getFake(ORGANIZATIONS); - organizationService = new OrganizationService(fakeStateProvider); - }); - - it("getAll", async () => { - const mockData: OrganizationData[] = buildMockOrganizations(1); - fakeActiveUserState.nextState(arrayToRecord(mockData)); - const orgs = await organizationService.getAll(); - expect(orgs).toHaveLength(1); - const org = orgs[0]; - expect(org).toEqual(new Organization(mockData[0])); - }); - - describe("canManageSponsorships", () => { - it("can because one is available", async () => { - const mockData: OrganizationData[] = buildMockOrganizations(1); - mockData[0].familySponsorshipAvailable = true; - fakeActiveUserState.nextState(arrayToRecord(mockData)); - const result = await firstValueFrom(organizationService.canManageSponsorships$); - expect(result).toBe(true); - }); - - it("can because one is used", async () => { - const mockData: OrganizationData[] = buildMockOrganizations(1); - mockData[0].familySponsorshipFriendlyName = "Something"; - fakeActiveUserState.nextState(arrayToRecord(mockData)); - const result = await firstValueFrom(organizationService.canManageSponsorships$); - expect(result).toBe(true); - }); - - it("can not because one isn't available or taken", async () => { - const mockData: OrganizationData[] = buildMockOrganizations(1); - mockData[0].familySponsorshipFriendlyName = null; - fakeActiveUserState.nextState(arrayToRecord(mockData)); - const result = await firstValueFrom(organizationService.canManageSponsorships$); - expect(result).toBe(false); - }); - }); - - describe("get", () => { - it("exists", async () => { - const mockData = buildMockOrganizations(1); - fakeActiveUserState.nextState(arrayToRecord(mockData)); - const result = await organizationService.get(mockData[0].id); - expect(result).toEqual(new Organization(mockData[0])); - }); - - it("does not exist", async () => { - const result = await organizationService.get("this-org-does-not-exist"); - expect(result).toBe(undefined); - }); - }); - - describe("organizations$", () => { - describe("null checking behavior", () => { - it("publishes an empty array if organizations in state = undefined", async () => { - const mockData: OrganizationData[] = undefined; - fakeActiveUserState.nextState(arrayToRecord(mockData)); - const result = await firstValueFrom(organizationService.organizations$); - expect(result).toEqual([]); - }); - - it("publishes an empty array if organizations in state = null", async () => { - const mockData: OrganizationData[] = null; - fakeActiveUserState.nextState(arrayToRecord(mockData)); - const result = await firstValueFrom(organizationService.organizations$); - expect(result).toEqual([]); - }); - - it("publishes an empty array if organizations in state = []", async () => { - const mockData: OrganizationData[] = []; - fakeActiveUserState.nextState(arrayToRecord(mockData)); - const result = await firstValueFrom(organizationService.organizations$); - expect(result).toEqual([]); - }); - }); - - describe("parameter handling & returns", () => { - it("publishes all organizations for the active user by default", async () => { - const mockData = buildMockOrganizations(10); - fakeActiveUserState.nextState(arrayToRecord(mockData)); - const result = await firstValueFrom(organizationService.organizations$); - expect(result).toEqual(mockData); - }); - - it("can be used to publish the organizations of a non active user if requested", async () => { - const activeUserMockData = buildMockOrganizations(10, "activeUserState"); - fakeActiveUserState.nextState(arrayToRecord(activeUserMockData)); - - const [nonActiveUserId, nonActiveUserMockOrganizations] = - await addNonActiveAccountToStateProvider(); - // This can be updated to use - // `firstValueFrom(organizations$(nonActiveUserId)` once all the - // promise based methods are removed from `OrganizationService` and the - // main observable is refactored to accept a userId - const result = await organizationService.getAll(nonActiveUserId); - - expect(result).toEqual(nonActiveUserMockOrganizations); - expect(result).not.toEqual(await firstValueFrom(organizationService.organizations$)); - }); - }); - }); - - describe("upsert()", () => { - it("can create the organization list if necassary", async () => { - // Notice that no default state is provided in this test, so the list in - // `stateProvider` will be null when the `upsert` method is called. - const mockData = buildMockOrganizations(); - await organizationService.upsert(mockData[0]); - const result = await firstValueFrom(organizationService.organizations$); - expect(result).toEqual(mockData.map((x) => new Organization(x))); - }); - - it("updates an organization that already exists in state, defaulting to the active user", async () => { - const mockData = buildMockOrganizations(10); - fakeActiveUserState.nextState(arrayToRecord(mockData)); - const indexToUpdate = 5; - const anUpdatedOrganization = { - ...buildMockOrganizations(1, "UPDATED").pop(), - id: mockData[indexToUpdate].id, - }; - await organizationService.upsert(anUpdatedOrganization); - const result = await firstValueFrom(organizationService.organizations$); - expect(result[indexToUpdate]).not.toEqual(new Organization(mockData[indexToUpdate])); - expect(result[indexToUpdate].id).toEqual(new Organization(mockData[indexToUpdate]).id); - expectIsEqualExceptForIndex( - result, - mockData.map((x) => new Organization(x)), - indexToUpdate, - ); - }); - - it("can also update an organization in state for a non-active user, if requested", async () => { - const activeUserMockData = buildMockOrganizations(10, "activeUserOrganizations"); - fakeActiveUserState.nextState(arrayToRecord(activeUserMockData)); - - const [nonActiveUserId, nonActiveUserMockOrganizations] = - await addNonActiveAccountToStateProvider(); - const indexToUpdate = 5; - const anUpdatedOrganization = { - ...buildMockOrganizations(1, "UPDATED").pop(), - id: nonActiveUserMockOrganizations[indexToUpdate].id, - }; - - await organizationService.upsert(anUpdatedOrganization, nonActiveUserId); - // This can be updated to use - // `firstValueFrom(organizations$(nonActiveUserId)` once all the - // promise based methods are removed from `OrganizationService` and the - // main observable is refactored to accept a userId - const result = await organizationService.getAll(nonActiveUserId); - - expect(result[indexToUpdate]).not.toEqual( - new Organization(nonActiveUserMockOrganizations[indexToUpdate]), - ); - expect(result[indexToUpdate].id).toEqual( - new Organization(nonActiveUserMockOrganizations[indexToUpdate]).id, - ); - expectIsEqualExceptForIndex( - result, - nonActiveUserMockOrganizations.map((x) => new Organization(x)), - indexToUpdate, - ); - - // Just to be safe, lets make sure the active user didn't get updated - // at all - const activeUserState = await firstValueFrom(organizationService.organizations$); - expect(activeUserState).toEqual(activeUserMockData.map((x) => new Organization(x))); - expect(activeUserState).not.toEqual(result); - }); - }); - - describe("replace()", () => { - it("replaces the entire organization list in state, defaulting to the active user", async () => { - const originalData = buildMockOrganizations(10); - fakeActiveUserState.nextState(arrayToRecord(originalData)); - - const newData = buildMockOrganizations(10, "newData"); - await organizationService.replace(arrayToRecord(newData)); - - const result = await firstValueFrom(organizationService.organizations$); - - expect(result).toEqual(newData); - expect(result).not.toEqual(originalData); - }); - - // This is more or less a test for logouts - it("can replace state with null", async () => { - const originalData = buildMockOrganizations(2); - fakeActiveUserState.nextState(arrayToRecord(originalData)); - await organizationService.replace(null); - const result = await firstValueFrom(organizationService.organizations$); - expect(result).toEqual([]); - expect(result).not.toEqual(originalData); - }); - - it("can also replace state for a non-active user, if requested", async () => { - const activeUserMockData = buildMockOrganizations(10, "activeUserOrganizations"); - fakeActiveUserState.nextState(arrayToRecord(activeUserMockData)); - - const [nonActiveUserId, originalOrganizations] = await addNonActiveAccountToStateProvider(); - const newData = buildMockOrganizations(10, "newData"); - - await organizationService.replace(arrayToRecord(newData), nonActiveUserId); - // This can be updated to use - // `firstValueFrom(organizations$(nonActiveUserId)` once all the - // promise based methods are removed from `OrganizationService` and the - // main observable is refactored to accept a userId - const result = await organizationService.getAll(nonActiveUserId); - expect(result).toEqual(newData); - expect(result).not.toEqual(originalOrganizations); - - // Just to be safe, lets make sure the active user didn't get updated - // at all - const activeUserState = await firstValueFrom(organizationService.organizations$); - expect(activeUserState).toEqual(activeUserMockData.map((x) => new Organization(x))); - expect(activeUserState).not.toEqual(result); - }); - }); -}); diff --git a/libs/common/src/admin-console/services/organization/organization.service.ts b/libs/common/src/admin-console/services/organization/organization.service.ts deleted file mode 100644 index 49e906bdac2..00000000000 --- a/libs/common/src/admin-console/services/organization/organization.service.ts +++ /dev/null @@ -1,160 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { map, Observable, firstValueFrom } from "rxjs"; -import { Jsonify } from "type-fest"; - -import { ORGANIZATIONS_DISK, StateProvider, UserKeyDefinition } from "../../../platform/state"; -import { UserId } from "../../../types/guid"; -import { InternalOrganizationServiceAbstraction } from "../../abstractions/organization/organization.service.abstraction"; -import { OrganizationData } from "../../models/data/organization.data"; -import { Organization } from "../../models/domain/organization"; - -/** - * The `KeyDefinition` for accessing organization lists in application state. - * @todo Ideally this wouldn't require a `fromJSON()` call, but `OrganizationData` - * has some properties that contain functions. This should probably get - * cleaned up. - */ -export const ORGANIZATIONS = UserKeyDefinition.record( - ORGANIZATIONS_DISK, - "organizations", - { - deserializer: (obj: Jsonify) => OrganizationData.fromJSON(obj), - clearOn: ["logout"], - }, -); - -/** - * Filter out organizations from an observable that __do not__ offer a - * families-for-enterprise sponsorship to members. - * @returns a function that can be used in `Observable` pipes, - * like `organizationService.organizations$` - */ -function mapToExcludeOrganizationsWithoutFamilySponsorshipSupport() { - return map((orgs) => orgs.filter((o) => o.canManageSponsorships)); -} - -/** - * Filter out organizations from an observable that the organization user - * __is not__ a direct member of. This will exclude organizations only - * accessible as a provider. - * @returns a function that can be used in `Observable` pipes, - * like `organizationService.organizations$` - */ -function mapToExcludeProviderOrganizations() { - return map((orgs) => orgs.filter((o) => o.isMember)); -} - -/** - * Map an observable stream of organizations down to a boolean indicating - * if any organizations exist (`orgs.length > 0`). - * @returns a function that can be used in `Observable` pipes, - * like `organizationService.organizations$` - */ -function mapToBooleanHasAnyOrganizations() { - return map((orgs) => orgs.length > 0); -} - -/** - * Map an observable stream of organizations down to a single organization. - * @param `organizationId` The ID of the organization you'd like to subscribe to - * @returns a function that can be used in `Observable` pipes, - * like `organizationService.organizations$` - */ -function mapToSingleOrganization(organizationId: string) { - return map((orgs) => orgs?.find((o) => o.id === organizationId)); -} - -export class OrganizationService implements InternalOrganizationServiceAbstraction { - organizations$: Observable = this.getOrganizationsFromState$(); - memberOrganizations$: Observable = this.organizations$.pipe( - mapToExcludeProviderOrganizations(), - ); - - constructor(private stateProvider: StateProvider) {} - - get$(id: string): Observable { - return this.organizations$.pipe(mapToSingleOrganization(id)); - } - - getAll$(userId?: UserId): Observable { - return this.getOrganizationsFromState$(userId); - } - - async getAll(userId?: string): Promise { - return await firstValueFrom(this.getOrganizationsFromState$(userId as UserId)); - } - - canManageSponsorships$ = this.organizations$.pipe( - mapToExcludeOrganizationsWithoutFamilySponsorshipSupport(), - mapToBooleanHasAnyOrganizations(), - ); - - familySponsorshipAvailable$ = this.organizations$.pipe( - map((orgs) => orgs.some((o) => o.familySponsorshipAvailable)), - ); - - async hasOrganizations(): Promise { - return await firstValueFrom(this.organizations$.pipe(mapToBooleanHasAnyOrganizations())); - } - - async upsert(organization: OrganizationData, userId?: UserId): Promise { - await this.stateFor(userId).update((existingOrganizations) => { - const organizations = existingOrganizations ?? {}; - organizations[organization.id] = organization; - return organizations; - }); - } - - async get(id: string): Promise { - return await firstValueFrom(this.organizations$.pipe(mapToSingleOrganization(id))); - } - - /** - * @deprecated For the CLI only - * @param id id of the organization - */ - async getFromState(id: string): Promise { - return await firstValueFrom(this.organizations$.pipe(mapToSingleOrganization(id))); - } - - async replace(organizations: { [id: string]: OrganizationData }, userId?: UserId): Promise { - await this.stateFor(userId).update(() => organizations); - } - - // Ideally this method would be renamed to organizations$() and the - // $organizations observable as it stands would be removed. This will - // require updates to callers, and so this method exists as a temporary - // workaround until we have time & a plan to update callers. - // - // It can be thought of as "organizations$ but with a userId option". - private getOrganizationsFromState$(userId?: UserId): Observable { - return this.stateFor(userId).state$.pipe(this.mapOrganizationRecordToArray()); - } - - /** - * Accepts a record of `OrganizationData`, which is how we store the - * organization list as a JSON object on disk, to an array of - * `Organization`, which is how the data is published to callers of the - * service. - * @returns a function that can be used to pipe organization data from - * stored state to an exposed object easily consumable by others. - */ - private mapOrganizationRecordToArray() { - return map, Organization[]>((orgs) => - Object.values(orgs ?? {})?.map((o) => new Organization(o)), - ); - } - - /** - * Fetches the organization list from on disk state for the specified user. - * @param userId the user ID to fetch the organization list for. Defaults to - * the currently active user. - * @returns an observable of organization state as it is stored on disk. - */ - private stateFor(userId?: UserId) { - return userId - ? this.stateProvider.getUser(userId, ORGANIZATIONS) - : this.stateProvider.getActive(ORGANIZATIONS); - } -} diff --git a/libs/common/src/admin-console/services/organization/vnext-organization.state.ts b/libs/common/src/admin-console/services/organization/organization.state.ts similarity index 86% rename from libs/common/src/admin-console/services/organization/vnext-organization.state.ts rename to libs/common/src/admin-console/services/organization/organization.state.ts index 48e09d6d076..fea0423f389 100644 --- a/libs/common/src/admin-console/services/organization/vnext-organization.state.ts +++ b/libs/common/src/admin-console/services/organization/organization.state.ts @@ -1,7 +1,6 @@ import { Jsonify } from "type-fest"; -import { ORGANIZATIONS_DISK, UserKeyDefinition } from "@bitwarden/common/platform/state"; - +import { ORGANIZATIONS_DISK, UserKeyDefinition } from "../../../platform/state"; import { OrganizationData } from "../../models/data/organization.data"; /** diff --git a/libs/common/src/admin-console/services/policy/default-vnext-policy.service.spec.ts b/libs/common/src/admin-console/services/policy/default-vnext-policy.service.spec.ts new file mode 100644 index 00000000000..f58e1d27ee6 --- /dev/null +++ b/libs/common/src/admin-console/services/policy/default-vnext-policy.service.spec.ts @@ -0,0 +1,590 @@ +import { mock, MockProxy } from "jest-mock-extended"; +import { firstValueFrom, of } from "rxjs"; + +import { FakeStateProvider, mockAccountServiceWith } from "../../../../spec"; +import { FakeSingleUserState } from "../../../../spec/fake-state"; +import { + OrganizationUserStatusType, + OrganizationUserType, + PolicyType, +} from "../../../admin-console/enums"; +import { PermissionsApi } from "../../../admin-console/models/api/permissions.api"; +import { OrganizationData } from "../../../admin-console/models/data/organization.data"; +import { PolicyData } from "../../../admin-console/models/data/policy.data"; +import { MasterPasswordPolicyOptions } from "../../../admin-console/models/domain/master-password-policy-options"; +import { Organization } from "../../../admin-console/models/domain/organization"; +import { Policy } from "../../../admin-console/models/domain/policy"; +import { ResetPasswordPolicyOptions } from "../../../admin-console/models/domain/reset-password-policy-options"; +import { POLICIES } from "../../../admin-console/services/policy/policy.service"; +import { PolicyId, UserId } from "../../../types/guid"; +import { OrganizationService } from "../../abstractions/organization/organization.service.abstraction"; + +import { DefaultvNextPolicyService, getFirstPolicy } from "./default-vnext-policy.service"; + +describe("PolicyService", () => { + const userId = "userId" as UserId; + let stateProvider: FakeStateProvider; + let organizationService: MockProxy; + let singleUserState: FakeSingleUserState>; + + let policyService: DefaultvNextPolicyService; + + beforeEach(() => { + const accountService = mockAccountServiceWith(userId); + stateProvider = new FakeStateProvider(accountService); + organizationService = mock(); + singleUserState = stateProvider.singleUser.getFake(userId, POLICIES); + + const organizations$ = of([ + // User + organization("org1", true, true, OrganizationUserStatusType.Confirmed, false), + // Owner + organization( + "org2", + true, + true, + OrganizationUserStatusType.Confirmed, + false, + OrganizationUserType.Owner, + ), + // Does not use policies + organization("org3", true, false, OrganizationUserStatusType.Confirmed, false), + // Another User + organization("org4", true, true, OrganizationUserStatusType.Confirmed, false), + // Another User + organization("org5", true, true, OrganizationUserStatusType.Confirmed, false), + // Can manage policies + organization("org6", true, true, OrganizationUserStatusType.Confirmed, true), + ]); + + organizationService.organizations$.calledWith(userId).mockReturnValue(organizations$); + + policyService = new DefaultvNextPolicyService(stateProvider, organizationService); + }); + + it("upsert", async () => { + singleUserState.nextState( + arrayToRecord([ + policyData("1", "test-organization", PolicyType.MaximumVaultTimeout, true, { minutes: 14 }), + ]), + ); + + await policyService.upsert( + policyData("99", "test-organization", PolicyType.DisableSend, true), + userId, + ); + + expect(await firstValueFrom(policyService.policies$(userId))).toEqual([ + { + id: "1", + organizationId: "test-organization", + type: PolicyType.MaximumVaultTimeout, + enabled: true, + data: { minutes: 14 }, + }, + { + id: "99", + organizationId: "test-organization", + type: PolicyType.DisableSend, + enabled: true, + }, + ]); + }); + + it("replace", async () => { + singleUserState.nextState( + arrayToRecord([ + policyData("1", "test-organization", PolicyType.MaximumVaultTimeout, true, { minutes: 14 }), + ]), + ); + + await policyService.replace( + { + "2": policyData("2", "test-organization", PolicyType.DisableSend, true), + }, + userId, + ); + + expect(await firstValueFrom(policyService.policies$(userId))).toEqual([ + { + id: "2", + organizationId: "test-organization", + type: PolicyType.DisableSend, + enabled: true, + }, + ]); + }); + + describe("masterPasswordPolicyOptions", () => { + it("returns default policy options", async () => { + const data: any = { + minComplexity: 5, + minLength: 20, + requireUpper: true, + }; + const model = [ + new Policy(policyData("1", "test-organization-3", PolicyType.MasterPassword, true, data)), + ]; + jest.spyOn(policyService as any, "policies$").mockReturnValue(of(model)); + + const result = await firstValueFrom(policyService.masterPasswordPolicyOptions$(userId)); + + expect(result).toEqual({ + minComplexity: 5, + minLength: 20, + requireLower: false, + requireNumbers: false, + requireSpecial: false, + requireUpper: true, + enforceOnLogin: false, + }); + }); + + it("returns undefined", async () => { + const data: any = {}; + const model = [ + new Policy( + policyData("3", "test-organization-3", PolicyType.DisablePersonalVaultExport, true, data), + ), + new Policy( + policyData("4", "test-organization-3", PolicyType.MaximumVaultTimeout, true, data), + ), + ]; + jest.spyOn(policyService as any, "policies$").mockReturnValue(of(model)); + + const result = await firstValueFrom(policyService.masterPasswordPolicyOptions$(userId)); + + expect(result).toBeUndefined(); + }); + + it("returns specified policy options", async () => { + const data: any = { + minLength: 14, + }; + const model = [ + new Policy( + policyData("3", "test-organization-3", PolicyType.DisablePersonalVaultExport, true, data), + ), + new Policy(policyData("4", "test-organization-3", PolicyType.MasterPassword, true, data)), + ]; + jest.spyOn(policyService as any, "policies$").mockReturnValue(of(model)); + + const result = await firstValueFrom(policyService.masterPasswordPolicyOptions$(userId)); + + expect(result).toEqual({ + minComplexity: 0, + minLength: 14, + requireLower: false, + requireNumbers: false, + requireSpecial: false, + requireUpper: false, + enforceOnLogin: false, + }); + }); + }); + + describe("evaluateMasterPassword", () => { + it("false", async () => { + const enforcedPolicyOptions = new MasterPasswordPolicyOptions(); + enforcedPolicyOptions.minLength = 14; + const result = policyService.evaluateMasterPassword(10, "password", enforcedPolicyOptions); + + expect(result).toEqual(false); + }); + + it("true", async () => { + const enforcedPolicyOptions = new MasterPasswordPolicyOptions(); + const result = policyService.evaluateMasterPassword(0, "password", enforcedPolicyOptions); + + expect(result).toEqual(true); + }); + }); + + describe("getResetPasswordPolicyOptions", () => { + it("default", async () => { + const result = policyService.getResetPasswordPolicyOptions([], ""); + + expect(result).toEqual([new ResetPasswordPolicyOptions(), false]); + }); + + it("returns autoEnrollEnabled true", async () => { + const data: any = { + autoEnrollEnabled: true, + }; + const policies = [ + new Policy(policyData("5", "test-organization-3", PolicyType.ResetPassword, true, data)), + ]; + const result = policyService.getResetPasswordPolicyOptions(policies, "test-organization-3"); + + expect(result).toEqual([{ autoEnrollEnabled: true }, true]); + }); + }); + + describe("policiesByType$", () => { + it("returns the specified PolicyType", async () => { + singleUserState.nextState( + arrayToRecord([ + policyData("policy1", "org1", PolicyType.ActivateAutofill, true), + policyData("policy2", "org1", PolicyType.DisablePersonalVaultExport, true), + ]), + ); + + const result = await firstValueFrom( + policyService + .policiesByType$(PolicyType.DisablePersonalVaultExport, userId) + .pipe(getFirstPolicy), + ); + + expect(result).toEqual({ + id: "policy2", + organizationId: "org1", + type: PolicyType.DisablePersonalVaultExport, + enabled: true, + }); + }); + + it("does not return disabled policies", async () => { + singleUserState.nextState( + arrayToRecord([ + policyData("policy1", "org1", PolicyType.ActivateAutofill, true), + policyData("policy2", "org1", PolicyType.DisablePersonalVaultExport, false), + ]), + ); + + const result = await firstValueFrom( + policyService + .policiesByType$(PolicyType.DisablePersonalVaultExport, userId) + .pipe(getFirstPolicy), + ); + + expect(result).toBeUndefined(); + }); + + it("does not return policies that do not apply to the user because the user's role is exempt", async () => { + singleUserState.nextState( + arrayToRecord([ + policyData("policy1", "org1", PolicyType.ActivateAutofill, true), + policyData("policy2", "org2", PolicyType.DisablePersonalVaultExport, false), + ]), + ); + + const result = await firstValueFrom( + policyService + .policiesByType$(PolicyType.DisablePersonalVaultExport, userId) + .pipe(getFirstPolicy), + ); + expect(result).toBeUndefined(); + }); + + it.each([ + ["owners", "org2"], + ["administrators", "org6"], + ])("returns the password generator policy for %s", async (_, organization) => { + singleUserState.nextState( + arrayToRecord([ + policyData("policy1", "org1", PolicyType.ActivateAutofill, false), + policyData("policy2", organization, PolicyType.PasswordGenerator, true), + ]), + ); + + const result = await firstValueFrom( + policyService.policiesByType$(PolicyType.PasswordGenerator, userId).pipe(getFirstPolicy), + ); + + expect(result).toBeTruthy(); + }); + + it("does not return policies for organizations that do not use policies", async () => { + singleUserState.nextState( + arrayToRecord([ + policyData("policy1", "org3", PolicyType.ActivateAutofill, true), + policyData("policy2", "org2", PolicyType.DisablePersonalVaultExport, true), + ]), + ); + + const result = await firstValueFrom( + policyService.policiesByType$(PolicyType.ActivateAutofill, userId).pipe(getFirstPolicy), + ); + + expect(result).toBeUndefined(); + }); + }); + + describe("policies$", () => { + it("returns all policies when none are disabled", async () => { + singleUserState.nextState( + arrayToRecord([ + policyData("policy1", "org4", PolicyType.DisablePersonalVaultExport, true), + policyData("policy2", "org1", PolicyType.ActivateAutofill, true), + policyData("policy3", "org5", PolicyType.DisablePersonalVaultExport, true), + policyData("policy4", "org1", PolicyType.DisablePersonalVaultExport, true), + ]), + ); + + const result = await firstValueFrom(policyService.policies$(userId)); + + expect(result).toEqual([ + { + id: "policy1", + organizationId: "org4", + type: PolicyType.DisablePersonalVaultExport, + enabled: true, + }, + { + id: "policy2", + organizationId: "org1", + type: PolicyType.ActivateAutofill, + enabled: true, + }, + { + id: "policy3", + organizationId: "org5", + type: PolicyType.DisablePersonalVaultExport, + enabled: true, + }, + { + id: "policy4", + organizationId: "org1", + type: PolicyType.DisablePersonalVaultExport, + enabled: true, + }, + ]); + }); + + it("returns all policies when some are disabled", async () => { + singleUserState.nextState( + arrayToRecord([ + policyData("policy1", "org4", PolicyType.DisablePersonalVaultExport, true), + policyData("policy2", "org1", PolicyType.ActivateAutofill, true), + policyData("policy3", "org5", PolicyType.DisablePersonalVaultExport, false), // disabled + policyData("policy4", "org1", PolicyType.DisablePersonalVaultExport, true), + ]), + ); + + const result = await firstValueFrom(policyService.policies$(userId)); + + expect(result).toEqual([ + { + id: "policy1", + organizationId: "org4", + type: PolicyType.DisablePersonalVaultExport, + enabled: true, + }, + { + id: "policy2", + organizationId: "org1", + type: PolicyType.ActivateAutofill, + enabled: true, + }, + { + id: "policy3", + organizationId: "org5", + type: PolicyType.DisablePersonalVaultExport, + enabled: false, + }, + { + id: "policy4", + organizationId: "org1", + type: PolicyType.DisablePersonalVaultExport, + enabled: true, + }, + ]); + }); + + it("returns policies that do not apply to the user because the user's role is exempt", async () => { + singleUserState.nextState( + arrayToRecord([ + policyData("policy1", "org4", PolicyType.DisablePersonalVaultExport, true), + policyData("policy2", "org1", PolicyType.ActivateAutofill, true), + policyData("policy3", "org5", PolicyType.DisablePersonalVaultExport, true), + policyData("policy4", "org2", PolicyType.DisablePersonalVaultExport, true), // owner + ]), + ); + + const result = await firstValueFrom(policyService.policies$(userId)); + + expect(result).toEqual([ + { + id: "policy1", + organizationId: "org4", + type: PolicyType.DisablePersonalVaultExport, + enabled: true, + }, + { + id: "policy2", + organizationId: "org1", + type: PolicyType.ActivateAutofill, + enabled: true, + }, + { + id: "policy3", + organizationId: "org5", + type: PolicyType.DisablePersonalVaultExport, + enabled: true, + }, + { + id: "policy4", + organizationId: "org2", + type: PolicyType.DisablePersonalVaultExport, + enabled: true, + }, + ]); + }); + + it("does not return policies for organizations that do not use policies", async () => { + singleUserState.nextState( + arrayToRecord([ + policyData("policy1", "org4", PolicyType.DisablePersonalVaultExport, true), + policyData("policy2", "org1", PolicyType.ActivateAutofill, true), + policyData("policy3", "org3", PolicyType.DisablePersonalVaultExport, true), // does not use policies + policyData("policy4", "org1", PolicyType.DisablePersonalVaultExport, true), + ]), + ); + + const result = await firstValueFrom(policyService.policies$(userId)); + + expect(result).toEqual([ + { + id: "policy1", + organizationId: "org4", + type: PolicyType.DisablePersonalVaultExport, + enabled: true, + }, + { + id: "policy2", + organizationId: "org1", + type: PolicyType.ActivateAutofill, + enabled: true, + }, + { + id: "policy3", + organizationId: "org3", + type: PolicyType.DisablePersonalVaultExport, + enabled: true, + }, + { + id: "policy4", + organizationId: "org1", + type: PolicyType.DisablePersonalVaultExport, + enabled: true, + }, + ]); + }); + }); + + describe("policyAppliesToUser$", () => { + it("returns true when the policyType applies to the user", async () => { + singleUserState.nextState( + arrayToRecord([ + policyData("policy1", "org4", PolicyType.DisablePersonalVaultExport, true), + policyData("policy2", "org1", PolicyType.ActivateAutofill, true), + policyData("policy3", "org5", PolicyType.DisablePersonalVaultExport, true), + policyData("policy4", "org1", PolicyType.DisablePersonalVaultExport, true), + ]), + ); + + const result = await firstValueFrom( + policyService.policyAppliesToUser$(PolicyType.DisablePersonalVaultExport, userId), + ); + + expect(result).toBe(true); + }); + + it("returns false when policyType is disabled", async () => { + singleUserState.nextState( + arrayToRecord([ + policyData("policy2", "org1", PolicyType.ActivateAutofill, true), + policyData("policy3", "org5", PolicyType.DisablePersonalVaultExport, false), // disabled + ]), + ); + + const result = await firstValueFrom( + policyService.policyAppliesToUser$(PolicyType.DisablePersonalVaultExport, userId), + ); + + expect(result).toBe(false); + }); + + it("returns false when the policyType does not apply to the user because the user's role is exempt", async () => { + singleUserState.nextState( + arrayToRecord([ + policyData("policy2", "org1", PolicyType.ActivateAutofill, true), + policyData("policy4", "org2", PolicyType.DisablePersonalVaultExport, true), // owner + ]), + ); + + const result = await firstValueFrom( + policyService.policyAppliesToUser$(PolicyType.DisablePersonalVaultExport, userId), + ); + + expect(result).toBe(false); + }); + + it("returns false for organizations that do not use policies", async () => { + singleUserState.nextState( + arrayToRecord([ + policyData("policy2", "org1", PolicyType.ActivateAutofill, true), + policyData("policy3", "org3", PolicyType.DisablePersonalVaultExport, true), // does not use policies + ]), + ); + + const result = await firstValueFrom( + policyService.policyAppliesToUser$(PolicyType.DisablePersonalVaultExport, userId), + ); + + expect(result).toBe(false); + }); + }); + + function policyData( + id: string, + organizationId: string, + type: PolicyType, + enabled: boolean, + data?: any, + ) { + const policyData = new PolicyData({} as any); + policyData.id = id as PolicyId; + policyData.organizationId = organizationId; + policyData.type = type; + policyData.enabled = enabled; + policyData.data = data; + + return policyData; + } + + function organizationData( + id: string, + enabled: boolean, + usePolicies: boolean, + status: OrganizationUserStatusType, + managePolicies: boolean, + type: OrganizationUserType = OrganizationUserType.User, + ) { + const organizationData = new OrganizationData({} as any, {} as any); + organizationData.id = id; + organizationData.enabled = enabled; + organizationData.usePolicies = usePolicies; + organizationData.status = status; + organizationData.permissions = new PermissionsApi({ managePolicies: managePolicies } as any); + organizationData.type = type; + return organizationData; + } + + function organization( + id: string, + enabled: boolean, + usePolicies: boolean, + status: OrganizationUserStatusType, + managePolicies: boolean, + type: OrganizationUserType = OrganizationUserType.User, + ) { + return new Organization( + organizationData(id, enabled, usePolicies, status, managePolicies, type), + ); + } + + function arrayToRecord(input: PolicyData[]): Record { + return Object.fromEntries(input.map((i) => [i.id, i])); + } +}); diff --git a/libs/common/src/admin-console/services/policy/default-vnext-policy.service.ts b/libs/common/src/admin-console/services/policy/default-vnext-policy.service.ts new file mode 100644 index 00000000000..bc56638a987 --- /dev/null +++ b/libs/common/src/admin-console/services/policy/default-vnext-policy.service.ts @@ -0,0 +1,240 @@ +import { combineLatest, map, Observable, of } from "rxjs"; + +import { StateProvider } from "../../../platform/state"; +import { UserId } from "../../../types/guid"; +import { OrganizationService } from "../../abstractions/organization/organization.service.abstraction"; +import { vNextPolicyService } from "../../abstractions/policy/vnext-policy.service"; +import { OrganizationUserStatusType, PolicyType } from "../../enums"; +import { PolicyData } from "../../models/data/policy.data"; +import { MasterPasswordPolicyOptions } from "../../models/domain/master-password-policy-options"; +import { Organization } from "../../models/domain/organization"; +import { Policy } from "../../models/domain/policy"; +import { ResetPasswordPolicyOptions } from "../../models/domain/reset-password-policy-options"; + +import { POLICIES } from "./vnext-policy-state"; + +export function policyRecordToArray(policiesMap: { [id: string]: PolicyData }) { + return Object.values(policiesMap || {}).map((f) => new Policy(f)); +} + +export const getFirstPolicy = map((policies) => { + return policies.at(0) ?? undefined; +}); + +export class DefaultvNextPolicyService implements vNextPolicyService { + constructor( + private stateProvider: StateProvider, + private organizationService: OrganizationService, + ) {} + + private policyState(userId: UserId) { + return this.stateProvider.getUser(userId, POLICIES); + } + + private policyData$(userId: UserId) { + return this.policyState(userId).state$.pipe(map((policyData) => policyData ?? {})); + } + + policies$(userId: UserId) { + return this.policyData$(userId).pipe(map((policyData) => policyRecordToArray(policyData))); + } + + policiesByType$(policyType: PolicyType, userId: UserId) { + const filteredPolicies$ = this.policies$(userId).pipe( + map((policies) => policies.filter((p) => p.type === policyType)), + ); + + if (!userId) { + throw new Error("No userId provided"); + } + + const organizations$ = this.organizationService.organizations$(userId); + + return combineLatest([filteredPolicies$, organizations$]).pipe( + map(([policies, organizations]) => this.enforcedPolicyFilter(policies, organizations)), + ); + } + + policyAppliesToUser$(policyType: PolicyType, userId: UserId) { + return this.policiesByType$(policyType, userId).pipe( + getFirstPolicy, + map((policy) => !!policy), + ); + } + + private enforcedPolicyFilter(policies: Policy[], organizations: Organization[]) { + const orgDict = Object.fromEntries(organizations.map((o) => [o.id, o])); + return policies.filter((policy) => { + const organization = orgDict[policy.organizationId]; + + // This shouldn't happen, i.e. the user should only have policies for orgs they are a member of + // But if it does, err on the side of enforcing the policy + if (!organization) { + return true; + } + + return ( + policy.enabled && + organization.status >= OrganizationUserStatusType.Accepted && + organization.usePolicies && + !this.isExemptFromPolicy(policy.type, organization) + ); + }); + } + + masterPasswordPolicyOptions$( + userId: UserId, + policies?: Policy[], + ): Observable { + const policies$ = policies ? of(policies) : this.policies$(userId); + return policies$.pipe( + map((obsPolicies) => { + const enforcedOptions: MasterPasswordPolicyOptions = new MasterPasswordPolicyOptions(); + const filteredPolicies = + obsPolicies.filter((p) => p.type === PolicyType.MasterPassword) ?? []; + + if (filteredPolicies.length === 0) { + return; + } + + filteredPolicies.forEach((currentPolicy) => { + if (!currentPolicy.enabled || !currentPolicy.data) { + return; + } + + if ( + currentPolicy.data.minComplexity != null && + currentPolicy.data.minComplexity > enforcedOptions.minComplexity + ) { + enforcedOptions.minComplexity = currentPolicy.data.minComplexity; + } + + if ( + currentPolicy.data.minLength != null && + currentPolicy.data.minLength > enforcedOptions.minLength + ) { + enforcedOptions.minLength = currentPolicy.data.minLength; + } + + if (currentPolicy.data.requireUpper) { + enforcedOptions.requireUpper = true; + } + + if (currentPolicy.data.requireLower) { + enforcedOptions.requireLower = true; + } + + if (currentPolicy.data.requireNumbers) { + enforcedOptions.requireNumbers = true; + } + + if (currentPolicy.data.requireSpecial) { + enforcedOptions.requireSpecial = true; + } + + if (currentPolicy.data.enforceOnLogin) { + enforcedOptions.enforceOnLogin = true; + } + }); + + return enforcedOptions; + }), + ); + } + + evaluateMasterPassword( + passwordStrength: number, + newPassword: string, + enforcedPolicyOptions?: MasterPasswordPolicyOptions, + ): boolean { + if (!enforcedPolicyOptions) { + return true; + } + + if ( + enforcedPolicyOptions.minComplexity > 0 && + enforcedPolicyOptions.minComplexity > passwordStrength + ) { + return false; + } + + if ( + enforcedPolicyOptions.minLength > 0 && + enforcedPolicyOptions.minLength > newPassword.length + ) { + return false; + } + + if (enforcedPolicyOptions.requireUpper && newPassword.toLocaleLowerCase() === newPassword) { + return false; + } + + if (enforcedPolicyOptions.requireLower && newPassword.toLocaleUpperCase() === newPassword) { + return false; + } + + if (enforcedPolicyOptions.requireNumbers && !/[0-9]/.test(newPassword)) { + return false; + } + + // eslint-disable-next-line + if (enforcedPolicyOptions.requireSpecial && !/[!@#$%\^&*]/g.test(newPassword)) { + return false; + } + + return true; + } + + getResetPasswordPolicyOptions( + policies: Policy[], + orgId: string, + ): [ResetPasswordPolicyOptions, boolean] { + const resetPasswordPolicyOptions = new ResetPasswordPolicyOptions(); + + if (!policies || !orgId) { + return [resetPasswordPolicyOptions, false]; + } + + const policy = policies.find( + (p) => p.organizationId === orgId && p.type === PolicyType.ResetPassword && p.enabled, + ); + resetPasswordPolicyOptions.autoEnrollEnabled = policy?.data?.autoEnrollEnabled ?? false; + + return [resetPasswordPolicyOptions, policy?.enabled ?? false]; + } + + async upsert(policy: PolicyData, userId: UserId): Promise { + await this.policyState(userId).update((policies) => { + policies ??= {}; + policies[policy.id] = policy; + return policies; + }); + } + + async replace(policies: { [id: string]: PolicyData }, userId: UserId): Promise { + await this.stateProvider.setUserState(POLICIES, policies, userId); + } + + /** + * Determines whether an orgUser is exempt from a specific policy because of their role + * Generally orgUsers who can manage policies are exempt from them, but some policies are stricter + */ + private isExemptFromPolicy(policyType: PolicyType, organization: Organization) { + switch (policyType) { + case PolicyType.MaximumVaultTimeout: + // Max Vault Timeout applies to everyone except owners + return organization.isOwner; + case PolicyType.PasswordGenerator: + // password generation policy applies to everyone + return false; + case PolicyType.PersonalOwnership: + // individual vault policy applies to everyone except admins and owners + return organization.isAdmin; + case PolicyType.FreeFamiliesSponsorshipPolicy: + // free Bitwarden families policy applies to everyone + return false; + default: + return organization.canManagePolicies; + } + } +} diff --git a/libs/common/src/admin-console/services/policy/policy.service.spec.ts b/libs/common/src/admin-console/services/policy/policy.service.spec.ts index d9802db9e38..48979f1e31e 100644 --- a/libs/common/src/admin-console/services/policy/policy.service.spec.ts +++ b/libs/common/src/admin-console/services/policy/policy.service.spec.ts @@ -2,8 +2,7 @@ import { mock, MockProxy } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; import { FakeStateProvider, mockAccountServiceWith } from "../../../../spec"; -import { FakeActiveUserState } from "../../../../spec/fake-state"; -import { OrganizationService } from "../../../admin-console/abstractions/organization/organization.service.abstraction"; +import { FakeActiveUserState, FakeSingleUserState } from "../../../../spec/fake-state"; import { OrganizationUserStatusType, OrganizationUserType, @@ -18,12 +17,14 @@ import { Policy } from "../../../admin-console/models/domain/policy"; import { ResetPasswordPolicyOptions } from "../../../admin-console/models/domain/reset-password-policy-options"; import { POLICIES, PolicyService } from "../../../admin-console/services/policy/policy.service"; import { PolicyId, UserId } from "../../../types/guid"; +import { OrganizationService } from "../../abstractions/organization/organization.service.abstraction"; describe("PolicyService", () => { const userId = "userId" as UserId; let stateProvider: FakeStateProvider; let organizationService: MockProxy; let activeUserState: FakeActiveUserState>; + let singleUserState: FakeSingleUserState>; let policyService: PolicyService; @@ -33,6 +34,7 @@ describe("PolicyService", () => { organizationService = mock(); activeUserState = stateProvider.activeUser.getFake(POLICIES); + singleUserState = stateProvider.singleUser.getFake(activeUserState.userId, POLICIES); const organizations$ = of([ // User @@ -56,9 +58,7 @@ describe("PolicyService", () => { organization("org6", true, true, OrganizationUserStatusType.Confirmed, true), ]); - organizationService.organizations$ = organizations$; - - organizationService.getAll$.mockReturnValue(organizations$); + organizationService.organizations$.mockReturnValue(organizations$); policyService = new PolicyService(stateProvider, organizationService); }); @@ -196,7 +196,7 @@ describe("PolicyService", () => { describe("getResetPasswordPolicyOptions", () => { it("default", async () => { - const result = policyService.getResetPasswordPolicyOptions(null, null); + const result = policyService.getResetPasswordPolicyOptions([], ""); expect(result).toEqual([new ResetPasswordPolicyOptions(), false]); }); @@ -220,19 +220,34 @@ describe("PolicyService", () => { arrayToRecord([ policyData("policy1", "org1", PolicyType.ActivateAutofill, true), policyData("policy2", "org1", PolicyType.DisablePersonalVaultExport, true), + policyData("policy3", "org1", PolicyType.RemoveUnlockWithPin, true), ]), ); - const result = await firstValueFrom( - policyService.get$(PolicyType.DisablePersonalVaultExport), - ); - - expect(result).toEqual({ + await expect( + firstValueFrom(policyService.get$(PolicyType.ActivateAutofill)), + ).resolves.toMatchObject({ + id: "policy1", + organizationId: "org1", + type: PolicyType.ActivateAutofill, + enabled: true, + }); + await expect( + firstValueFrom(policyService.get$(PolicyType.DisablePersonalVaultExport)), + ).resolves.toMatchObject({ id: "policy2", organizationId: "org1", type: PolicyType.DisablePersonalVaultExport, enabled: true, }); + await expect( + firstValueFrom(policyService.get$(PolicyType.RemoveUnlockWithPin)), + ).resolves.toMatchObject({ + id: "policy3", + organizationId: "org1", + type: PolicyType.RemoveUnlockWithPin, + enabled: true, + }); }); it("does not return disabled policies", async () => { @@ -297,7 +312,7 @@ describe("PolicyService", () => { describe("getAll$", () => { it("returns the specified PolicyTypes", async () => { - activeUserState.nextState( + singleUserState.nextState( arrayToRecord([ policyData("policy1", "org4", PolicyType.DisablePersonalVaultExport, true), policyData("policy2", "org1", PolicyType.ActivateAutofill, true), @@ -307,7 +322,7 @@ describe("PolicyService", () => { ); const result = await firstValueFrom( - policyService.getAll$(PolicyType.DisablePersonalVaultExport), + policyService.getAll$(PolicyType.DisablePersonalVaultExport, activeUserState.userId), ); expect(result).toEqual([ @@ -333,7 +348,7 @@ describe("PolicyService", () => { }); it("does not return disabled policies", async () => { - activeUserState.nextState( + singleUserState.nextState( arrayToRecord([ policyData("policy1", "org4", PolicyType.DisablePersonalVaultExport, true), policyData("policy2", "org1", PolicyType.ActivateAutofill, true), @@ -343,7 +358,7 @@ describe("PolicyService", () => { ); const result = await firstValueFrom( - policyService.getAll$(PolicyType.DisablePersonalVaultExport), + policyService.getAll$(PolicyType.DisablePersonalVaultExport, activeUserState.userId), ); expect(result).toEqual([ @@ -363,7 +378,7 @@ describe("PolicyService", () => { }); it("does not return policies that do not apply to the user because the user's role is exempt", async () => { - activeUserState.nextState( + singleUserState.nextState( arrayToRecord([ policyData("policy1", "org4", PolicyType.DisablePersonalVaultExport, true), policyData("policy2", "org1", PolicyType.ActivateAutofill, true), @@ -373,7 +388,7 @@ describe("PolicyService", () => { ); const result = await firstValueFrom( - policyService.getAll$(PolicyType.DisablePersonalVaultExport), + policyService.getAll$(PolicyType.DisablePersonalVaultExport, activeUserState.userId), ); expect(result).toEqual([ @@ -393,7 +408,7 @@ describe("PolicyService", () => { }); it("does not return policies for organizations that do not use policies", async () => { - activeUserState.nextState( + singleUserState.nextState( arrayToRecord([ policyData("policy1", "org4", PolicyType.DisablePersonalVaultExport, true), policyData("policy2", "org1", PolicyType.ActivateAutofill, true), @@ -403,7 +418,7 @@ describe("PolicyService", () => { ); const result = await firstValueFrom( - policyService.getAll$(PolicyType.DisablePersonalVaultExport), + policyService.getAll$(PolicyType.DisablePersonalVaultExport, activeUserState.userId), ); expect(result).toEqual([ diff --git a/libs/common/src/admin-console/services/policy/policy.service.ts b/libs/common/src/admin-console/services/policy/policy.service.ts index 7a04ba38aa7..ed4c7970a78 100644 --- a/libs/common/src/admin-console/services/policy/policy.service.ts +++ b/libs/common/src/admin-console/services/policy/policy.service.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { combineLatest, firstValueFrom, map, Observable, of } from "rxjs"; +import { combineLatest, firstValueFrom, map, Observable, of, switchMap } from "rxjs"; import { UserKeyDefinition, POLICIES_DISK, StateProvider } from "../../../platform/state"; import { PolicyId, UserId } from "../../../types/guid"; @@ -39,7 +39,11 @@ export class PolicyService implements InternalPolicyServiceAbstraction { map((policies) => policies.filter((p) => p.type === policyType)), ); - return combineLatest([filteredPolicies$, this.organizationService.organizations$]).pipe( + const organizations$ = this.stateProvider.activeUserId$.pipe( + switchMap((userId) => this.organizationService.organizations$(userId)), + ); + + return combineLatest([filteredPolicies$, organizations$]).pipe( map( ([policies, organizations]) => this.enforcedPolicyFilter(policies, organizations)?.at(0) ?? null, @@ -47,13 +51,13 @@ export class PolicyService implements InternalPolicyServiceAbstraction { ); } - getAll$(policyType: PolicyType, userId?: UserId) { + getAll$(policyType: PolicyType, userId: UserId) { const filteredPolicies$ = this.stateProvider.getUserState$(POLICIES, userId).pipe( map((policyData) => policyRecordToArray(policyData)), map((policies) => policies.filter((p) => p.type === policyType)), ); - return combineLatest([filteredPolicies$, this.organizationService.getAll$(userId)]).pipe( + return combineLatest([filteredPolicies$, this.organizationService.organizations$(userId)]).pipe( map(([policies, organizations]) => this.enforcedPolicyFilter(policies, organizations)), ); } @@ -243,6 +247,9 @@ export class PolicyService implements InternalPolicyServiceAbstraction { case PolicyType.FreeFamiliesSponsorshipPolicy: // free Bitwarden families policy applies to everyone return false; + case PolicyType.RemoveUnlockWithPin: + // free Remove Unlock with PIN policy applies to everyone + return false; default: return organization.canManagePolicies; } diff --git a/libs/common/src/admin-console/services/policy/vnext-policy-state.ts b/libs/common/src/admin-console/services/policy/vnext-policy-state.ts new file mode 100644 index 00000000000..7f53e781ad3 --- /dev/null +++ b/libs/common/src/admin-console/services/policy/vnext-policy-state.ts @@ -0,0 +1,8 @@ +import { POLICIES_DISK, UserKeyDefinition } from "../../../platform/state"; +import { PolicyId } from "../../../types/guid"; +import { PolicyData } from "../../models/data/policy.data"; + +export const POLICIES = UserKeyDefinition.record(POLICIES_DISK, "policies", { + deserializer: (policyData) => policyData, + clearOn: ["logout"], +}); diff --git a/libs/common/src/admin-console/services/provider/provider-api.service.ts b/libs/common/src/admin-console/services/provider/provider-api.service.ts index 2ee921393ff..dc82ec011f4 100644 --- a/libs/common/src/admin-console/services/provider/provider-api.service.ts +++ b/libs/common/src/admin-console/services/provider/provider-api.service.ts @@ -1,3 +1,5 @@ +import { AddableOrganizationResponse } from "@bitwarden/common/admin-console/models/response/addable-organization.response"; + import { ApiService } from "../../../abstractions/api.service"; import { ProviderApiServiceAbstraction } from "../../abstractions/provider/provider-api.service.abstraction"; import { ProviderSetupRequest } from "../../models/request/provider/provider-setup.request"; @@ -44,4 +46,34 @@ export class ProviderApiService implements ProviderApiServiceAbstraction { async deleteProvider(id: string): Promise { await this.apiService.send("DELETE", "/providers/" + id, null, true, false); } + + async getProviderAddableOrganizations( + providerId: string, + ): Promise { + const response = await this.apiService.send( + "GET", + "/providers/" + providerId + "/clients/addable", + null, + true, + true, + ); + + return response.map((data: any) => new AddableOrganizationResponse(data)); + } + + addOrganizationToProvider( + providerId: string, + request: { + key: string; + organizationId: string; + }, + ): Promise { + return this.apiService.send( + "POST", + "/providers/" + providerId + "/clients/existing", + request, + true, + false, + ); + } } diff --git a/libs/common/src/auth/abstractions/account-api.service.ts b/libs/common/src/auth/abstractions/account-api.service.ts index 78fbb2cf882..61fdd4f9d68 100644 --- a/libs/common/src/auth/abstractions/account-api.service.ts +++ b/libs/common/src/auth/abstractions/account-api.service.ts @@ -1,6 +1,7 @@ import { RegisterFinishRequest } from "../models/request/registration/register-finish.request"; import { RegisterSendVerificationEmailRequest } from "../models/request/registration/register-send-verification-email.request"; import { RegisterVerificationEmailClickedRequest } from "../models/request/registration/register-verification-email-clicked.request"; +import { SetVerifyDevicesRequest } from "../models/request/set-verify-devices.request"; import { Verification } from "../types/verification"; export abstract class AccountApiService { @@ -18,7 +19,7 @@ export abstract class AccountApiService { * * @param request - The request object containing * information needed to send the verification email, such as the user's email address. - * @returns A promise that resolves to a string tokencontaining the user's encrypted + * @returns A promise that resolves to a string token containing the user's encrypted * information which must be submitted to complete registration or `null` if * email verification is enabled (users must get the token by clicking a * link in the email that will be sent to them). @@ -33,7 +34,7 @@ export abstract class AccountApiService { * * @param request - The request object containing the email verification token and the * user's email address (which is required to validate the token) - * @returns A promise that resolves when the event is logged on the server succcessfully or a bad + * @returns A promise that resolves when the event is logged on the server successfully or a bad * request if the token is invalid for any reason. */ abstract registerVerificationEmailClicked( @@ -50,4 +51,15 @@ export abstract class AccountApiService { * registration process is successfully completed. */ abstract registerFinish(request: RegisterFinishRequest): Promise; + + /** + * Sets the [dbo].[User].[VerifyDevices] flag to true or false. + * + * @param request - The request object is a SecretVerificationRequest extension + * that also contains the boolean value that the VerifyDevices property is being + * set to. + * @returns A promise that resolves when the process is successfully completed or + * a bad request if secret verification fails. + */ + abstract setVerifyDevices(request: SetVerifyDevicesRequest): Promise; } diff --git a/libs/common/src/auth/abstractions/account.service.ts b/libs/common/src/auth/abstractions/account.service.ts index 094e005e656..1686eefda06 100644 --- a/libs/common/src/auth/abstractions/account.service.ts +++ b/libs/common/src/auth/abstractions/account.service.ts @@ -43,6 +43,8 @@ export abstract class AccountService { * Observable of the last activity time for each account. */ accountActivity$: Observable>; + /** Observable of the new device login verification property for the account. */ + accountVerifyNewDeviceLogin$: Observable; /** Account list in order of descending recency */ sortedUserIds$: Observable; /** Next account that is not the current active account */ @@ -73,6 +75,15 @@ export abstract class AccountService { * @param emailVerified */ abstract setAccountEmailVerified(userId: UserId, emailVerified: boolean): Promise; + /** + * updates the `accounts$` observable with the new VerifyNewDeviceLogin property for the account. + * @param userId + * @param VerifyNewDeviceLogin + */ + abstract setAccountVerifyNewDeviceLogin( + userId: UserId, + verifyNewDeviceLogin: boolean, + ): Promise; /** * Updates the `activeAccount$` observable with the new active account. * @param userId diff --git a/libs/common/src/auth/abstractions/devices/responses/device.response.ts b/libs/common/src/auth/abstractions/devices/responses/device.response.ts index 707616744ad..84a2fb03c28 100644 --- a/libs/common/src/auth/abstractions/devices/responses/device.response.ts +++ b/libs/common/src/auth/abstractions/devices/responses/device.response.ts @@ -1,6 +1,11 @@ import { DeviceType } from "../../../../enums"; import { BaseResponse } from "../../../../models/response/base.response"; +export interface DevicePendingAuthRequest { + id: string; + creationDate: string; +} + export class DeviceResponse extends BaseResponse { id: string; userId: string; @@ -10,7 +15,7 @@ export class DeviceResponse extends BaseResponse { creationDate: string; revisionDate: string; isTrusted: boolean; - devicePendingAuthRequest: { id: string; creationDate: string } | null; + devicePendingAuthRequest: DevicePendingAuthRequest | null; constructor(response: any) { super(response); diff --git a/libs/common/src/auth/abstractions/devices/views/device.view.ts b/libs/common/src/auth/abstractions/devices/views/device.view.ts index 22e522b9eb0..f3b78f216e0 100644 --- a/libs/common/src/auth/abstractions/devices/views/device.view.ts +++ b/libs/common/src/auth/abstractions/devices/views/device.view.ts @@ -1,20 +1,19 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { DeviceType } from "../../../../enums"; import { View } from "../../../../models/view/view"; import { DeviceResponse } from "../responses/device.response"; export class DeviceView implements View { - id: string; - userId: string; - name: string; - identifier: string; - type: DeviceType; - creationDate: string; - revisionDate: string; - response: DeviceResponse; + id: string | undefined; + userId: string | undefined; + name: string | undefined; + identifier: string | undefined; + type: DeviceType | undefined; + creationDate: string | undefined; + revisionDate: string | undefined; + response: DeviceResponse | undefined; constructor(deviceResponse: DeviceResponse) { Object.assign(this, deviceResponse); + this.response = deviceResponse; } } diff --git a/libs/common/src/auth/abstractions/master-password-api.service.abstraction.ts b/libs/common/src/auth/abstractions/master-password-api.service.abstraction.ts new file mode 100644 index 00000000000..442347ca456 --- /dev/null +++ b/libs/common/src/auth/abstractions/master-password-api.service.abstraction.ts @@ -0,0 +1,28 @@ +import { PasswordRequest } from "../models/request/password.request"; +import { SetPasswordRequest } from "../models/request/set-password.request"; +import { UpdateTdeOffboardingPasswordRequest } from "../models/request/update-tde-offboarding-password.request"; +import { UpdateTempPasswordRequest } from "../models/request/update-temp-password.request"; + +export abstract class MasterPasswordApiService { + /** + * POSTs a SetPasswordRequest to "/accounts/set-password" + */ + abstract setPassword: (request: SetPasswordRequest) => Promise; + + /** + * POSTs a PasswordRequest to "/accounts/password" + */ + abstract postPassword: (request: PasswordRequest) => Promise; + + /** + * PUTs an UpdateTempPasswordRequest to "/accounts/update-temp-password" + */ + abstract putUpdateTempPassword: (request: UpdateTempPasswordRequest) => Promise; + + /** + * PUTs an UpdateTdeOffboardingPasswordRequest to "/accounts/update-tde-offboarding-password" + */ + abstract putUpdateTdeOffboardingPassword: ( + request: UpdateTdeOffboardingPasswordRequest, + ) => Promise; +} diff --git a/libs/common/src/auth/abstractions/sso-login.service.abstraction.ts b/libs/common/src/auth/abstractions/sso-login.service.abstraction.ts index 3f3731e0e1b..b30739d94a8 100644 --- a/libs/common/src/auth/abstractions/sso-login.service.abstraction.ts +++ b/libs/common/src/auth/abstractions/sso-login.service.abstraction.ts @@ -1,5 +1,5 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore +import { UserId } from "@bitwarden/common/types/guid"; + export abstract class SsoLoginServiceAbstraction { /** * Gets the code verifier used for SSO. @@ -11,7 +11,7 @@ export abstract class SsoLoginServiceAbstraction { * @see https://datatracker.ietf.org/doc/html/rfc7636 * @returns The code verifier used for SSO. */ - getCodeVerifier: () => Promise; + abstract getCodeVerifier: () => Promise; /** * Sets the code verifier used for SSO. * @@ -21,7 +21,7 @@ export abstract class SsoLoginServiceAbstraction { * and verify it matches the one sent in the request for the `authorization_code`. * @see https://datatracker.ietf.org/doc/html/rfc7636 */ - setCodeVerifier: (codeVerifier: string) => Promise; + abstract setCodeVerifier: (codeVerifier: string) => Promise; /** * Gets the value of the SSO state. * @@ -31,7 +31,7 @@ export abstract class SsoLoginServiceAbstraction { * @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1 * @returns The SSO state. */ - getSsoState: () => Promise; + abstract getSsoState: () => Promise; /** * Sets the value of the SSO state. * @@ -40,7 +40,7 @@ export abstract class SsoLoginServiceAbstraction { * returns the `state` in the callback and the client verifies that the value returned matches the value sent. * @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1 */ - setSsoState: (ssoState: string) => Promise; + abstract setSsoState: (ssoState: string) => Promise; /** * Gets the value of the user's organization sso identifier. * @@ -48,20 +48,20 @@ export abstract class SsoLoginServiceAbstraction { * Do not use this value outside of the SSO login flow. * @returns The user's organization identifier. */ - getOrganizationSsoIdentifier: () => Promise; + abstract getOrganizationSsoIdentifier: () => Promise; /** * Sets the value of the user's organization sso identifier. * * This should only be used during the SSO flow to identify the organization that the user is attempting to log in to. * Do not use this value outside of the SSO login flow. */ - setOrganizationSsoIdentifier: (organizationIdentifier: string) => Promise; + abstract setOrganizationSsoIdentifier: (organizationIdentifier: string) => Promise; /** * Gets the user's email. * Note: This should only be used during the SSO flow to identify the user that is attempting to log in. * @returns The user's email. */ - getSsoEmail: () => Promise; + abstract getSsoEmail: () => Promise; /** * Sets the user's email. * Note: This should only be used during the SSO flow to identify the user that is attempting to log in. @@ -69,17 +69,21 @@ export abstract class SsoLoginServiceAbstraction { * @returns A promise that resolves when the email has been set. * */ - setSsoEmail: (email: string) => Promise; + abstract setSsoEmail: (email: string) => Promise; /** * Gets the value of the active user's organization sso identifier. * * This should only be used post successful SSO login once the user is initialized. + * @param userId The user id for retrieving the org identifier state. */ - getActiveUserOrganizationSsoIdentifier: () => Promise; + abstract getActiveUserOrganizationSsoIdentifier: (userId: UserId) => Promise; /** * Sets the value of the active user's organization sso identifier. * * This should only be used post successful SSO login once the user is initialized. */ - setActiveUserOrganizationSsoIdentifier: (organizationIdentifier: string) => Promise; + abstract setActiveUserOrganizationSsoIdentifier: ( + organizationIdentifier: string, + userId: UserId | undefined, + ) => Promise; } diff --git a/libs/common/src/auth/abstractions/token.service.ts b/libs/common/src/auth/abstractions/token.service.ts index 4695e45e658..0c8db6fdcd1 100644 --- a/libs/common/src/auth/abstractions/token.service.ts +++ b/libs/common/src/auth/abstractions/token.service.ts @@ -2,9 +2,8 @@ // @ts-strict-ignore import { Observable } from "rxjs"; -import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; +import { VaultTimeout, VaultTimeoutAction } from "../../key-management/vault-timeout"; import { UserId } from "../../types/guid"; -import { VaultTimeout } from "../../types/vault-timeout.type"; import { SetTokensResult } from "../models/domain/set-tokens-result"; import { DecodedAccessToken } from "../services/token.service"; diff --git a/libs/common/src/auth/abstractions/two-factor.service.ts b/libs/common/src/auth/abstractions/two-factor.service.ts index 00987dabd98..528e52bf5da 100644 --- a/libs/common/src/auth/abstractions/two-factor.service.ts +++ b/libs/common/src/auth/abstractions/two-factor.service.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { TwoFactorProviderType } from "../enums/two-factor-provider-type"; import { IdentityTwoFactorResponse } from "../models/response/identity-two-factor.response"; @@ -11,15 +9,52 @@ export interface TwoFactorProviderDetails { sort: number; premium: boolean; } - export abstract class TwoFactorService { - init: () => void; - getSupportedProviders: (win: Window) => Promise; - getDefaultProvider: (webAuthnSupported: boolean) => Promise; - setSelectedProvider: (type: TwoFactorProviderType) => Promise; - clearSelectedProvider: () => Promise; + /** + * Initializes the client-side's TwoFactorProviders const with translations. + */ + abstract init(): void; - setProviders: (response: IdentityTwoFactorResponse) => Promise; - clearProviders: () => Promise; - getProviders: () => Promise>; + /** + * Gets a list of two-factor providers from state that are supported on the current client. + * E.g., WebAuthn and Duo are not available on all clients. + * @returns A list of supported two-factor providers or an empty list if none are stored in state. + */ + abstract getSupportedProviders(win: Window): Promise; + + /** + * Gets the previously selected two-factor provider or the default two factor provider based on priority. + * @param webAuthnSupported - Whether or not WebAuthn is supported by the client. Prevents WebAuthn from being the default provider if false. + */ + abstract getDefaultProvider(webAuthnSupported: boolean): Promise; + + /** + * Sets the selected two-factor provider in state. + * @param type - The type of two-factor provider to set as the selected provider. + */ + abstract setSelectedProvider(type: TwoFactorProviderType): Promise; + + /** + * Clears the selected two-factor provider from state. + */ + abstract clearSelectedProvider(): Promise; + + /** + * Sets the list of available two-factor providers in state. + * @param response - the response from Identity for when 2FA is required. Includes the list of available 2FA providers. + */ + abstract setProviders(response: IdentityTwoFactorResponse): Promise; + + /** + * Clears the list of available two-factor providers from state. + */ + abstract clearProviders(): Promise; + + /** + * Gets the list of two-factor providers from state. + * Note: no filtering is done here, so this will return all providers, including potentially + * unsupported ones for the current client. + * @returns A list of two-factor providers or null if none are stored in state. + */ + abstract getProviders(): Promise | null>; } diff --git a/libs/common/src/auth/enums/two-factor-provider-type.ts b/libs/common/src/auth/enums/two-factor-provider-type.ts index a1708032016..b3308b6c12f 100644 --- a/libs/common/src/auth/enums/two-factor-provider-type.ts +++ b/libs/common/src/auth/enums/two-factor-provider-type.ts @@ -7,4 +7,5 @@ export enum TwoFactorProviderType { Remember = 5, OrganizationDuo = 6, WebAuthn = 7, + RecoveryCode = 8, } diff --git a/libs/common/src/auth/models/domain/auth-result.ts b/libs/common/src/auth/models/domain/auth-result.ts index 1c176c2b84b..fdc8c963a1b 100644 --- a/libs/common/src/auth/models/domain/auth-result.ts +++ b/libs/common/src/auth/models/domain/auth-result.ts @@ -22,6 +22,7 @@ export class AuthResult { ssoEmail2FaSessionToken?: string; email: string; requiresEncryptionKeyMigration: boolean; + requiresDeviceVerification: boolean; get requiresCaptcha() { return !Utils.isNullOrWhitespace(this.captchaSiteKey); diff --git a/libs/common/src/auth/models/request/identity-token/password-token.request.ts b/libs/common/src/auth/models/request/identity-token/password-token.request.ts index 456e058a234..3fe466e143b 100644 --- a/libs/common/src/auth/models/request/identity-token/password-token.request.ts +++ b/libs/common/src/auth/models/request/identity-token/password-token.request.ts @@ -13,6 +13,7 @@ export class PasswordTokenRequest extends TokenRequest implements CaptchaProtect public captchaResponse: string, protected twoFactor: TokenTwoFactorRequest, device?: DeviceRequest, + public newDeviceOtp?: string, ) { super(twoFactor, device); } @@ -28,6 +29,10 @@ export class PasswordTokenRequest extends TokenRequest implements CaptchaProtect obj.captchaResponse = this.captchaResponse; } + if (this.newDeviceOtp) { + obj.newDeviceOtp = this.newDeviceOtp; + } + return obj; } diff --git a/libs/common/src/auth/models/request/identity-token/token.request.ts b/libs/common/src/auth/models/request/identity-token/token.request.ts index 390f184c069..497038878d0 100644 --- a/libs/common/src/auth/models/request/identity-token/token.request.ts +++ b/libs/common/src/auth/models/request/identity-token/token.request.ts @@ -14,7 +14,6 @@ export abstract class TokenRequest { this.device = device != null ? device : null; } - // eslint-disable-next-line alterIdentityTokenHeaders(headers: Headers) { // Implemented in subclass if required } diff --git a/libs/common/src/auth/models/request/set-verify-devices.request.ts b/libs/common/src/auth/models/request/set-verify-devices.request.ts new file mode 100644 index 00000000000..4835e9f09cc --- /dev/null +++ b/libs/common/src/auth/models/request/set-verify-devices.request.ts @@ -0,0 +1,8 @@ +import { SecretVerificationRequest } from "./secret-verification.request"; + +export class SetVerifyDevicesRequest extends SecretVerificationRequest { + /** + * This is the input for a user update that controls [dbo].[Users].[VerifyDevices] + */ + verifyDevices!: boolean; +} diff --git a/libs/common/src/auth/models/request/two-factor-recovery.request.ts b/libs/common/src/auth/models/request/two-factor-recovery.request.ts deleted file mode 100644 index 79ef6da280c..00000000000 --- a/libs/common/src/auth/models/request/two-factor-recovery.request.ts +++ /dev/null @@ -1,8 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { SecretVerificationRequest } from "./secret-verification.request"; - -export class TwoFactorRecoveryRequest extends SecretVerificationRequest { - recoveryCode: string; - email: string; -} diff --git a/libs/common/src/auth/models/request/webauthn-rotate-credential.request.ts b/libs/common/src/auth/models/request/webauthn-rotate-credential.request.ts index 791c4688078..84103fe5d29 100644 --- a/libs/common/src/auth/models/request/webauthn-rotate-credential.request.ts +++ b/libs/common/src/auth/models/request/webauthn-rotate-credential.request.ts @@ -1,8 +1,10 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { RotateableKeySet } from "../../../../../auth/src/common/models"; +import { EncString } from "../../../platform/models/domain/enc-string"; export class WebauthnRotateCredentialRequest { id: string; diff --git a/libs/common/src/auth/models/response/auth-request.response.ts b/libs/common/src/auth/models/response/auth-request.response.ts index d0c5d663065..372ae047f4d 100644 --- a/libs/common/src/auth/models/response/auth-request.response.ts +++ b/libs/common/src/auth/models/response/auth-request.response.ts @@ -6,8 +6,11 @@ const RequestTimeOut = 60000 * 15; //15 Minutes export class AuthRequestResponse extends BaseResponse { id: string; publicKey: string; - requestDeviceType: DeviceType; + requestDeviceType: string; + requestDeviceTypeValue: DeviceType; + requestDeviceIdentifier: string; requestIpAddress: string; + requestCountryName: string; key: string; // could be either an encrypted MasterKey or an encrypted UserKey masterPasswordHash: string; // if hash is present, the `key` above is an encrypted MasterKey (else `key` is an encrypted UserKey) creationDate: string; @@ -21,7 +24,10 @@ export class AuthRequestResponse extends BaseResponse { this.id = this.getResponseProperty("Id"); this.publicKey = this.getResponseProperty("PublicKey"); this.requestDeviceType = this.getResponseProperty("RequestDeviceType"); + this.requestDeviceTypeValue = this.getResponseProperty("RequestDeviceTypeValue"); + this.requestDeviceIdentifier = this.getResponseProperty("RequestDeviceIdentifier"); this.requestIpAddress = this.getResponseProperty("RequestIpAddress"); + this.requestCountryName = this.getResponseProperty("RequestCountryName"); this.key = this.getResponseProperty("Key"); this.masterPasswordHash = this.getResponseProperty("MasterPasswordHash"); this.creationDate = this.getResponseProperty("CreationDate"); diff --git a/libs/common/src/auth/models/response/identity-device-verification.response.ts b/libs/common/src/auth/models/response/identity-device-verification.response.ts new file mode 100644 index 00000000000..b45f47e99e1 --- /dev/null +++ b/libs/common/src/auth/models/response/identity-device-verification.response.ts @@ -0,0 +1,13 @@ +import { BaseResponse } from "@bitwarden/common/models/response/base.response"; + +export class IdentityDeviceVerificationResponse extends BaseResponse { + deviceVerified: boolean; + captchaToken: string; + + constructor(response: any) { + super(response); + this.deviceVerified = this.getResponseProperty("DeviceVerified") ?? false; + + this.captchaToken = this.getResponseProperty("CaptchaBypassToken"); + } +} diff --git a/libs/common/src/auth/models/response/identity-response.ts b/libs/common/src/auth/models/response/identity-response.ts new file mode 100644 index 00000000000..26503a9cc2f --- /dev/null +++ b/libs/common/src/auth/models/response/identity-response.ts @@ -0,0 +1,8 @@ +import { IdentityDeviceVerificationResponse } from "@bitwarden/common/auth/models/response/identity-device-verification.response"; +import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; +import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response"; + +export type IdentityResponse = + | IdentityTokenResponse + | IdentityTwoFactorResponse + | IdentityDeviceVerificationResponse; diff --git a/libs/common/src/auth/models/view/login-via-auth-request.view.ts b/libs/common/src/auth/models/view/login-via-auth-request.view.ts new file mode 100644 index 00000000000..0691b8efd86 --- /dev/null +++ b/libs/common/src/auth/models/view/login-via-auth-request.view.ts @@ -0,0 +1,17 @@ +import { Jsonify } from "type-fest"; + +import { AuthRequest } from "@bitwarden/common/auth/models/request/auth.request"; +import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; +import { View } from "@bitwarden/common/models/view/view"; + +export class LoginViaAuthRequestView implements View { + authRequest: AuthRequest | undefined = undefined; + authRequestResponse: AuthRequestResponse | undefined = undefined; + fingerprintPhrase: string | undefined = undefined; + privateKey: string | undefined = undefined; + publicKey: string | undefined = undefined; + + static fromJSON(obj: Partial>): LoginViaAuthRequestView { + return Object.assign(new LoginViaAuthRequestView(), obj); + } +} diff --git a/libs/common/src/auth/services/account-api.service.ts b/libs/common/src/auth/services/account-api.service.ts index e10b0686f61..0347694c465 100644 --- a/libs/common/src/auth/services/account-api.service.ts +++ b/libs/common/src/auth/services/account-api.service.ts @@ -10,6 +10,7 @@ import { UserVerificationService } from "../abstractions/user-verification/user- import { RegisterFinishRequest } from "../models/request/registration/register-finish.request"; import { RegisterSendVerificationEmailRequest } from "../models/request/registration/register-send-verification-email.request"; import { RegisterVerificationEmailClickedRequest } from "../models/request/registration/register-verification-email-clicked.request"; +import { SetVerifyDevicesRequest } from "../models/request/set-verify-devices.request"; import { Verification } from "../types/verification"; export class AccountApiServiceImplementation implements AccountApiService { @@ -102,4 +103,21 @@ export class AccountApiServiceImplementation implements AccountApiService { throw e; } } + + async setVerifyDevices(request: SetVerifyDevicesRequest): Promise { + try { + const response = await this.apiService.send( + "POST", + "/accounts/verify-devices", + request, + true, + true, + ); + + return response; + } catch (e: unknown) { + this.logService.error(e); + throw e; + } + } } diff --git a/libs/common/src/auth/services/account.service.spec.ts b/libs/common/src/auth/services/account.service.spec.ts index 227949156ee..3fc47002083 100644 --- a/libs/common/src/auth/services/account.service.spec.ts +++ b/libs/common/src/auth/services/account.service.spec.ts @@ -7,7 +7,10 @@ import { MockProxy, mock } from "jest-mock-extended"; import { firstValueFrom } from "rxjs"; import { FakeGlobalState } from "../../../spec/fake-state"; -import { FakeGlobalStateProvider } from "../../../spec/fake-state-provider"; +import { + FakeGlobalStateProvider, + FakeSingleUserStateProvider, +} from "../../../spec/fake-state-provider"; import { trackEmissions } from "../../../spec/utils"; import { LogService } from "../../platform/abstractions/log.service"; import { MessagingService } from "../../platform/abstractions/messaging.service"; @@ -19,6 +22,7 @@ import { ACCOUNT_ACCOUNTS, ACCOUNT_ACTIVE_ACCOUNT_ID, ACCOUNT_ACTIVITY, + ACCOUNT_VERIFY_NEW_DEVICE_LOGIN, AccountServiceImplementation, } from "./account.service"; @@ -66,9 +70,11 @@ describe("accountService", () => { let messagingService: MockProxy; let logService: MockProxy; let globalStateProvider: FakeGlobalStateProvider; + let singleUserStateProvider: FakeSingleUserStateProvider; let sut: AccountServiceImplementation; let accountsState: FakeGlobalState>; let activeAccountIdState: FakeGlobalState; + let accountActivityState: FakeGlobalState>; const userId = Utils.newGuid() as UserId; const userInfo = { email: "email", name: "name", emailVerified: true }; @@ -76,11 +82,18 @@ describe("accountService", () => { messagingService = mock(); logService = mock(); globalStateProvider = new FakeGlobalStateProvider(); + singleUserStateProvider = new FakeSingleUserStateProvider(); - sut = new AccountServiceImplementation(messagingService, logService, globalStateProvider); + sut = new AccountServiceImplementation( + messagingService, + logService, + globalStateProvider, + singleUserStateProvider, + ); accountsState = globalStateProvider.getFake(ACCOUNT_ACCOUNTS); activeAccountIdState = globalStateProvider.getFake(ACCOUNT_ACTIVE_ACCOUNT_ID); + accountActivityState = globalStateProvider.getFake(ACCOUNT_ACTIVITY); }); afterEach(() => { @@ -126,6 +139,22 @@ describe("accountService", () => { }); }); + describe("accountsVerifyNewDeviceLogin$", () => { + it("returns expected value", async () => { + // Arrange + const expected = true; + // we need to set this state since it is how we initialize the VerifyNewDeviceLogin$ + activeAccountIdState.stateSubject.next(userId); + singleUserStateProvider.getFake(userId, ACCOUNT_VERIFY_NEW_DEVICE_LOGIN).nextState(expected); + + // Act + const result = await firstValueFrom(sut.accountVerifyNewDeviceLogin$); + + // Assert + expect(result).toEqual(expected); + }); + }); + describe("addAccount", () => { it("should emit the new account", async () => { await sut.addAccount(userId, userInfo); @@ -224,6 +253,33 @@ describe("accountService", () => { }); }); + describe("setAccountVerifyNewDeviceLogin", () => { + const initialState = true; + beforeEach(() => { + activeAccountIdState.stateSubject.next(userId); + singleUserStateProvider + .getFake(userId, ACCOUNT_VERIFY_NEW_DEVICE_LOGIN) + .nextState(initialState); + }); + + it("should update the VerifyNewDeviceLogin", async () => { + const expected = false; + expect(await firstValueFrom(sut.accountVerifyNewDeviceLogin$)).toEqual(initialState); + + await sut.setAccountVerifyNewDeviceLogin(userId, expected); + const currentState = await firstValueFrom(sut.accountVerifyNewDeviceLogin$); + + expect(currentState).toEqual(expected); + }); + + it("should NOT update VerifyNewDeviceLogin when userId is null", async () => { + await sut.setAccountVerifyNewDeviceLogin(null, false); + const currentState = await firstValueFrom(sut.accountVerifyNewDeviceLogin$); + + expect(currentState).toEqual(initialState); + }); + }); + describe("clean", () => { beforeEach(() => { accountsState.stateSubject.next({ [userId]: userInfo }); @@ -256,6 +312,7 @@ describe("accountService", () => { beforeEach(() => { accountsState.stateSubject.next({ [userId]: userInfo }); activeAccountIdState.stateSubject.next(userId); + accountActivityState.stateSubject.next({ [userId]: new Date(1) }); }); it("should emit null if no account is provided", async () => { @@ -269,6 +326,34 @@ describe("accountService", () => { // eslint-disable-next-line @typescript-eslint/no-floating-promises expect(sut.switchAccount("unknown" as UserId)).rejects.toThrowError("Account does not exist"); }); + + it("should change active account when switched to the new account", async () => { + const newUserId = Utils.newGuid() as UserId; + accountsState.stateSubject.next({ [newUserId]: userInfo }); + + await sut.switchAccount(newUserId); + + await expect(firstValueFrom(sut.activeAccount$)).resolves.toEqual({ + id: newUserId, + ...userInfo, + }); + await expect(firstValueFrom(sut.accountActivity$)).resolves.toEqual({ + [userId]: new Date(1), + [newUserId]: expect.toAlmostEqual(new Date(), 1000), + }); + }); + + it("should not change active account when already switched to the same account", async () => { + await sut.switchAccount(userId); + + await expect(firstValueFrom(sut.activeAccount$)).resolves.toEqual({ + id: userId, + ...userInfo, + }); + await expect(firstValueFrom(sut.accountActivity$)).resolves.toEqual({ + [userId]: new Date(1), + }); + }); }); describe("account activity", () => { diff --git a/libs/common/src/auth/services/account.service.ts b/libs/common/src/auth/services/account.service.ts index d4479815c5d..50ba2455d78 100644 --- a/libs/common/src/auth/services/account.service.ts +++ b/libs/common/src/auth/services/account.service.ts @@ -7,6 +7,10 @@ import { shareReplay, combineLatest, Observable, + switchMap, + filter, + timeout, + of, } from "rxjs"; import { @@ -23,6 +27,8 @@ import { GlobalState, GlobalStateProvider, KeyDefinition, + SingleUserStateProvider, + UserKeyDefinition, } from "../../platform/state"; import { UserId } from "../../types/guid"; @@ -42,6 +48,15 @@ export const ACCOUNT_ACTIVITY = KeyDefinition.record(ACCOUNT_DISK, deserializer: (activity) => new Date(activity), }); +export const ACCOUNT_VERIFY_NEW_DEVICE_LOGIN = new UserKeyDefinition( + ACCOUNT_DISK, + "verifyNewDeviceLogin", + { + deserializer: (verifyDevices) => verifyDevices, + clearOn: ["logout"], + }, +); + const LOGGED_OUT_INFO: AccountInfo = { email: "", emailVerified: false, @@ -73,6 +88,7 @@ export class AccountServiceImplementation implements InternalAccountService { accounts$: Observable>; activeAccount$: Observable; accountActivity$: Observable>; + accountVerifyNewDeviceLogin$: Observable; sortedUserIds$: Observable; nextUpAccount$: Observable; @@ -80,6 +96,7 @@ export class AccountServiceImplementation implements InternalAccountService { private messagingService: MessagingService, private logService: LogService, private globalStateProvider: GlobalStateProvider, + private singleUserStateProvider: SingleUserStateProvider, ) { this.accountsState = this.globalStateProvider.get(ACCOUNT_ACCOUNTS); this.activeAccountIdState = this.globalStateProvider.get(ACCOUNT_ACTIVE_ACCOUNT_ID); @@ -114,6 +131,12 @@ export class AccountServiceImplementation implements InternalAccountService { return nextId ? { id: nextId, ...accounts[nextId] } : null; }), ); + this.accountVerifyNewDeviceLogin$ = this.activeAccountIdState.state$.pipe( + switchMap( + (userId) => + this.singleUserStateProvider.get(userId, ACCOUNT_VERIFY_NEW_DEVICE_LOGIN).state$, + ), + ); } async addAccount(userId: UserId, accountData: AccountInfo): Promise { @@ -149,21 +172,28 @@ export class AccountServiceImplementation implements InternalAccountService { async switchAccount(userId: UserId | null): Promise { let updateActivity = false; await this.activeAccountIdState.update( - (_, accounts) => { - if (userId == null) { - // indicates no account is active - return null; - } - - if (accounts?.[userId] == null) { - throw new Error("Account does not exist"); - } + (_, __) => { updateActivity = true; return userId; }, { - combineLatestWith: this.accounts$, - shouldUpdate: (id) => { + combineLatestWith: this.accountsState.state$.pipe( + filter((accounts) => { + if (userId == null) { + // Don't worry about accounts when we are about to set active user to null + return true; + } + + return accounts?.[userId] != null; + }), + // If we don't get the desired account with enough time, just return empty as that will result in the same error + timeout({ first: 1000, with: () => of({} as Record) }), + ), + shouldUpdate: (id, accounts) => { + if (userId != null && accounts?.[userId] == null) { + throw new Error("Account does not exist"); + } + // update only if userId changes return id !== userId; }, @@ -193,6 +223,20 @@ export class AccountServiceImplementation implements InternalAccountService { ); } + async setAccountVerifyNewDeviceLogin( + userId: UserId, + setVerifyNewDeviceLogin: boolean, + ): Promise { + if (!Utils.isGuid(userId)) { + // only store for valid userIds + return; + } + + await this.singleUserStateProvider.get(userId, ACCOUNT_VERIFY_NEW_DEVICE_LOGIN).update(() => { + return setVerifyNewDeviceLogin; + }); + } + async removeAccountActivity(userId: UserId): Promise { await this.globalStateProvider.get(ACCOUNT_ACTIVITY).update( (activity) => { diff --git a/libs/common/src/auth/services/anonymous-hub.service.ts b/libs/common/src/auth/services/anonymous-hub.service.ts index a268c8a2712..3900dd53ee0 100644 --- a/libs/common/src/auth/services/anonymous-hub.service.ts +++ b/libs/common/src/auth/services/anonymous-hub.service.ts @@ -9,6 +9,8 @@ import { import { MessagePackHubProtocol } from "@microsoft/signalr-protocol-msgpack"; import { firstValueFrom } from "rxjs"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { AuthRequestServiceAbstraction } from "../../../../auth/src/common/abstractions"; import { NotificationType } from "../../enums"; import { diff --git a/libs/common/src/auth/services/auth.service.spec.ts b/libs/common/src/auth/services/auth.service.spec.ts index 5663384714d..fc236c91a21 100644 --- a/libs/common/src/auth/services/auth.service.spec.ts +++ b/libs/common/src/auth/services/auth.service.spec.ts @@ -1,7 +1,8 @@ import { MockProxy, mock } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; -import { KeyService } from "../../../../key-management/src/abstractions/key.service"; +import { KeyService } from "@bitwarden/key-management"; + import { FakeAccountService, makeStaticByteArray, diff --git a/libs/common/src/auth/services/auth.service.ts b/libs/common/src/auth/services/auth.service.ts index bab83fc55db..da70baf3999 100644 --- a/libs/common/src/auth/services/auth.service.ts +++ b/libs/common/src/auth/services/auth.service.ts @@ -11,7 +11,8 @@ import { switchMap, } from "rxjs"; -import { KeyService } from "../../../../key-management/src/abstractions/key.service"; +import { KeyService } from "@bitwarden/key-management"; + import { ApiService } from "../../abstractions/api.service"; import { StateService } from "../../platform/abstractions/state.service"; import { MessageSender } from "../../platform/messaging"; diff --git a/libs/common/src/auth/services/devices/devices.service.implementation.ts b/libs/common/src/auth/services/devices/devices.service.implementation.ts index cd6f1148dd8..cdaa7a9fc4e 100644 --- a/libs/common/src/auth/services/devices/devices.service.implementation.ts +++ b/libs/common/src/auth/services/devices/devices.service.implementation.ts @@ -1,8 +1,7 @@ import { Observable, defer, map } from "rxjs"; -import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; - import { ListResponse } from "../../../models/response/list.response"; +import { AppIdService } from "../../../platform/abstractions/app-id.service"; import { DevicesServiceAbstraction } from "../../abstractions/devices/devices.service.abstraction"; import { DeviceResponse } from "../../abstractions/devices/responses/device.response"; import { DeviceView } from "../../abstractions/devices/views/device.view"; diff --git a/libs/common/src/auth/services/master-password/master-password-api.service.implementation.ts b/libs/common/src/auth/services/master-password/master-password-api.service.implementation.ts new file mode 100644 index 00000000000..a91ccab24ef --- /dev/null +++ b/libs/common/src/auth/services/master-password/master-password-api.service.implementation.ts @@ -0,0 +1,85 @@ +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; + +import { MasterPasswordApiService as MasterPasswordApiServiceAbstraction } from "../../abstractions/master-password-api.service.abstraction"; +import { PasswordRequest } from "../../models/request/password.request"; +import { SetPasswordRequest } from "../../models/request/set-password.request"; +import { UpdateTdeOffboardingPasswordRequest } from "../../models/request/update-tde-offboarding-password.request"; +import { UpdateTempPasswordRequest } from "../../models/request/update-temp-password.request"; + +export class MasterPasswordApiService implements MasterPasswordApiServiceAbstraction { + constructor( + private apiService: ApiService, + private logService: LogService, + ) {} + + async setPassword(request: SetPasswordRequest): Promise { + try { + const response = await this.apiService.send( + "POST", + "/accounts/set-password", + request, + true, + false, + ); + + return response; + } catch (e: unknown) { + this.logService.error(e); + throw e; + } + } + + async postPassword(request: PasswordRequest): Promise { + try { + const response = await this.apiService.send( + "POST", + "/accounts/password", + request, + true, + false, + ); + + return response; + } catch (e: unknown) { + this.logService.error(e); + throw e; + } + } + + async putUpdateTempPassword(request: UpdateTempPasswordRequest): Promise { + try { + const response = await this.apiService.send( + "PUT", + "/accounts/update-temp-password", + request, + true, + false, + ); + + return response; + } catch (e: unknown) { + this.logService.error(e); + throw e; + } + } + + async putUpdateTdeOffboardingPassword( + request: UpdateTdeOffboardingPasswordRequest, + ): Promise { + try { + const response = await this.apiService.send( + "PUT", + "/accounts/update-tde-offboarding-password", + request, + true, + false, + ); + + return response; + } catch (e: unknown) { + this.logService.error(e); + throw e; + } + } +} diff --git a/libs/common/src/auth/services/master-password/master-password-api.service.spec.ts b/libs/common/src/auth/services/master-password/master-password-api.service.spec.ts new file mode 100644 index 00000000000..64d4fdf1c7b --- /dev/null +++ b/libs/common/src/auth/services/master-password/master-password-api.service.spec.ts @@ -0,0 +1,130 @@ +import { mock, MockProxy } from "jest-mock-extended"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { KdfType } from "@bitwarden/key-management"; + +import { PasswordRequest } from "../../models/request/password.request"; +import { SetPasswordRequest } from "../../models/request/set-password.request"; +import { UpdateTdeOffboardingPasswordRequest } from "../../models/request/update-tde-offboarding-password.request"; +import { UpdateTempPasswordRequest } from "../../models/request/update-temp-password.request"; + +import { MasterPasswordApiService } from "./master-password-api.service.implementation"; + +describe("MasterPasswordApiService", () => { + let apiService: MockProxy; + let logService: MockProxy; + + let sut: MasterPasswordApiService; + + beforeEach(() => { + apiService = mock(); + logService = mock(); + + sut = new MasterPasswordApiService(apiService, logService); + }); + + it("should instantiate", () => { + expect(sut).not.toBeFalsy(); + }); + + describe("setPassword", () => { + it("should call apiService.send with the correct parameters", async () => { + // Arrange + const request = new SetPasswordRequest( + "masterPasswordHash", + "key", + "masterPasswordHint", + "orgIdentifier", + { + publicKey: "publicKey", + encryptedPrivateKey: "encryptedPrivateKey", + }, + KdfType.PBKDF2_SHA256, + 600_000, + ); + + // Act + await sut.setPassword(request); + + // Assert + expect(apiService.send).toHaveBeenCalledWith( + "POST", + "/accounts/set-password", + request, + true, + false, + ); + }); + }); + + describe("postPassword", () => { + it("should call apiService.send with the correct parameters", async () => { + // Arrange + const request = { + newMasterPasswordHash: "newMasterPasswordHash", + masterPasswordHint: "masterPasswordHint", + key: "key", + masterPasswordHash: "masterPasswordHash", + } as PasswordRequest; + + // Act + await sut.postPassword(request); + + // Assert + expect(apiService.send).toHaveBeenCalledWith( + "POST", + "/accounts/password", + request, + true, + false, + ); + }); + }); + + describe("putUpdateTempPassword", () => { + it("should call apiService.send with the correct parameters", async () => { + // Arrange + const request = { + masterPasswordHint: "masterPasswordHint", + newMasterPasswordHash: "newMasterPasswordHash", + key: "key", + } as UpdateTempPasswordRequest; + + // Act + await sut.putUpdateTempPassword(request); + + // Assert + expect(apiService.send).toHaveBeenCalledWith( + "PUT", + "/accounts/update-temp-password", + request, + true, + false, + ); + }); + }); + + describe("putUpdateTdeOffboardingPassword", () => { + it("should call apiService.send with the correct parameters", async () => { + // Arrange + const request = { + masterPasswordHint: "masterPasswordHint", + newMasterPasswordHash: "newMasterPasswordHash", + key: "key", + } as UpdateTdeOffboardingPasswordRequest; + + // Act + await sut.putUpdateTdeOffboardingPassword(request); + + // Assert + expect(apiService.send).toHaveBeenCalledWith( + "PUT", + "/accounts/update-tde-offboarding-password", + request, + true, + false, + ); + }); + }); +}); diff --git a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts index ed622b21c86..8516400fe09 100644 --- a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts +++ b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts @@ -2,13 +2,13 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { KeyService } from "@bitwarden/key-management"; -import { UserId } from "../../../../common/src/types/guid"; -import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { OrganizationApiServiceAbstraction } from "../../admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationAutoEnrollStatusResponse } from "../../admin-console/models/response/organization-auto-enroll-status.response"; +import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service"; import { I18nService } from "../../platform/abstractions/i18n.service"; +import { UserId } from "../../types/guid"; import { Account, AccountInfo, AccountService } from "../abstractions/account.service"; import { PasswordResetEnrollmentServiceImplementation } from "./password-reset-enrollment.service.implementation"; diff --git a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts index c5451ce30c6..824521d8a2e 100644 --- a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts +++ b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts @@ -6,10 +6,10 @@ import { OrganizationUserApiService, OrganizationUserResetPasswordEnrollmentRequest, } from "@bitwarden/admin-console/common"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { KeyService } from "@bitwarden/key-management"; -import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { OrganizationApiServiceAbstraction } from "../../admin-console/abstractions/organization/organization-api.service.abstraction"; +import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service"; import { I18nService } from "../../platform/abstractions/i18n.service"; import { Utils } from "../../platform/misc/utils"; import { UserKey } from "../../types/key"; diff --git a/libs/common/src/auth/services/sso-login.service.spec.ts b/libs/common/src/auth/services/sso-login.service.spec.ts new file mode 100644 index 00000000000..6764755e6ca --- /dev/null +++ b/libs/common/src/auth/services/sso-login.service.spec.ts @@ -0,0 +1,94 @@ +import { mock, MockProxy } from "jest-mock-extended"; + +import { + CODE_VERIFIER, + GLOBAL_ORGANIZATION_SSO_IDENTIFIER, + SSO_EMAIL, + SSO_STATE, + SsoLoginService, + USER_ORGANIZATION_SSO_IDENTIFIER, +} from "@bitwarden/common/auth/services/sso-login.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { UserId } from "@bitwarden/common/types/guid"; + +import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from "../../../spec"; + +describe("SSOLoginService ", () => { + let sut: SsoLoginService; + + let accountService: FakeAccountService; + let mockSingleUserStateProvider: FakeStateProvider; + let mockLogService: MockProxy; + let userId: UserId; + + beforeEach(() => { + jest.clearAllMocks(); + + userId = Utils.newGuid() as UserId; + accountService = mockAccountServiceWith(userId); + mockSingleUserStateProvider = new FakeStateProvider(accountService); + mockLogService = mock(); + + sut = new SsoLoginService(mockSingleUserStateProvider, mockLogService); + }); + + it("instantiates", () => { + expect(sut).not.toBeFalsy(); + }); + + it("gets and sets code verifier", async () => { + const codeVerifier = "test-code-verifier"; + await sut.setCodeVerifier(codeVerifier); + mockSingleUserStateProvider.getGlobal(CODE_VERIFIER); + + const result = await sut.getCodeVerifier(); + expect(result).toBe(codeVerifier); + }); + + it("gets and sets SSO state", async () => { + const ssoState = "test-sso-state"; + await sut.setSsoState(ssoState); + mockSingleUserStateProvider.getGlobal(SSO_STATE); + + const result = await sut.getSsoState(); + expect(result).toBe(ssoState); + }); + + it("gets and sets organization SSO identifier", async () => { + const orgIdentifier = "test-org-identifier"; + await sut.setOrganizationSsoIdentifier(orgIdentifier); + mockSingleUserStateProvider.getGlobal(GLOBAL_ORGANIZATION_SSO_IDENTIFIER); + + const result = await sut.getOrganizationSsoIdentifier(); + expect(result).toBe(orgIdentifier); + }); + + it("gets and sets SSO email", async () => { + const email = "test@example.com"; + await sut.setSsoEmail(email); + mockSingleUserStateProvider.getGlobal(SSO_EMAIL); + + const result = await sut.getSsoEmail(); + expect(result).toBe(email); + }); + + it("gets and sets active user organization SSO identifier", async () => { + const userId = Utils.newGuid() as UserId; + const orgIdentifier = "test-active-org-identifier"; + await sut.setActiveUserOrganizationSsoIdentifier(orgIdentifier, userId); + mockSingleUserStateProvider.getUser(userId, USER_ORGANIZATION_SSO_IDENTIFIER); + + const result = await sut.getActiveUserOrganizationSsoIdentifier(userId); + expect(result).toBe(orgIdentifier); + }); + + it("logs error when setting active user organization SSO identifier with undefined userId", async () => { + const orgIdentifier = "test-active-org-identifier"; + await sut.setActiveUserOrganizationSsoIdentifier(orgIdentifier, undefined); + + expect(mockLogService.error).toHaveBeenCalledWith( + "Tried to set a user organization sso identifier with an undefined user id.", + ); + }); +}); diff --git a/libs/common/src/auth/services/sso-login.service.ts b/libs/common/src/auth/services/sso-login.service.ts index 32019e8d568..9b4b8656782 100644 --- a/libs/common/src/auth/services/sso-login.service.ts +++ b/libs/common/src/auth/services/sso-login.service.ts @@ -1,11 +1,12 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { firstValueFrom } from "rxjs"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { UserId } from "@bitwarden/common/types/guid"; + import { - ActiveUserState, GlobalState, KeyDefinition, + SingleUserState, SSO_DISK, StateProvider, UserKeyDefinition, @@ -15,21 +16,21 @@ import { SsoLoginServiceAbstraction } from "../abstractions/sso-login.service.ab /** * Uses disk storage so that the code verifier can be persisted across sso redirects. */ -const CODE_VERIFIER = new KeyDefinition(SSO_DISK, "ssoCodeVerifier", { +export const CODE_VERIFIER = new KeyDefinition(SSO_DISK, "ssoCodeVerifier", { deserializer: (codeVerifier) => codeVerifier, }); /** * Uses disk storage so that the sso state can be persisted across sso redirects. */ -const SSO_STATE = new KeyDefinition(SSO_DISK, "ssoState", { +export const SSO_STATE = new KeyDefinition(SSO_DISK, "ssoState", { deserializer: (state) => state, }); /** * Uses disk storage so that the organization sso identifier can be persisted across sso redirects. */ -const USER_ORGANIZATION_SSO_IDENTIFIER = new UserKeyDefinition( +export const USER_ORGANIZATION_SSO_IDENTIFIER = new UserKeyDefinition( SSO_DISK, "organizationSsoIdentifier", { @@ -41,7 +42,7 @@ const USER_ORGANIZATION_SSO_IDENTIFIER = new UserKeyDefinition( /** * Uses disk storage so that the organization sso identifier can be persisted across sso redirects. */ -const GLOBAL_ORGANIZATION_SSO_IDENTIFIER = new KeyDefinition( +export const GLOBAL_ORGANIZATION_SSO_IDENTIFIER = new KeyDefinition( SSO_DISK, "organizationSsoIdentifier", { @@ -52,7 +53,7 @@ const GLOBAL_ORGANIZATION_SSO_IDENTIFIER = new KeyDefinition( /** * Uses disk storage so that the user's email can be persisted across sso redirects. */ -const SSO_EMAIL = new KeyDefinition(SSO_DISK, "ssoEmail", { +export const SSO_EMAIL = new KeyDefinition(SSO_DISK, "ssoEmail", { deserializer: (state) => state, }); @@ -61,19 +62,18 @@ export class SsoLoginService implements SsoLoginServiceAbstraction { private ssoState: GlobalState; private orgSsoIdentifierState: GlobalState; private ssoEmailState: GlobalState; - private activeUserOrgSsoIdentifierState: ActiveUserState; - constructor(private stateProvider: StateProvider) { + constructor( + private stateProvider: StateProvider, + private logService: LogService, + ) { this.codeVerifierState = this.stateProvider.getGlobal(CODE_VERIFIER); this.ssoState = this.stateProvider.getGlobal(SSO_STATE); this.orgSsoIdentifierState = this.stateProvider.getGlobal(GLOBAL_ORGANIZATION_SSO_IDENTIFIER); this.ssoEmailState = this.stateProvider.getGlobal(SSO_EMAIL); - this.activeUserOrgSsoIdentifierState = this.stateProvider.getActive( - USER_ORGANIZATION_SSO_IDENTIFIER, - ); } - getCodeVerifier(): Promise { + getCodeVerifier(): Promise { return firstValueFrom(this.codeVerifierState.state$); } @@ -81,7 +81,7 @@ export class SsoLoginService implements SsoLoginServiceAbstraction { await this.codeVerifierState.update((_) => codeVerifier); } - getSsoState(): Promise { + getSsoState(): Promise { return firstValueFrom(this.ssoState.state$); } @@ -89,7 +89,7 @@ export class SsoLoginService implements SsoLoginServiceAbstraction { await this.ssoState.update((_) => ssoState); } - getOrganizationSsoIdentifier(): Promise { + getOrganizationSsoIdentifier(): Promise { return firstValueFrom(this.orgSsoIdentifierState.state$); } @@ -97,7 +97,7 @@ export class SsoLoginService implements SsoLoginServiceAbstraction { await this.orgSsoIdentifierState.update((_) => organizationIdentifier); } - getSsoEmail(): Promise { + getSsoEmail(): Promise { return firstValueFrom(this.ssoEmailState.state$); } @@ -105,11 +105,24 @@ export class SsoLoginService implements SsoLoginServiceAbstraction { await this.ssoEmailState.update((_) => email); } - getActiveUserOrganizationSsoIdentifier(): Promise { - return firstValueFrom(this.activeUserOrgSsoIdentifierState.state$); + getActiveUserOrganizationSsoIdentifier(userId: UserId): Promise { + return firstValueFrom(this.userOrgSsoIdentifierState(userId).state$); } - async setActiveUserOrganizationSsoIdentifier(organizationIdentifier: string): Promise { - await this.activeUserOrgSsoIdentifierState.update((_) => organizationIdentifier); + async setActiveUserOrganizationSsoIdentifier( + organizationIdentifier: string, + userId: UserId | undefined, + ): Promise { + if (userId === undefined) { + this.logService.error( + "Tried to set a user organization sso identifier with an undefined user id.", + ); + return; + } + await this.userOrgSsoIdentifierState(userId).update((_) => organizationIdentifier); + } + + private userOrgSsoIdentifierState(userId: UserId): SingleUserState { + return this.stateProvider.getUser(userId, USER_ORGANIZATION_SSO_IDENTIFIER); } } diff --git a/libs/common/src/auth/services/token.service.spec.ts b/libs/common/src/auth/services/token.service.spec.ts index f8882e1b118..449f5d17ad1 100644 --- a/libs/common/src/auth/services/token.service.spec.ts +++ b/libs/common/src/auth/services/token.service.spec.ts @@ -1,11 +1,17 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { MockProxy, mock } from "jest-mock-extended"; import { firstValueFrom } from "rxjs"; import { LogoutReason } from "@bitwarden/auth/common"; import { FakeSingleUserStateProvider, FakeGlobalStateProvider } from "../../../spec"; -import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; -import { EncryptService } from "../../platform/abstractions/encrypt.service"; +import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service"; +import { + VaultTimeout, + VaultTimeoutAction, + VaultTimeoutStringType, +} from "../../key-management/vault-timeout"; import { KeyGenerationService } from "../../platform/abstractions/key-generation.service"; import { LogService } from "../../platform/abstractions/log.service"; import { AbstractStorageService } from "../../platform/abstractions/storage.service"; @@ -14,7 +20,6 @@ import { StorageOptions } from "../../platform/models/domain/storage-options"; import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key"; import { CsprngArray } from "../../types/csprng"; import { UserId } from "../../types/guid"; -import { VaultTimeout, VaultTimeoutStringType } from "../../types/vault-timeout.type"; import { SetTokensResult } from "../models/domain/set-tokens-result"; import { ACCOUNT_ACTIVE_ACCOUNT_ID } from "./account.service"; diff --git a/libs/common/src/auth/services/token.service.ts b/libs/common/src/auth/services/token.service.ts index 4b7cc2cab01..7b87e55cfc7 100644 --- a/libs/common/src/auth/services/token.service.ts +++ b/libs/common/src/auth/services/token.service.ts @@ -5,8 +5,12 @@ import { Opaque } from "type-fest"; import { LogoutReason, decodeJwtTokenToJson } from "@bitwarden/auth/common"; -import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; -import { EncryptService } from "../../platform/abstractions/encrypt.service"; +import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service"; +import { + VaultTimeout, + VaultTimeoutAction, + VaultTimeoutStringType, +} from "../../key-management/vault-timeout"; import { KeyGenerationService } from "../../platform/abstractions/key-generation.service"; import { LogService } from "../../platform/abstractions/log.service"; import { AbstractStorageService } from "../../platform/abstractions/storage.service"; @@ -22,7 +26,6 @@ import { UserKeyDefinition, } from "../../platform/state"; import { UserId } from "../../types/guid"; -import { VaultTimeout, VaultTimeoutStringType } from "../../types/vault-timeout.type"; import { TokenService as TokenServiceAbstraction } from "../abstractions/token.service"; import { SetTokensResult } from "../models/domain/set-tokens-result"; diff --git a/libs/common/src/auth/services/two-factor.service.ts b/libs/common/src/auth/services/two-factor.service.ts index 3826ffaaa22..83e113268a2 100644 --- a/libs/common/src/auth/services/two-factor.service.ts +++ b/libs/common/src/auth/services/two-factor.service.ts @@ -206,7 +206,7 @@ export class TwoFactorService implements TwoFactorServiceAbstraction { await this.providersState.update(() => null); } - getProviders(): Promise> { + getProviders(): Promise | null> { return firstValueFrom(this.providers$); } } diff --git a/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts b/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts index 081dafb1706..d56dd6dda3b 100644 --- a/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts +++ b/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts @@ -12,17 +12,17 @@ import { BiometricsStatus, KdfConfig, KeyService, + KdfConfigService, } from "@bitwarden/key-management"; -import { KdfConfigService } from "../../../../../key-management/src/abstractions/kdf-config.service"; import { FakeAccountService, mockAccountServiceWith } from "../../../../spec"; -import { VaultTimeoutSettingsService } from "../../../abstractions/vault-timeout/vault-timeout-settings.service"; +import { InternalMasterPasswordServiceAbstraction } from "../../../key-management/master-password/abstractions/master-password.service.abstraction"; +import { VaultTimeoutSettingsService } from "../../../key-management/vault-timeout"; import { I18nService } from "../../../platform/abstractions/i18n.service"; import { HashPurpose } from "../../../platform/enums"; import { Utils } from "../../../platform/misc/utils"; import { UserId } from "../../../types/guid"; import { MasterKey } from "../../../types/key"; -import { InternalMasterPasswordServiceAbstraction } from "../../abstractions/master-password.service.abstraction"; import { UserVerificationApiServiceAbstraction } from "../../abstractions/user-verification/user-verification-api.service.abstraction"; import { VerificationType } from "../../enums/verification-type"; import { MasterPasswordPolicyResponse } from "../../models/response/master-password-policy.response"; @@ -224,6 +224,8 @@ describe("UserVerificationService", () => { expect(result).toEqual({ policyOptions: null, masterKey: "masterKey", + kdfConfig: "kdfConfig", + email: "email", }); }); @@ -282,6 +284,8 @@ describe("UserVerificationService", () => { expect(result).toEqual({ policyOptions: "MasterPasswordPolicyOptions", masterKey: "masterKey", + kdfConfig: "kdfConfig", + email: "email", }); }); diff --git a/libs/common/src/auth/services/user-verification/user-verification.service.ts b/libs/common/src/auth/services/user-verification/user-verification.service.ts index 2935c1958a4..1ff629114ab 100644 --- a/libs/common/src/auth/services/user-verification/user-verification.service.ts +++ b/libs/common/src/auth/services/user-verification/user-verification.service.ts @@ -10,12 +10,14 @@ import { KeyService, } from "@bitwarden/key-management"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { PinServiceAbstraction } from "../../../../../auth/src/common/abstractions/pin.service.abstraction"; +import { InternalMasterPasswordServiceAbstraction } from "../../../key-management/master-password/abstractions/master-password.service.abstraction"; import { I18nService } from "../../../platform/abstractions/i18n.service"; import { HashPurpose } from "../../../platform/enums"; import { UserId } from "../../../types/guid"; import { AccountService } from "../../abstractions/account.service"; -import { InternalMasterPasswordServiceAbstraction } from "../../abstractions/master-password.service.abstraction"; import { UserVerificationApiServiceAbstraction } from "../../abstractions/user-verification/user-verification-api.service.abstraction"; import { UserVerificationService as UserVerificationServiceAbstraction } from "../../abstractions/user-verification/user-verification.service.abstraction"; import { VerificationType } from "../../enums/verification-type"; @@ -161,6 +163,8 @@ export class UserVerificationService implements UserVerificationServiceAbstracti const request = new VerifyOTPRequest(verification.secret); try { await this.userVerificationApiService.postAccountVerifyOTP(request); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { throw new Error(this.i18nService.t("invalidVerificationCode")); } @@ -219,6 +223,8 @@ export class UserVerificationService implements UserVerificationServiceAbstracti request.masterPasswordHash = serverKeyHash; try { policyOptions = await this.userVerificationApiService.postAccountVerifyPassword(request); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { throw new Error(this.i18nService.t("invalidMasterPassword")); } @@ -231,7 +237,7 @@ export class UserVerificationService implements UserVerificationServiceAbstracti ); await this.masterPasswordService.setMasterKeyHash(localKeyHash, userId); await this.masterPasswordService.setMasterKey(masterKey, userId); - return { policyOptions, masterKey }; + return { policyOptions, masterKey, kdfConfig, email }; } private async verifyUserByPIN(verification: PinVerification, userId: UserId): Promise { diff --git a/libs/common/src/auth/types/verification.ts b/libs/common/src/auth/types/verification.ts index 2dddd5fb914..307a584fb36 100644 --- a/libs/common/src/auth/types/verification.ts +++ b/libs/common/src/auth/types/verification.ts @@ -1,3 +1,5 @@ +import { KdfConfig } from "@bitwarden/key-management"; + import { MasterKey } from "../../types/key"; import { VerificationType } from "../enums/verification-type"; import { MasterPasswordPolicyResponse } from "../models/response/master-password-policy.response"; @@ -22,5 +24,7 @@ export type ServerSideVerification = OtpVerification | MasterPasswordVerificatio export type MasterPasswordVerificationResponse = { masterKey: MasterKey; - policyOptions: MasterPasswordPolicyResponse; + kdfConfig: KdfConfig; + email: string; + policyOptions: MasterPasswordPolicyResponse | null; }; diff --git a/libs/common/src/auth/webauthn-iframe.ts b/libs/common/src/auth/webauthn-iframe.ts index 1e360a53507..4bc278b9a65 100644 --- a/libs/common/src/auth/webauthn-iframe.ts +++ b/libs/common/src/auth/webauthn-iframe.ts @@ -25,7 +25,10 @@ export class WebAuthnIFrame { const params = new URLSearchParams({ data: this.base64Encode(JSON.stringify(data)), parent: encodeURIComponent(this.win.document.location.href), - btnText: encodeURIComponent(this.i18nService.t("webAuthnAuthenticate")), + btnText: encodeURIComponent(this.i18nService.t("readSecurityKey")), + btnAwaitingInteractionText: encodeURIComponent( + this.i18nService.t("awaitingSecurityKeyInteraction"), + ), v: "1", }); diff --git a/libs/common/src/autofill/services/domain-settings.service.spec.ts b/libs/common/src/autofill/services/domain-settings.service.spec.ts index a25653f168c..36f7d0eacec 100644 --- a/libs/common/src/autofill/services/domain-settings.service.spec.ts +++ b/libs/common/src/autofill/services/domain-settings.service.spec.ts @@ -1,9 +1,8 @@ import { MockProxy, mock } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; - import { FakeStateProvider, FakeAccountService, mockAccountServiceWith } from "../../../spec"; +import { ConfigService } from "../../platform/abstractions/config/config.service"; import { Utils } from "../../platform/misc/utils"; import { UserId } from "../../types/guid"; @@ -29,7 +28,7 @@ describe("DefaultDomainSettingsService", () => { jest.spyOn(domainSettingsService, "getUrlEquivalentDomains"); domainSettingsService.equivalentDomains$ = of(mockEquivalentDomains); - domainSettingsService.blockedInteractionsUris$ = of(null); + domainSettingsService.blockedInteractionsUris$ = of({}); }); describe("getUrlEquivalentDomains", () => { diff --git a/libs/common/src/autofill/services/domain-settings.service.ts b/libs/common/src/autofill/services/domain-settings.service.ts index aeb3af69dae..b2833b9ee25 100644 --- a/libs/common/src/autofill/services/domain-settings.service.ts +++ b/libs/common/src/autofill/services/domain-settings.service.ts @@ -2,8 +2,7 @@ // @ts-strict-ignore import { map, Observable, switchMap, of } from "rxjs"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; - +import { FeatureFlag } from "../../enums/feature-flag.enum"; import { NeverDomains, EquivalentDomains, @@ -36,7 +35,7 @@ const BLOCKED_INTERACTIONS_URIS = new KeyDefinition( DOMAIN_SETTINGS_DISK, "blockedInteractionsUris", { - deserializer: (value: NeverDomains) => value ?? null, + deserializer: (value: NeverDomains) => value ?? {}, }, ); @@ -131,7 +130,7 @@ export class DefaultDomainSettingsService implements DomainSettingsService { switchMap((featureIsEnabled) => featureIsEnabled ? this.blockedInteractionsUrisState.state$ : of({} as NeverDomains), ), - map((disabledUris) => (Object.keys(disabledUris).length ? disabledUris : null)), + map((disabledUris) => (Object.keys(disabledUris).length ? disabledUris : {})), ); this.equivalentDomainsState = this.stateProvider.getActive(EQUIVALENT_DOMAINS); diff --git a/libs/common/src/autofill/utils.spec.ts b/libs/common/src/autofill/utils.spec.ts index 4dd36ba7d89..93f515aa338 100644 --- a/libs/common/src/autofill/utils.spec.ts +++ b/libs/common/src/autofill/utils.spec.ts @@ -1,9 +1,13 @@ +import { NeverDomains } from "@bitwarden/common/models/domain/domain-service"; + +import { CardView } from "../vault/models/view/card.view"; + import { - normalizeExpiryYearFormat, isCardExpired, + isUrlInList, + normalizeExpiryYearFormat, parseYearMonthExpiry, -} from "@bitwarden/common/autofill/utils"; -import { CardView } from "@bitwarden/common/vault/models/view/card.view"; +} from "./utils"; function getExpiryYearValueFormats(currentCentury: string) { return [ @@ -284,3 +288,73 @@ describe("parseYearMonthExpiry", () => { }); }); }); + +describe("isUrlInList", () => { + let mockUrlList: NeverDomains; + + it("returns false if the passed URL list is empty", () => { + const urlIsInList = isUrlInList("", mockUrlList); + + expect(urlIsInList).toEqual(false); + }); + + it("returns true if the URL hostname is on the passed URL list", () => { + mockUrlList = { + ["bitwarden.com"]: { bannerIsDismissed: true }, + ["duckduckgo.com"]: null, + [".lan"]: null, + [".net"]: null, + ["localhost"]: null, + ["extensions"]: null, + }; + + const testPages = [ + "https://www.bitwarden.com/landing-page?some_query_string_key=1&another_one=1", + " https://duckduckgo.com/pro ", // Note: embedded whitespacing is intentional + "https://network-private-domain.lan/homelabs-dashboard", + "https://jsfiddle.net/", + "https://localhost:8443/#/login", + "chrome://extensions/", + ]; + + for (const pageUrl of testPages) { + const urlIsInList = isUrlInList(pageUrl, mockUrlList); + + expect(urlIsInList).toEqual(true); + } + }); + + it("returns false if no items on the passed URL list are a full match for the page hostname", () => { + const urlIsInList = isUrlInList("https://paypal.com/", { + ["some.packed.subdomains.sandbox.paypal.com"]: null, + }); + + expect(urlIsInList).toEqual(false); + }); + + it("returns false if the URL hostname is not on the passed URL list", () => { + const testPages = ["https://archive.org/", "bitwarden.com.some.otherdomain.com"]; + + for (const pageUrl of testPages) { + const urlIsInList = isUrlInList(pageUrl, mockUrlList); + + expect(urlIsInList).toEqual(false); + } + }); + + it("returns false if the passed URL is empty", () => { + const urlIsInList = isUrlInList("", mockUrlList); + + expect(urlIsInList).toEqual(false); + }); + + it("returns false if the passed URL is not a valid URL", () => { + const testPages = ["twasbrillingandtheslithytoves", "/landing-page", undefined]; + + for (const pageUrl of testPages) { + const urlIsInList = isUrlInList(pageUrl, mockUrlList); + + expect(urlIsInList).toEqual(false); + } + }); +}); diff --git a/libs/common/src/autofill/utils.ts b/libs/common/src/autofill/utils.ts index 6bee5e1a198..3078e15bce2 100644 --- a/libs/common/src/autofill/utils.ts +++ b/libs/common/src/autofill/utils.ts @@ -1,11 +1,15 @@ +import { NeverDomains } from "@bitwarden/common/models/domain/domain-service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; + +import { CardView } from "../vault/models/view/card.view"; + import { DelimiterPatternExpression, ExpiryFullYearPattern, ExpiryFullYearPatternExpression, IrrelevantExpiryCharactersPatternExpression, MonthPatternExpression, -} from "@bitwarden/common/autofill/constants"; -import { CardView } from "@bitwarden/common/vault/models/view/card.view"; +} from "./constants"; type NonZeroIntegers = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; type Year = `${NonZeroIntegers}${NonZeroIntegers}${0 | NonZeroIntegers}${0 | NonZeroIntegers}`; @@ -328,3 +332,29 @@ export function parseYearMonthExpiry(combinedExpiryValue: string): [Year | null, return [parsedYear, parsedMonth]; } + +/** + * Takes a URL string and a NeverDomains object and determines if the passed URL's hostname is in `urlList` + * + * @param {string} url - representation of URL to check + * @param {NeverDomains} urlList - object with hostname key names + */ +export function isUrlInList(url: string = "", urlList: NeverDomains = {}): boolean { + const urlListKeys = urlList && Object.keys(urlList); + + if (urlListKeys.length && url?.length) { + let tabHostname; + try { + tabHostname = Utils.getHostname(url); + } catch { + // If the input was invalid, exit early and return false + return false; + } + + if (tabHostname) { + return urlListKeys.some((blockedHostname) => tabHostname.endsWith(blockedHostname)); + } + } + + return false; +} diff --git a/libs/common/src/billing/abstractions/account/account-billing-api.service.abstraction.ts b/libs/common/src/billing/abstractions/account/account-billing-api.service.abstraction.ts index 06400081e11..e0e8b7377c5 100644 --- a/libs/common/src/billing/abstractions/account/account-billing-api.service.abstraction.ts +++ b/libs/common/src/billing/abstractions/account/account-billing-api.service.abstraction.ts @@ -3,7 +3,7 @@ import { BillingInvoiceResponse, BillingTransactionResponse, -} from "@bitwarden/common/billing/models/response/billing.response"; +} from "../../models/response/billing.response"; export class AccountBillingApiServiceAbstraction { getBillingInvoices: (status?: string, startAfter?: string) => Promise; diff --git a/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts b/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts index 4b08b52a136..21089933a59 100644 --- a/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts +++ b/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts @@ -1,20 +1,22 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; -import { PaymentMethodType } from "@bitwarden/common/billing/enums"; -import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; -import { UpdatePaymentMethodRequest } from "@bitwarden/common/billing/models/request/update-payment-method.request"; -import { VerifyBankAccountRequest } from "@bitwarden/common/billing/models/request/verify-bank-account.request"; -import { InvoicesResponse } from "@bitwarden/common/billing/models/response/invoices.response"; -import { PaymentMethodResponse } from "@bitwarden/common/billing/models/response/payment-method.response"; + +import { TaxInfoResponse } from "@bitwarden/common/billing/models/response/tax-info.response"; import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request"; +import { ProviderOrganizationOrganizationDetailsResponse } from "../../admin-console/models/response/provider/provider-organization.response"; import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request"; import { OrganizationBillingMetadataResponse } from "../../billing/models/response/organization-billing-metadata.response"; import { PlanResponse } from "../../billing/models/response/plan.response"; import { ListResponse } from "../../models/response/list.response"; +import { PaymentMethodType } from "../enums"; import { CreateClientOrganizationRequest } from "../models/request/create-client-organization.request"; +import { ExpandedTaxInfoUpdateRequest } from "../models/request/expanded-tax-info-update.request"; import { UpdateClientOrganizationRequest } from "../models/request/update-client-organization.request"; +import { UpdatePaymentMethodRequest } from "../models/request/update-payment-method.request"; +import { VerifyBankAccountRequest } from "../models/request/verify-bank-account.request"; +import { InvoicesResponse } from "../models/response/invoices.response"; +import { PaymentMethodResponse } from "../models/response/payment-method.response"; import { ProviderSubscriptionResponse } from "../models/response/provider-subscription-response"; export abstract class BillingApiServiceAbstraction { @@ -50,6 +52,8 @@ export abstract class BillingApiServiceAbstraction { getProviderSubscription: (providerId: string) => Promise; + getProviderTaxInformation: (providerId: string) => Promise; + updateOrganizationPaymentMethod: ( organizationId: string, request: UpdatePaymentMethodRequest, @@ -66,6 +70,11 @@ export abstract class BillingApiServiceAbstraction { request: UpdateClientOrganizationRequest, ) => Promise; + updateProviderPaymentMethod: ( + providerId: string, + request: UpdatePaymentMethodRequest, + ) => Promise; + updateProviderTaxInformation: ( providerId: string, request: ExpandedTaxInfoUpdateRequest, @@ -76,6 +85,11 @@ export abstract class BillingApiServiceAbstraction { request: VerifyBankAccountRequest, ) => Promise; + verifyProviderBankAccount: ( + providerId: string, + request: VerifyBankAccountRequest, + ) => Promise; + restartSubscription: ( organizationId: string, request: OrganizationCreateRequest, diff --git a/libs/common/src/billing/abstractions/organization-billing.service.ts b/libs/common/src/billing/abstractions/organization-billing.service.ts index 7c4e0a39f8f..69309014fac 100644 --- a/libs/common/src/billing/abstractions/organization-billing.service.ts +++ b/libs/common/src/billing/abstractions/organization-billing.service.ts @@ -1,11 +1,10 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { BillingSourceResponse } from "@bitwarden/common/billing/models/response/billing.response"; -import { PaymentSourceResponse } from "@bitwarden/common/billing/models/response/payment-source.response"; import { OrganizationResponse } from "../../admin-console/models/response/organization.response"; import { InitiationPath } from "../../models/request/reference-event.request"; import { PaymentMethodType, PlanType } from "../enums"; +import { PaymentSourceResponse } from "../models/response/payment-source.response"; export type OrganizationInformation = { name: string; @@ -46,9 +45,7 @@ export type SubscriptionInformation = { }; export abstract class OrganizationBillingServiceAbstraction { - getPaymentSource: ( - organizationId: string, - ) => Promise; + getPaymentSource: (organizationId: string) => Promise; purchaseSubscription: (subscription: SubscriptionInformation) => Promise; diff --git a/libs/common/src/billing/abstractions/organizations/organization-billing-api.service.abstraction.ts b/libs/common/src/billing/abstractions/organizations/organization-billing-api.service.abstraction.ts index 6a72724a5ec..2ed25491049 100644 --- a/libs/common/src/billing/abstractions/organizations/organization-billing-api.service.abstraction.ts +++ b/libs/common/src/billing/abstractions/organizations/organization-billing-api.service.abstraction.ts @@ -3,7 +3,7 @@ import { BillingInvoiceResponse, BillingTransactionResponse, -} from "@bitwarden/common/billing/models/response/billing.response"; +} from "../../models/response/billing.response"; export class OrganizationBillingApiServiceAbstraction { getBillingInvoices: ( diff --git a/libs/common/src/billing/abstractions/tax.service.abstraction.ts b/libs/common/src/billing/abstractions/tax.service.abstraction.ts index 438d3f394e0..7a744dae856 100644 --- a/libs/common/src/billing/abstractions/tax.service.abstraction.ts +++ b/libs/common/src/billing/abstractions/tax.service.abstraction.ts @@ -1,7 +1,7 @@ -import { CountryListItem } from "@bitwarden/common/billing/models/domain"; -import { PreviewIndividualInvoiceRequest } from "@bitwarden/common/billing/models/request/preview-individual-invoice.request"; -import { PreviewOrganizationInvoiceRequest } from "@bitwarden/common/billing/models/request/preview-organization-invoice.request"; -import { PreviewInvoiceResponse } from "@bitwarden/common/billing/models/response/preview-invoice.response"; +import { CountryListItem } from "../models/domain"; +import { PreviewIndividualInvoiceRequest } from "../models/request/preview-individual-invoice.request"; +import { PreviewOrganizationInvoiceRequest } from "../models/request/preview-organization-invoice.request"; +import { PreviewInvoiceResponse } from "../models/response/preview-invoice.response"; export abstract class TaxServiceAbstraction { abstract getCountries(): CountryListItem[]; diff --git a/libs/common/src/billing/models/domain/tax-information.ts b/libs/common/src/billing/models/domain/tax-information.ts index 78e1bcc42b5..794cdef3ed4 100644 --- a/libs/common/src/billing/models/domain/tax-information.ts +++ b/libs/common/src/billing/models/domain/tax-information.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { TaxInfoResponse } from "@bitwarden/common/billing/models/response/tax-info.response"; +import { TaxInfoResponse } from "../response/tax-info.response"; export class TaxInformation { country: string; diff --git a/libs/common/src/billing/models/request/expanded-tax-info-update.request.ts b/libs/common/src/billing/models/request/expanded-tax-info-update.request.ts index 784d2691629..83b254ac512 100644 --- a/libs/common/src/billing/models/request/expanded-tax-info-update.request.ts +++ b/libs/common/src/billing/models/request/expanded-tax-info-update.request.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { TaxInformation } from "@bitwarden/common/billing/models/domain/tax-information"; +import { TaxInformation } from "../domain/tax-information"; import { TaxInfoUpdateRequest } from "./tax-info-update.request"; diff --git a/libs/common/src/billing/models/request/preview-organization-invoice.request.ts b/libs/common/src/billing/models/request/preview-organization-invoice.request.ts index 365dff5c110..40d8db03d3b 100644 --- a/libs/common/src/billing/models/request/preview-organization-invoice.request.ts +++ b/libs/common/src/billing/models/request/preview-organization-invoice.request.ts @@ -1,4 +1,4 @@ -import { PlanType } from "@bitwarden/common/billing/enums"; +import { PlanType } from "../../enums"; export class PreviewOrganizationInvoiceRequest { organizationId?: string; diff --git a/libs/common/src/billing/models/request/tokenized-payment-source.request.ts b/libs/common/src/billing/models/request/tokenized-payment-source.request.ts index c740e4157ed..e4bf575cc6a 100644 --- a/libs/common/src/billing/models/request/tokenized-payment-source.request.ts +++ b/libs/common/src/billing/models/request/tokenized-payment-source.request.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { PaymentMethodType } from "@bitwarden/common/billing/enums"; +import { PaymentMethodType } from "../../enums"; export class TokenizedPaymentSourceRequest { type: PaymentMethodType; diff --git a/libs/common/src/billing/models/request/update-payment-method.request.ts b/libs/common/src/billing/models/request/update-payment-method.request.ts index 9ef91ae579b..10b03103716 100644 --- a/libs/common/src/billing/models/request/update-payment-method.request.ts +++ b/libs/common/src/billing/models/request/update-payment-method.request.ts @@ -1,7 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; -import { TokenizedPaymentSourceRequest } from "@bitwarden/common/billing/models/request/tokenized-payment-source.request"; +import { ExpandedTaxInfoUpdateRequest } from "./expanded-tax-info-update.request"; +import { TokenizedPaymentSourceRequest } from "./tokenized-payment-source.request"; export class UpdatePaymentMethodRequest { paymentSource: TokenizedPaymentSourceRequest; diff --git a/libs/common/src/billing/models/response/invoices.response.ts b/libs/common/src/billing/models/response/invoices.response.ts index bf797ba42d6..05c95b83c6f 100644 --- a/libs/common/src/billing/models/response/invoices.response.ts +++ b/libs/common/src/billing/models/response/invoices.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "@bitwarden/common/models/response/base.response"; +import { BaseResponse } from "../../../models/response/base.response"; export class InvoicesResponse extends BaseResponse { invoices: InvoiceResponse[] = []; diff --git a/libs/common/src/billing/models/response/payment-source.response.ts b/libs/common/src/billing/models/response/payment-source.response.ts index 1aeeb450b11..93418fc2f55 100644 --- a/libs/common/src/billing/models/response/payment-source.response.ts +++ b/libs/common/src/billing/models/response/payment-source.response.ts @@ -1,5 +1,5 @@ -import { PaymentMethodType } from "@bitwarden/common/billing/enums"; -import { BaseResponse } from "@bitwarden/common/models/response/base.response"; +import { BaseResponse } from "../../../models/response/base.response"; +import { PaymentMethodType } from "../../enums"; export class PaymentSourceResponse extends BaseResponse { type: PaymentMethodType; diff --git a/libs/common/src/billing/models/response/preview-invoice.response.ts b/libs/common/src/billing/models/response/preview-invoice.response.ts index c822a569bb3..efd3da3e9f1 100644 --- a/libs/common/src/billing/models/response/preview-invoice.response.ts +++ b/libs/common/src/billing/models/response/preview-invoice.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "@bitwarden/common/models/response/base.response"; +import { BaseResponse } from "../../../models/response/base.response"; export class PreviewInvoiceResponse extends BaseResponse { effectiveTaxRate: number; diff --git a/libs/common/src/billing/models/response/provider-subscription-response.ts b/libs/common/src/billing/models/response/provider-subscription-response.ts index 2ecf988addd..d861a8a9d46 100644 --- a/libs/common/src/billing/models/response/provider-subscription-response.ts +++ b/libs/common/src/billing/models/response/provider-subscription-response.ts @@ -1,9 +1,11 @@ -import { ProviderType } from "@bitwarden/common/admin-console/enums"; -import { PlanType, ProductTierType } from "@bitwarden/common/billing/enums"; -import { SubscriptionSuspensionResponse } from "@bitwarden/common/billing/models/response/subscription-suspension.response"; -import { TaxInfoResponse } from "@bitwarden/common/billing/models/response/tax-info.response"; +import { PaymentSourceResponse } from "@bitwarden/common/billing/models/response/payment-source.response"; +import { ProviderType } from "../../../admin-console/enums"; import { BaseResponse } from "../../../models/response/base.response"; +import { PlanType, ProductTierType } from "../../enums"; + +import { SubscriptionSuspensionResponse } from "./subscription-suspension.response"; +import { TaxInfoResponse } from "./tax-info.response"; export class ProviderSubscriptionResponse extends BaseResponse { status: string; @@ -16,6 +18,7 @@ export class ProviderSubscriptionResponse extends BaseResponse { cancelAt?: string; suspension?: SubscriptionSuspensionResponse; providerType: ProviderType; + paymentSource?: PaymentSourceResponse; constructor(response: any) { super(response); @@ -38,6 +41,10 @@ export class ProviderSubscriptionResponse extends BaseResponse { this.suspension = new SubscriptionSuspensionResponse(suspension); } this.providerType = this.getResponseProperty("providerType"); + const paymentSource = this.getResponseProperty("paymentSource"); + if (paymentSource != null) { + this.paymentSource = new PaymentSourceResponse(paymentSource); + } } } diff --git a/libs/common/src/billing/models/response/subscription-suspension.response.ts b/libs/common/src/billing/models/response/subscription-suspension.response.ts index 418e1c443c8..3d714a05dba 100644 --- a/libs/common/src/billing/models/response/subscription-suspension.response.ts +++ b/libs/common/src/billing/models/response/subscription-suspension.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "@bitwarden/common/models/response/base.response"; +import { BaseResponse } from "../../../models/response/base.response"; export class SubscriptionSuspensionResponse extends BaseResponse { suspensionDate: string; diff --git a/libs/common/src/billing/models/response/tax-id-types.response.ts b/libs/common/src/billing/models/response/tax-id-types.response.ts index 0d5cce46c8c..f31f2133b34 100644 --- a/libs/common/src/billing/models/response/tax-id-types.response.ts +++ b/libs/common/src/billing/models/response/tax-id-types.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "@bitwarden/common/models/response/base.response"; +import { BaseResponse } from "../../../models/response/base.response"; export class TaxIdTypesResponse extends BaseResponse { taxIdTypes: TaxIdTypeResponse[] = []; diff --git a/libs/common/src/billing/models/response/tax-rate.response.ts b/libs/common/src/billing/models/response/tax-rate.response.ts deleted file mode 100644 index 2c07129ba2c..00000000000 --- a/libs/common/src/billing/models/response/tax-rate.response.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { BaseResponse } from "../../../models/response/base.response"; - -export class TaxRateResponse extends BaseResponse { - id: string; - country: string; - state: string; - postalCode: string; - rate: number; - - constructor(response: any) { - super(response); - this.id = this.getResponseProperty("Id"); - this.country = this.getResponseProperty("Country"); - this.state = this.getResponseProperty("State"); - this.postalCode = this.getResponseProperty("PostalCode"); - this.rate = this.getResponseProperty("Rate"); - } -} diff --git a/libs/common/src/billing/services/account/billing-account-profile-state.service.spec.ts b/libs/common/src/billing/services/account/billing-account-profile-state.service.spec.ts index 372d8099865..02dbef469d6 100644 --- a/libs/common/src/billing/services/account/billing-account-profile-state.service.spec.ts +++ b/libs/common/src/billing/services/account/billing-account-profile-state.service.spec.ts @@ -1,17 +1,16 @@ import { firstValueFrom } from "rxjs"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { BillingHistoryResponse } from "@bitwarden/common/billing/models/response/billing-history.response"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; - import { FakeAccountService, mockAccountServiceWith, FakeStateProvider, FakeSingleUserState, } from "../../../../spec"; +import { ApiService } from "../../../abstractions/api.service"; +import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service"; import { UserId } from "../../../types/guid"; import { BillingAccountProfile } from "../../abstractions/account/billing-account-profile-state.service"; +import { BillingHistoryResponse } from "../../models/response/billing-history.response"; import { BILLING_ACCOUNT_PROFILE_KEY_DEFINITION, diff --git a/libs/common/src/billing/services/account/billing-account-profile-state.service.ts b/libs/common/src/billing/services/account/billing-account-profile-state.service.ts index 579a81eeb5c..155ce1493b4 100644 --- a/libs/common/src/billing/services/account/billing-account-profile-state.service.ts +++ b/libs/common/src/billing/services/account/billing-account-profile-state.service.ts @@ -1,8 +1,7 @@ import { map, Observable, combineLatest, concatMap } from "rxjs"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; - +import { ApiService } from "../../../abstractions/api.service"; +import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service"; import { BILLING_DISK, StateProvider, UserKeyDefinition } from "../../../platform/state"; import { UserId } from "../../../types/guid"; import { diff --git a/libs/common/src/billing/services/billing-api.service.ts b/libs/common/src/billing/services/billing-api.service.ts index 7ce5602f3cc..2292f26e616 100644 --- a/libs/common/src/billing/services/billing-api.service.ts +++ b/libs/common/src/billing/services/billing-api.service.ts @@ -1,33 +1,28 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; -import { UpdatePaymentMethodRequest } from "@bitwarden/common/billing/models/request/update-payment-method.request"; -import { VerifyBankAccountRequest } from "@bitwarden/common/billing/models/request/verify-bank-account.request"; -import { InvoicesResponse } from "@bitwarden/common/billing/models/response/invoices.response"; -import { PaymentMethodResponse } from "@bitwarden/common/billing/models/response/payment-method.response"; -import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { ToastService } from "@bitwarden/components"; + +import { TaxInfoResponse } from "@bitwarden/common/billing/models/response/tax-info.response"; import { ApiService } from "../../abstractions/api.service"; import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request"; -import { BillingApiServiceAbstraction } from "../../billing/abstractions"; -import { PaymentMethodType } from "../../billing/enums"; -import { ExpandedTaxInfoUpdateRequest } from "../../billing/models/request/expanded-tax-info-update.request"; -import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request"; -import { OrganizationBillingMetadataResponse } from "../../billing/models/response/organization-billing-metadata.response"; -import { PlanResponse } from "../../billing/models/response/plan.response"; +import { ProviderOrganizationOrganizationDetailsResponse } from "../../admin-console/models/response/provider/provider-organization.response"; import { ListResponse } from "../../models/response/list.response"; +import { BillingApiServiceAbstraction } from "../abstractions"; +import { PaymentMethodType } from "../enums"; import { CreateClientOrganizationRequest } from "../models/request/create-client-organization.request"; +import { ExpandedTaxInfoUpdateRequest } from "../models/request/expanded-tax-info-update.request"; +import { SubscriptionCancellationRequest } from "../models/request/subscription-cancellation.request"; import { UpdateClientOrganizationRequest } from "../models/request/update-client-organization.request"; +import { UpdatePaymentMethodRequest } from "../models/request/update-payment-method.request"; +import { VerifyBankAccountRequest } from "../models/request/verify-bank-account.request"; +import { InvoicesResponse } from "../models/response/invoices.response"; +import { OrganizationBillingMetadataResponse } from "../models/response/organization-billing-metadata.response"; +import { PaymentMethodResponse } from "../models/response/payment-method.response"; +import { PlanResponse } from "../models/response/plan.response"; import { ProviderSubscriptionResponse } from "../models/response/provider-subscription-response"; export class BillingApiService implements BillingApiServiceAbstraction { - constructor( - private apiService: ApiService, - private logService: LogService, - private toastService: ToastService, - ) {} + constructor(private apiService: ApiService) {} cancelOrganizationSubscription( organizationId: string, @@ -89,14 +84,12 @@ export class BillingApiService implements BillingApiServiceAbstraction { } async getOrganizationPaymentMethod(organizationId: string): Promise { - const response = await this.execute(() => - this.apiService.send( - "GET", - "/organizations/" + organizationId + "/billing/payment-method", - null, - true, - true, - ), + const response = await this.apiService.send( + "GET", + "/organizations/" + organizationId + "/billing/payment-method", + null, + true, + true, ); return new PaymentMethodResponse(response); } @@ -120,38 +113,49 @@ export class BillingApiService implements BillingApiServiceAbstraction { async getProviderClientOrganizations( providerId: string, ): Promise> { - const response = await this.execute(() => - this.apiService.send("GET", "/providers/" + providerId + "/organizations", null, true, true), + const response = await this.apiService.send( + "GET", + "/providers/" + providerId + "/organizations", + null, + true, + true, ); return new ListResponse(response, ProviderOrganizationOrganizationDetailsResponse); } async getProviderInvoices(providerId: string): Promise { - const response = await this.execute(() => - this.apiService.send( - "GET", - "/providers/" + providerId + "/billing/invoices", - null, - true, - true, - ), + const response = await this.apiService.send( + "GET", + "/providers/" + providerId + "/billing/invoices", + null, + true, + true, ); return new InvoicesResponse(response); } async getProviderSubscription(providerId: string): Promise { - const response = await this.execute(() => - this.apiService.send( - "GET", - "/providers/" + providerId + "/billing/subscription", - null, - true, - true, - ), + const response = await this.apiService.send( + "GET", + "/providers/" + providerId + "/billing/subscription", + null, + true, + true, ); return new ProviderSubscriptionResponse(response); } + async getProviderTaxInformation(providerId: string): Promise { + const response = await this.apiService.send( + "GET", + "/providers/" + providerId + "/billing/tax-information", + null, + true, + true, + ); + return new TaxInfoResponse(response); + } + async updateOrganizationPaymentMethod( organizationId: string, request: UpdatePaymentMethodRequest, @@ -192,6 +196,19 @@ export class BillingApiService implements BillingApiServiceAbstraction { ); } + async updateProviderPaymentMethod( + providerId: string, + request: UpdatePaymentMethodRequest, + ): Promise { + return await this.apiService.send( + "PUT", + "/providers/" + providerId + "/billing/payment-method", + request, + true, + false, + ); + } + async updateProviderTaxInformation(providerId: string, request: ExpandedTaxInfoUpdateRequest) { return await this.apiService.send( "PUT", @@ -215,6 +232,19 @@ export class BillingApiService implements BillingApiServiceAbstraction { ); } + async verifyProviderBankAccount( + providerId: string, + request: VerifyBankAccountRequest, + ): Promise { + return await this.apiService.send( + "POST", + "/providers/" + providerId + "/billing/payment-method/verify-bank-account", + request, + true, + false, + ); + } + async restartSubscription( organizationId: string, request: OrganizationCreateRequest, @@ -227,20 +257,4 @@ export class BillingApiService implements BillingApiServiceAbstraction { false, ); } - - private async execute(request: () => Promise): Promise { - try { - return await request(); - } catch (error) { - this.logService.error(error); - if (error instanceof ErrorResponse) { - this.toastService.showToast({ - variant: "error", - title: null, - message: error.getSingleMessage(), - }); - } - throw error; - } - } } diff --git a/libs/common/src/billing/services/organization-billing.service.ts b/libs/common/src/billing/services/organization-billing.service.ts index ca10b368662..83efbf0a30c 100644 --- a/libs/common/src/billing/services/organization-billing.service.ts +++ b/libs/common/src/billing/services/organization-billing.service.ts @@ -1,18 +1,5 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { - BillingApiServiceAbstraction, - OrganizationBillingServiceAbstraction, - OrganizationInformation, - PaymentInformation, - PlanInformation, - SubscriptionInformation, -} from "@bitwarden/common/billing/abstractions"; -import { BillingSourceResponse } from "@bitwarden/common/billing/models/response/billing.response"; -import { PaymentSourceResponse } from "@bitwarden/common/billing/models/response/payment-source.response"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { SyncService } from "@bitwarden/common/platform/sync"; import { KeyService } from "@bitwarden/key-management"; import { ApiService } from "../../abstractions/api.service"; @@ -20,12 +7,22 @@ import { OrganizationApiServiceAbstraction as OrganizationApiService } from "../ import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request"; import { OrganizationKeysRequest } from "../../admin-console/models/request/organization-keys.request"; import { OrganizationResponse } from "../../admin-console/models/response/organization.response"; -import { EncryptService } from "../../platform/abstractions/encrypt.service"; +import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service"; import { I18nService } from "../../platform/abstractions/i18n.service"; import { EncString } from "../../platform/models/domain/enc-string"; +import { SyncService } from "../../platform/sync"; import { OrgKey } from "../../types/key"; +import { + BillingApiServiceAbstraction, + OrganizationBillingServiceAbstraction, + OrganizationInformation, + PaymentInformation, + PlanInformation, + SubscriptionInformation, +} from "../abstractions"; import { PlanType } from "../enums"; import { OrganizationNoPaymentMethodCreateRequest } from "../models/request/organization-no-payment-method-create-request"; +import { PaymentSourceResponse } from "../models/response/payment-source.response"; interface OrganizationKeys { encryptedKey: EncString; @@ -38,7 +35,6 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs constructor( private apiService: ApiService, private billingApiService: BillingApiServiceAbstraction, - private configService: ConfigService, private keyService: KeyService, private encryptService: EncryptService, private i18nService: I18nService, @@ -46,21 +42,9 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs private syncService: SyncService, ) {} - async getPaymentSource( - organizationId: string, - ): Promise { - const deprecateStripeSourcesAPI = await this.configService.getFeatureFlag( - FeatureFlag.AC2476_DeprecateStripeSourcesAPI, - ); - - if (deprecateStripeSourcesAPI) { - const paymentMethod = - await this.billingApiService.getOrganizationPaymentMethod(organizationId); - return paymentMethod.paymentSource; - } else { - const billing = await this.organizationApiService.getBilling(organizationId); - return billing.paymentSource; - } + async getPaymentSource(organizationId: string): Promise { + const paymentMethod = await this.billingApiService.getOrganizationPaymentMethod(organizationId); + return paymentMethod.paymentSource; } async purchaseSubscription(subscription: SubscriptionInformation): Promise { diff --git a/libs/common/src/billing/services/tax.service.ts b/libs/common/src/billing/services/tax.service.ts index 45e57267ec0..aa27c99adc8 100644 --- a/libs/common/src/billing/services/tax.service.ts +++ b/libs/common/src/billing/services/tax.service.ts @@ -1,9 +1,9 @@ -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; -import { CountryListItem } from "@bitwarden/common/billing/models/domain"; -import { PreviewIndividualInvoiceRequest } from "@bitwarden/common/billing/models/request/preview-individual-invoice.request"; -import { PreviewOrganizationInvoiceRequest } from "@bitwarden/common/billing/models/request/preview-organization-invoice.request"; -import { PreviewInvoiceResponse } from "@bitwarden/common/billing/models/response/preview-invoice.response"; +import { ApiService } from "../../abstractions/api.service"; +import { TaxServiceAbstraction } from "../abstractions/tax.service.abstraction"; +import { CountryListItem } from "../models/domain"; +import { PreviewIndividualInvoiceRequest } from "../models/request/preview-individual-invoice.request"; +import { PreviewOrganizationInvoiceRequest } from "../models/request/preview-organization-invoice.request"; +import { PreviewInvoiceResponse } from "../models/response/preview-invoice.response"; export class TaxService implements TaxServiceAbstraction { constructor(private apiService: ApiService) {} diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 42482c0e901..0dbc1f9adc0 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -4,48 +4,56 @@ * Flags MUST be short lived and SHALL be removed once enabled. */ export enum FeatureFlag { - BrowserFilelessImport = "browser-fileless-import", - ItemShare = "item-share", - GeneratorToolsModernization = "generator-tools-modernization", - AC1795_UpdatedSubscriptionStatusSection = "AC-1795_updated-subscription-status-section", - ExtensionRefresh = "extension-refresh", - PersistPopupView = "persist-popup-view", - PM4154_BulkEncryptionService = "PM-4154-bulk-encryption-service", - UseTreeWalkerApiForPageDetailsCollection = "use-tree-walker-api-for-page-details-collection", - EmailVerification = "email-verification", - InlineMenuFieldQualification = "inline-menu-field-qualification", - TwoFactorComponentRefactor = "two-factor-component-refactor", - InlineMenuPositioningImprovements = "inline-menu-positioning-improvements", - ProviderClientVaultPrivacyBanner = "ac-2833-provider-client-vault-privacy-banner", - VaultBulkManagementAction = "vault-bulk-management-action", - IdpAutoSubmitLogin = "idp-auto-submit-login", - UnauthenticatedExtensionUIRefresh = "unauth-ui-refresh", - GenerateIdentityFillScriptRefactor = "generate-identity-fill-script-refactor", - EnableNewCardCombinedExpiryAutofill = "enable-new-card-combined-expiry-autofill", - DelayFido2PageScriptInitWithinMv2 = "delay-fido2-page-script-init-within-mv2", + /* Admin Console Team */ AccountDeprovisioning = "pm-10308-account-deprovisioning", - SSHKeyVaultItem = "ssh-key-vault-item", - SSHAgent = "ssh-agent", - NotificationBarAddLoginImprovements = "notification-bar-add-login-improvements", - BlockBrowserInjectionsByDomain = "block-browser-injections-by-domain", - AC2476_DeprecateStripeSourcesAPI = "AC-2476-deprecate-stripe-sources-api", - CipherKeyEncryption = "cipher-key-encryption", VerifiedSsoDomainEndpoint = "pm-12337-refactor-sso-details-endpoint", - PM11901_RefactorSelfHostingLicenseUploader = "PM-11901-refactor-self-hosting-license-uploader", - PM14505AdminConsoleIntegrationPage = "pm-14505-admin-console-integration-page", + LimitItemDeletion = "pm-15493-restrict-item-deletion-to-can-manage-permission", + + /* Autofill */ + BlockBrowserInjectionsByDomain = "block-browser-injections-by-domain", + DelayFido2PageScriptInitWithinMv2 = "delay-fido2-page-script-init-within-mv2", + EnableNewCardCombinedExpiryAutofill = "enable-new-card-combined-expiry-autofill", + GenerateIdentityFillScriptRefactor = "generate-identity-fill-script-refactor", + IdpAutoSubmitLogin = "idp-auto-submit-login", + InlineMenuFieldQualification = "inline-menu-field-qualification", + InlineMenuPositioningImprovements = "inline-menu-positioning-improvements", + NotificationBarAddLoginImprovements = "notification-bar-add-login-improvements", + NotificationRefresh = "notification-refresh", + UseTreeWalkerApiForPageDetailsCollection = "use-tree-walker-api-for-page-details-collection", + + /* Tools */ + ItemShare = "item-share", CriticalApps = "pm-14466-risk-insights-critical-application", - TrialPaymentOptional = "PM-8163-trial-payment", - SecurityTasks = "security-tasks", + EnableRiskInsightsNotifications = "enable-risk-insights-notifications", + DesktopSendUIRefresh = "desktop-send-ui-refresh", + ExportAttachments = "export-attachments", + + /* Vault */ + PM8851_BrowserOnboardingNudge = "pm-8851-browser-onboarding-nudge", + PM9111ExtensionPersistAddEditForm = "pm-9111-extension-persist-add-edit-form", NewDeviceVerificationTemporaryDismiss = "new-device-temporary-dismiss", NewDeviceVerificationPermanentDismiss = "new-device-permanent-dismiss", - DisableFreeFamiliesSponsorship = "PM-12274-disable-free-families-sponsorship", - InlineMenuTotp = "inline-menu-totp", + VaultBulkManagementAction = "vault-bulk-management-action", + SecurityTasks = "security-tasks", + + /* Auth */ + PM9112_DeviceApprovalPersistence = "pm-9112-device-approval-persistence", + + /* Key Management */ + Argon2Default = "argon2-default", + + UserKeyRotationV2 = "userkey-rotation-v2", + PM4154_BulkEncryptionService = "PM-4154-bulk-encryption-service", + UnauthenticatedExtensionUIRefresh = "unauth-ui-refresh", + CipherKeyEncryption = "cipher-key-encryption", + TrialPaymentOptional = "PM-8163-trial-payment", MacOsNativeCredentialSync = "macos-native-credential-sync", - PM11360RemoveProviderExportPermission = "pm-11360-remove-provider-export-permission", - PM12443RemovePagingLogic = "pm-12443-remove-paging-logic", PrivateKeyRegeneration = "pm-12241-private-key-regeneration", ResellerManagedOrgAlert = "PM-15814-alert-owners-of-reseller-managed-orgs", - Argon2Default = "argon2-default", + AccountDeprovisioningBanner = "pm-17120-account-deprovisioning-admin-console-banner", + PM15179_AddExistingOrgsFromProviderPortal = "pm-15179-add-existing-orgs-from-provider-portal", + PM12276_BreadcrumbEventLogs = "pm-12276-breadcrumbing-for-business-features", + PM18794_ProviderPaymentMethod = "pm-18794-provider-payment-method", } export type AllowedFeatureFlagTypes = boolean | number | string; @@ -60,48 +68,56 @@ const FALSE = false as boolean; * We support true as a value as we prefer flags to "enable" not "disable". */ export const DefaultFeatureFlagValue = { - [FeatureFlag.BrowserFilelessImport]: FALSE, - [FeatureFlag.ItemShare]: FALSE, - [FeatureFlag.GeneratorToolsModernization]: FALSE, - [FeatureFlag.AC1795_UpdatedSubscriptionStatusSection]: FALSE, - [FeatureFlag.ExtensionRefresh]: FALSE, - [FeatureFlag.PersistPopupView]: FALSE, - [FeatureFlag.PM4154_BulkEncryptionService]: FALSE, - [FeatureFlag.UseTreeWalkerApiForPageDetailsCollection]: FALSE, - [FeatureFlag.EmailVerification]: FALSE, - [FeatureFlag.InlineMenuFieldQualification]: FALSE, - [FeatureFlag.TwoFactorComponentRefactor]: FALSE, - [FeatureFlag.InlineMenuPositioningImprovements]: FALSE, - [FeatureFlag.ProviderClientVaultPrivacyBanner]: FALSE, - [FeatureFlag.VaultBulkManagementAction]: FALSE, - [FeatureFlag.IdpAutoSubmitLogin]: FALSE, - [FeatureFlag.UnauthenticatedExtensionUIRefresh]: FALSE, - [FeatureFlag.GenerateIdentityFillScriptRefactor]: FALSE, - [FeatureFlag.EnableNewCardCombinedExpiryAutofill]: FALSE, - [FeatureFlag.DelayFido2PageScriptInitWithinMv2]: FALSE, + /* Admin Console Team */ [FeatureFlag.AccountDeprovisioning]: FALSE, - [FeatureFlag.SSHKeyVaultItem]: FALSE, - [FeatureFlag.SSHAgent]: FALSE, - [FeatureFlag.NotificationBarAddLoginImprovements]: FALSE, - [FeatureFlag.BlockBrowserInjectionsByDomain]: FALSE, - [FeatureFlag.AC2476_DeprecateStripeSourcesAPI]: FALSE, - [FeatureFlag.CipherKeyEncryption]: FALSE, [FeatureFlag.VerifiedSsoDomainEndpoint]: FALSE, - [FeatureFlag.PM11901_RefactorSelfHostingLicenseUploader]: FALSE, - [FeatureFlag.PM14505AdminConsoleIntegrationPage]: FALSE, + [FeatureFlag.LimitItemDeletion]: FALSE, + + /* Autofill */ + [FeatureFlag.BlockBrowserInjectionsByDomain]: FALSE, + [FeatureFlag.DelayFido2PageScriptInitWithinMv2]: FALSE, + [FeatureFlag.EnableNewCardCombinedExpiryAutofill]: FALSE, + [FeatureFlag.GenerateIdentityFillScriptRefactor]: FALSE, + [FeatureFlag.IdpAutoSubmitLogin]: FALSE, + [FeatureFlag.InlineMenuFieldQualification]: FALSE, + [FeatureFlag.InlineMenuPositioningImprovements]: FALSE, + [FeatureFlag.NotificationBarAddLoginImprovements]: FALSE, + [FeatureFlag.NotificationRefresh]: FALSE, + [FeatureFlag.UseTreeWalkerApiForPageDetailsCollection]: FALSE, + + /* Tools */ + [FeatureFlag.ItemShare]: FALSE, [FeatureFlag.CriticalApps]: FALSE, - [FeatureFlag.TrialPaymentOptional]: FALSE, - [FeatureFlag.SecurityTasks]: FALSE, + [FeatureFlag.EnableRiskInsightsNotifications]: FALSE, + [FeatureFlag.DesktopSendUIRefresh]: FALSE, + [FeatureFlag.ExportAttachments]: FALSE, + + /* Vault */ + [FeatureFlag.PM8851_BrowserOnboardingNudge]: FALSE, + [FeatureFlag.PM9111ExtensionPersistAddEditForm]: FALSE, [FeatureFlag.NewDeviceVerificationTemporaryDismiss]: FALSE, [FeatureFlag.NewDeviceVerificationPermanentDismiss]: FALSE, - [FeatureFlag.DisableFreeFamiliesSponsorship]: FALSE, - [FeatureFlag.InlineMenuTotp]: FALSE, + [FeatureFlag.VaultBulkManagementAction]: FALSE, + [FeatureFlag.SecurityTasks]: FALSE, + + /* Auth */ + [FeatureFlag.PM9112_DeviceApprovalPersistence]: FALSE, + + /* Key Management */ + [FeatureFlag.Argon2Default]: FALSE, + + [FeatureFlag.UserKeyRotationV2]: FALSE, + [FeatureFlag.PM4154_BulkEncryptionService]: FALSE, + [FeatureFlag.UnauthenticatedExtensionUIRefresh]: FALSE, + [FeatureFlag.CipherKeyEncryption]: FALSE, + [FeatureFlag.TrialPaymentOptional]: FALSE, [FeatureFlag.MacOsNativeCredentialSync]: FALSE, - [FeatureFlag.PM11360RemoveProviderExportPermission]: FALSE, - [FeatureFlag.PM12443RemovePagingLogic]: FALSE, [FeatureFlag.PrivateKeyRegeneration]: FALSE, [FeatureFlag.ResellerManagedOrgAlert]: FALSE, - [FeatureFlag.Argon2Default]: FALSE, + [FeatureFlag.AccountDeprovisioningBanner]: FALSE, + [FeatureFlag.PM15179_AddExistingOrgsFromProviderPortal]: FALSE, + [FeatureFlag.PM12276_BreadcrumbEventLogs]: FALSE, + [FeatureFlag.PM18794_ProviderPaymentMethod]: FALSE, } satisfies Record; export type DefaultFeatureFlagValueType = typeof DefaultFeatureFlagValue; diff --git a/libs/common/src/enums/push-technology.enum.ts b/libs/common/src/enums/push-technology.enum.ts new file mode 100644 index 00000000000..9452c144bb7 --- /dev/null +++ b/libs/common/src/enums/push-technology.enum.ts @@ -0,0 +1,13 @@ +/** + * The preferred push technology of the server. + */ +export enum PushTechnology { + /** + * Indicates that we should use SignalR over web sockets to receive push notifications from the server. + */ + SignalR = 0, + /** + * Indicatates that we should use WebPush to receive push notifications from the server. + */ + WebPush = 1, +} diff --git a/libs/common/src/key-management/crypto/abstractions/bulk-encrypt.service.ts b/libs/common/src/key-management/crypto/abstractions/bulk-encrypt.service.ts new file mode 100644 index 00000000000..3e47ccdb5f2 --- /dev/null +++ b/libs/common/src/key-management/crypto/abstractions/bulk-encrypt.service.ts @@ -0,0 +1,10 @@ +import { Decryptable } from "@bitwarden/common/platform/interfaces/decryptable.interface"; +import { InitializerMetadata } from "@bitwarden/common/platform/interfaces/initializer-metadata.interface"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; + +export abstract class BulkEncryptService { + abstract decryptItems( + items: Decryptable[], + key: SymmetricCryptoKey, + ): Promise; +} diff --git a/libs/common/src/platform/abstractions/encrypt.service.ts b/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts similarity index 80% rename from libs/common/src/platform/abstractions/encrypt.service.ts rename to libs/common/src/key-management/crypto/abstractions/encrypt.service.ts index a660524699d..f7f064f5251 100644 --- a/libs/common/src/platform/abstractions/encrypt.service.ts +++ b/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts @@ -1,9 +1,9 @@ -import { Decryptable } from "../interfaces/decryptable.interface"; -import { Encrypted } from "../interfaces/encrypted"; -import { InitializerMetadata } from "../interfaces/initializer-metadata.interface"; -import { EncArrayBuffer } from "../models/domain/enc-array-buffer"; -import { EncString } from "../models/domain/enc-string"; -import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; +import { Decryptable } from "@bitwarden/common/platform/interfaces/decryptable.interface"; +import { Encrypted } from "@bitwarden/common/platform/interfaces/encrypted"; +import { InitializerMetadata } from "@bitwarden/common/platform/interfaces/initializer-metadata.interface"; +import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; +import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; export abstract class EncryptService { abstract encrypt(plainValue: string | Uint8Array, key: SymmetricCryptoKey): Promise; @@ -33,10 +33,9 @@ export abstract class EncryptService { encThing: Encrypted, key: SymmetricCryptoKey, decryptTrace?: string, - ): Promise; + ): Promise; abstract rsaEncrypt(data: Uint8Array, publicKey: Uint8Array): Promise; abstract rsaDecrypt(data: EncString, privateKey: Uint8Array): Promise; - abstract resolveLegacyKey(key: SymmetricCryptoKey, encThing: Encrypted): SymmetricCryptoKey; /** * @deprecated Replaced by BulkEncryptService, remove once the feature is tested and the featureflag PM-4154-multi-worker-encryption-service is removed * @param items The items to decrypt diff --git a/libs/common/src/platform/services/cryptography/bulk-encrypt.service.implementation.ts b/libs/common/src/key-management/crypto/services/bulk-encrypt.service.implementation.ts similarity index 84% rename from libs/common/src/platform/services/cryptography/bulk-encrypt.service.implementation.ts rename to libs/common/src/key-management/crypto/services/bulk-encrypt.service.implementation.ts index 1320fbae0e0..1d1e0f52279 100644 --- a/libs/common/src/platform/services/cryptography/bulk-encrypt.service.implementation.ts +++ b/libs/common/src/key-management/crypto/services/bulk-encrypt.service.implementation.ts @@ -3,15 +3,14 @@ import { firstValueFrom, fromEvent, filter, map, takeUntil, defaultIfEmpty, Subject } from "rxjs"; import { Jsonify } from "type-fest"; -import { BulkEncryptService } from "../../abstractions/bulk-encrypt.service"; -import { CryptoFunctionService } from "../../abstractions/crypto-function.service"; -import { LogService } from "../../abstractions/log.service"; -import { Decryptable } from "../../interfaces/decryptable.interface"; -import { InitializerMetadata } from "../../interfaces/initializer-metadata.interface"; -import { Utils } from "../../misc/utils"; -import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key"; - -import { getClassInitializer } from "./get-class-initializer"; +import { BulkEncryptService } from "@bitwarden/common/key-management/crypto/abstractions/bulk-encrypt.service"; +import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { Decryptable } from "@bitwarden/common/platform/interfaces/decryptable.interface"; +import { InitializerMetadata } from "@bitwarden/common/platform/interfaces/initializer-metadata.interface"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { getClassInitializer } from "@bitwarden/common/platform/services/cryptography/get-class-initializer"; // TTL (time to live) is not strictly required but avoids tying up memory resources if inactive const workerTTL = 60000; // 1 minute @@ -88,7 +87,7 @@ export class BulkEncryptServiceImplementation implements BulkEncryptService { new Worker( new URL( /* webpackChunkName: 'encrypt-worker' */ - "@bitwarden/common/platform/services/cryptography/encrypt.worker.ts", + "@bitwarden/common/key-management/crypto/services/encrypt.worker.ts", import.meta.url, ), ), diff --git a/libs/common/src/platform/services/cryptography/encrypt.service.implementation.ts b/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts similarity index 85% rename from libs/common/src/platform/services/cryptography/encrypt.service.implementation.ts rename to libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts index 68263cadf27..d426340c277 100644 --- a/libs/common/src/platform/services/cryptography/encrypt.service.implementation.ts +++ b/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts @@ -1,17 +1,21 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Utils } from "../../../platform/misc/utils"; -import { CryptoFunctionService } from "../../abstractions/crypto-function.service"; -import { EncryptService } from "../../abstractions/encrypt.service"; -import { LogService } from "../../abstractions/log.service"; -import { EncryptionType, encryptionTypeToString as encryptionTypeName } from "../../enums"; -import { Decryptable } from "../../interfaces/decryptable.interface"; -import { Encrypted } from "../../interfaces/encrypted"; -import { InitializerMetadata } from "../../interfaces/initializer-metadata.interface"; -import { EncArrayBuffer } from "../../models/domain/enc-array-buffer"; -import { EncString } from "../../models/domain/enc-string"; -import { EncryptedObject } from "../../models/domain/encrypted-object"; -import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key"; +import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { + EncryptionType, + encryptionTypeToString as encryptionTypeName, +} from "@bitwarden/common/platform/enums"; +import { Decryptable } from "@bitwarden/common/platform/interfaces/decryptable.interface"; +import { Encrypted } from "@bitwarden/common/platform/interfaces/encrypted"; +import { InitializerMetadata } from "@bitwarden/common/platform/interfaces/initializer-metadata.interface"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; +import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { EncryptedObject } from "@bitwarden/common/platform/models/domain/encrypted-object"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; + +import { EncryptService } from "../abstractions/encrypt.service"; export class EncryptServiceImplementation implements EncryptService { constructor( @@ -74,8 +78,6 @@ export class EncryptServiceImplementation implements EncryptService { throw new Error("No key provided for decryption."); } - key = this.resolveLegacyKey(key, encString); - // DO NOT REMOVE OR MOVE. This prevents downgrade to mac-less CBC, which would compromise integrity and confidentiality. if (key.macKey != null && encString?.mac == null) { this.logService.error( @@ -132,7 +134,7 @@ export class EncryptServiceImplementation implements EncryptService { encThing: Encrypted, key: SymmetricCryptoKey, decryptContext: string = "no context", - ): Promise { + ): Promise { if (key == null) { throw new Error("No encryption key provided."); } @@ -141,8 +143,6 @@ export class EncryptServiceImplementation implements EncryptService { throw new Error("Nothing provided for decryption."); } - key = this.resolveLegacyKey(key, encThing); - // DO NOT REMOVE OR MOVE. This prevents downgrade to mac-less CBC, which would compromise integrity and confidentiality. if (key.macKey != null && encThing.macBytes == null) { this.logService.error( @@ -294,19 +294,4 @@ export class EncryptServiceImplementation implements EncryptService { this.logService.error(msg); } } - - /** - * Transform into new key for the old encrypt-then-mac scheme if required, otherwise return the current key unchanged - * @param encThing The encrypted object (e.g. encString or encArrayBuffer) that you want to decrypt - */ - resolveLegacyKey(key: SymmetricCryptoKey, encThing: Encrypted): SymmetricCryptoKey { - if ( - encThing.encryptionType === EncryptionType.AesCbc128_HmacSha256_B64 && - key.encType === EncryptionType.AesCbc256_B64 - ) { - return new SymmetricCryptoKey(key.key, EncryptionType.AesCbc128_HmacSha256_B64); - } - - return key; - } } diff --git a/libs/common/src/key-management/crypto/services/encrypt.service.spec.ts b/libs/common/src/key-management/crypto/services/encrypt.service.spec.ts new file mode 100644 index 00000000000..3ce0d5883d2 --- /dev/null +++ b/libs/common/src/key-management/crypto/services/encrypt.service.spec.ts @@ -0,0 +1,445 @@ +import { mockReset, mock } from "jest-mock-extended"; + +import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { EncryptionType } from "@bitwarden/common/platform/enums"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; +import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { CsprngArray } from "@bitwarden/common/types/csprng"; + +import { makeStaticByteArray } from "../../../../spec"; + +import { EncryptServiceImplementation } from "./encrypt.service.implementation"; + +describe("EncryptService", () => { + const cryptoFunctionService = mock(); + const logService = mock(); + + let encryptService: EncryptServiceImplementation; + + beforeEach(() => { + mockReset(cryptoFunctionService); + mockReset(logService); + + encryptService = new EncryptServiceImplementation(cryptoFunctionService, logService, true); + }); + + describe("encrypt", () => { + it("throws if no key is provided", () => { + return expect(encryptService.encrypt(null, null)).rejects.toThrow( + "No encryption key provided.", + ); + }); + it("returns null if no data is provided", async () => { + const key = mock(); + const actual = await encryptService.encrypt(null, key); + expect(actual).toBeNull(); + }); + it("creates an EncString for Aes256Cbc", async () => { + const key = new SymmetricCryptoKey(makeStaticByteArray(32)); + const plainValue = "data"; + cryptoFunctionService.aesEncrypt.mockResolvedValue(makeStaticByteArray(4, 100)); + cryptoFunctionService.randomBytes.mockResolvedValue(makeStaticByteArray(16) as CsprngArray); + const result = await encryptService.encrypt(plainValue, key); + expect(cryptoFunctionService.aesEncrypt).toHaveBeenCalledWith( + Utils.fromByteStringToArray(plainValue), + makeStaticByteArray(16), + makeStaticByteArray(32), + ); + expect(cryptoFunctionService.hmac).not.toHaveBeenCalled(); + + expect(Utils.fromB64ToArray(result.data).length).toEqual(4); + expect(Utils.fromB64ToArray(result.iv).length).toEqual(16); + }); + it("creates an EncString for Aes256Cbc_HmacSha256_B64", async () => { + const key = new SymmetricCryptoKey(makeStaticByteArray(64)); + const plainValue = "data"; + cryptoFunctionService.hmac.mockResolvedValue(makeStaticByteArray(32)); + cryptoFunctionService.aesEncrypt.mockResolvedValue(makeStaticByteArray(4, 100)); + cryptoFunctionService.randomBytes.mockResolvedValue(makeStaticByteArray(16) as CsprngArray); + const result = await encryptService.encrypt(plainValue, key); + expect(cryptoFunctionService.aesEncrypt).toHaveBeenCalledWith( + Utils.fromByteStringToArray(plainValue), + makeStaticByteArray(16), + makeStaticByteArray(32), + ); + + const macData = new Uint8Array(16 + 4); + macData.set(makeStaticByteArray(16)); + macData.set(makeStaticByteArray(4, 100), 16); + expect(cryptoFunctionService.hmac).toHaveBeenCalledWith( + macData, + makeStaticByteArray(32, 32), + "sha256", + ); + + expect(Utils.fromB64ToArray(result.data).length).toEqual(4); + expect(Utils.fromB64ToArray(result.iv).length).toEqual(16); + expect(Utils.fromB64ToArray(result.mac).length).toEqual(32); + }); + }); + + describe("encryptToBytes", () => { + const plainValue = makeStaticByteArray(16, 1); + + it("throws if no key is provided", () => { + return expect(encryptService.encryptToBytes(plainValue, null)).rejects.toThrow( + "No encryption key", + ); + }); + + it("encrypts data with provided Aes256Cbc key and returns correct encbuffer", async () => { + const key = new SymmetricCryptoKey(makeStaticByteArray(32, 0)); + const iv = makeStaticByteArray(16, 80); + const cipherText = makeStaticByteArray(20, 150); + cryptoFunctionService.randomBytes.mockResolvedValue(iv as CsprngArray); + cryptoFunctionService.aesEncrypt.mockResolvedValue(cipherText); + + const actual = await encryptService.encryptToBytes(plainValue, key); + const expectedBytes = new Uint8Array(1 + iv.byteLength + cipherText.byteLength); + expectedBytes.set([EncryptionType.AesCbc256_B64]); + expectedBytes.set(iv, 1); + expectedBytes.set(cipherText, 1 + iv.byteLength); + + expect(actual.buffer).toEqualBuffer(expectedBytes); + }); + + it("encrypts data with provided Aes256Cbc_HmacSha256 key and returns correct encbuffer", async () => { + const key = new SymmetricCryptoKey(makeStaticByteArray(64, 0)); + const iv = makeStaticByteArray(16, 80); + const mac = makeStaticByteArray(32, 100); + const cipherText = makeStaticByteArray(20, 150); + cryptoFunctionService.randomBytes.mockResolvedValue(iv as CsprngArray); + cryptoFunctionService.aesEncrypt.mockResolvedValue(cipherText); + cryptoFunctionService.hmac.mockResolvedValue(mac); + + const actual = await encryptService.encryptToBytes(plainValue, key); + const expectedBytes = new Uint8Array( + 1 + iv.byteLength + mac.byteLength + cipherText.byteLength, + ); + expectedBytes.set([EncryptionType.AesCbc256_HmacSha256_B64]); + expectedBytes.set(iv, 1); + expectedBytes.set(mac, 1 + iv.byteLength); + expectedBytes.set(cipherText, 1 + iv.byteLength + mac.byteLength); + + expect(actual.buffer).toEqualBuffer(expectedBytes); + }); + }); + + describe("decryptToBytes", () => { + const encType = EncryptionType.AesCbc256_HmacSha256_B64; + const key = new SymmetricCryptoKey(makeStaticByteArray(64, 100)); + const computedMac = new Uint8Array(1); + const encBuffer = new EncArrayBuffer(makeStaticByteArray(60, encType)); + + beforeEach(() => { + cryptoFunctionService.hmac.mockResolvedValue(computedMac); + }); + + it("throws if no key is provided", () => { + return expect(encryptService.decryptToBytes(encBuffer, null)).rejects.toThrow( + "No encryption key", + ); + }); + + it("throws if no encrypted value is provided", () => { + return expect(encryptService.decryptToBytes(null, key)).rejects.toThrow( + "Nothing provided for decryption", + ); + }); + + it("decrypts data with provided key for Aes256Cbc", async () => { + const decryptedBytes = makeStaticByteArray(10, 200); + + cryptoFunctionService.hmac.mockResolvedValue(makeStaticByteArray(1)); + cryptoFunctionService.compare.mockResolvedValue(true); + cryptoFunctionService.aesDecrypt.mockResolvedValueOnce(decryptedBytes); + + const actual = await encryptService.decryptToBytes(encBuffer, key); + + expect(cryptoFunctionService.aesDecrypt).toBeCalledWith( + expect.toEqualBuffer(encBuffer.dataBytes), + expect.toEqualBuffer(encBuffer.ivBytes), + expect.toEqualBuffer(key.encKey), + "cbc", + ); + + expect(actual).toEqualBuffer(decryptedBytes); + }); + + it("decrypts data with provided key for Aes256Cbc", async () => { + const key = new SymmetricCryptoKey(makeStaticByteArray(32, 0)); + const encBuffer = new EncArrayBuffer(makeStaticByteArray(60, EncryptionType.AesCbc256_B64)); + const decryptedBytes = makeStaticByteArray(10, 200); + + cryptoFunctionService.hmac.mockResolvedValue(makeStaticByteArray(1)); + cryptoFunctionService.compare.mockResolvedValue(true); + cryptoFunctionService.aesDecrypt.mockResolvedValueOnce(decryptedBytes); + + const actual = await encryptService.decryptToBytes(encBuffer, key); + + expect(cryptoFunctionService.aesDecrypt).toBeCalledWith( + expect.toEqualBuffer(encBuffer.dataBytes), + expect.toEqualBuffer(encBuffer.ivBytes), + expect.toEqualBuffer(key.encKey), + "cbc", + ); + + expect(actual).toEqualBuffer(decryptedBytes); + }); + + it("compares macs using CryptoFunctionService", async () => { + const expectedMacData = new Uint8Array( + encBuffer.ivBytes.byteLength + encBuffer.dataBytes.byteLength, + ); + expectedMacData.set(new Uint8Array(encBuffer.ivBytes)); + expectedMacData.set(new Uint8Array(encBuffer.dataBytes), encBuffer.ivBytes.byteLength); + + await encryptService.decryptToBytes(encBuffer, key); + + expect(cryptoFunctionService.hmac).toBeCalledWith( + expect.toEqualBuffer(expectedMacData), + key.macKey, + "sha256", + ); + + expect(cryptoFunctionService.compare).toBeCalledWith( + expect.toEqualBuffer(encBuffer.macBytes), + expect.toEqualBuffer(computedMac), + ); + }); + + it("returns null if macs don't match", async () => { + cryptoFunctionService.compare.mockResolvedValue(false); + + const actual = await encryptService.decryptToBytes(encBuffer, key); + expect(cryptoFunctionService.compare).toHaveBeenCalled(); + expect(cryptoFunctionService.aesDecrypt).not.toHaveBeenCalled(); + expect(actual).toBeNull(); + }); + + it("returns null if mac could not be calculated", async () => { + cryptoFunctionService.hmac.mockResolvedValue(null); + + const actual = await encryptService.decryptToBytes(encBuffer, key); + expect(cryptoFunctionService.hmac).toHaveBeenCalled(); + expect(cryptoFunctionService.aesDecrypt).not.toHaveBeenCalled(); + expect(actual).toBeNull(); + }); + + it("returns null if key is Aes256Cbc but encbuffer is Aes256Cbc_HmacSha256", async () => { + const key = new SymmetricCryptoKey(makeStaticByteArray(32, 0)); + cryptoFunctionService.compare.mockResolvedValue(true); + + const actual = await encryptService.decryptToBytes(encBuffer, key); + + expect(actual).toBeNull(); + expect(cryptoFunctionService.aesDecrypt).not.toHaveBeenCalled(); + }); + + it("returns null if key is Aes256Cbc_HmacSha256 but encbuffer is Aes256Cbc", async () => { + const key = new SymmetricCryptoKey(makeStaticByteArray(64, 0)); + cryptoFunctionService.compare.mockResolvedValue(true); + const buffer = new EncArrayBuffer(makeStaticByteArray(200, EncryptionType.AesCbc256_B64)); + const actual = await encryptService.decryptToBytes(buffer, key); + + expect(actual).toBeNull(); + expect(cryptoFunctionService.aesDecrypt).not.toHaveBeenCalled(); + }); + }); + + describe("decryptToUtf8", () => { + it("throws if no key is provided", () => { + return expect(encryptService.decryptToUtf8(null, null)).rejects.toThrow( + "No key provided for decryption.", + ); + }); + + it("decrypts data with provided key for Aes256Cbc_HmacSha256", async () => { + const key = new SymmetricCryptoKey(makeStaticByteArray(64, 0)); + const encString = new EncString(EncryptionType.AesCbc256_HmacSha256_B64, "data", "iv", "mac"); + cryptoFunctionService.aesDecryptFastParameters.mockReturnValue({ + macData: makeStaticByteArray(32, 0), + macKey: makeStaticByteArray(32, 0), + mac: makeStaticByteArray(32, 0), + } as any); + cryptoFunctionService.hmacFast.mockResolvedValue(makeStaticByteArray(32, 0)); + cryptoFunctionService.compareFast.mockResolvedValue(true); + cryptoFunctionService.aesDecryptFast.mockResolvedValue("data"); + + const actual = await encryptService.decryptToUtf8(encString, key); + expect(actual).toEqual("data"); + expect(cryptoFunctionService.compareFast).toHaveBeenCalledWith( + makeStaticByteArray(32, 0), + makeStaticByteArray(32, 0), + ); + }); + + it("decrypts data with provided key for Aes256Cbc", async () => { + const key = new SymmetricCryptoKey(makeStaticByteArray(32, 0)); + const encString = new EncString(EncryptionType.AesCbc256_B64, "data"); + cryptoFunctionService.aesDecryptFastParameters.mockReturnValue({} as any); + cryptoFunctionService.hmacFast.mockResolvedValue(makeStaticByteArray(32, 0)); + cryptoFunctionService.compareFast.mockResolvedValue(true); + cryptoFunctionService.aesDecryptFast.mockResolvedValue("data"); + + const actual = await encryptService.decryptToUtf8(encString, key); + expect(actual).toEqual("data"); + expect(cryptoFunctionService.compareFast).not.toHaveBeenCalled(); + }); + + it("returns null if key is Aes256Cbc_HmacSha256 but EncString is Aes256Cbc", async () => { + const key = new SymmetricCryptoKey(makeStaticByteArray(64, 0)); + const encString = new EncString(EncryptionType.AesCbc256_B64, "data"); + + const actual = await encryptService.decryptToUtf8(encString, key); + expect(actual).toBeNull(); + expect(logService.error).toHaveBeenCalled(); + }); + + it("returns null if key is Aes256Cbc but encstring is AesCbc256_HmacSha256", async () => { + const key = new SymmetricCryptoKey(makeStaticByteArray(32, 0)); + const encString = new EncString(EncryptionType.AesCbc256_HmacSha256_B64, "data", "iv", "mac"); + + const actual = await encryptService.decryptToUtf8(encString, key); + expect(actual).toBeNull(); + expect(logService.error).toHaveBeenCalled(); + }); + + it("returns null if macs don't match", async () => { + const key = new SymmetricCryptoKey(makeStaticByteArray(64, 0)); + const encString = new EncString(EncryptionType.AesCbc256_HmacSha256_B64, "data", "iv", "mac"); + cryptoFunctionService.aesDecryptFastParameters.mockReturnValue({ + macData: makeStaticByteArray(32, 0), + macKey: makeStaticByteArray(32, 0), + mac: makeStaticByteArray(32, 0), + } as any); + cryptoFunctionService.hmacFast.mockResolvedValue(makeStaticByteArray(32, 0)); + cryptoFunctionService.compareFast.mockResolvedValue(false); + cryptoFunctionService.aesDecryptFast.mockResolvedValue("data"); + + const actual = await encryptService.decryptToUtf8(encString, key); + expect(actual).toBeNull(); + }); + }); + + describe("decryptToUtf8", () => { + it("throws if no key is provided", () => { + return expect(encryptService.decryptToUtf8(null, null)).rejects.toThrow( + "No key provided for decryption.", + ); + }); + it("returns null if key is mac key but encstring has no mac", async () => { + const key = new SymmetricCryptoKey( + makeStaticByteArray(64, 0), + EncryptionType.AesCbc256_HmacSha256_B64, + ); + const encString = new EncString(EncryptionType.AesCbc256_B64, "data"); + + const actual = await encryptService.decryptToUtf8(encString, key); + expect(actual).toBeNull(); + expect(logService.error).toHaveBeenCalled(); + }); + }); + + describe("rsa", () => { + const data = makeStaticByteArray(10, 100); + const encryptedData = makeStaticByteArray(10, 150); + const publicKey = makeStaticByteArray(10, 200); + const privateKey = makeStaticByteArray(10, 250); + const encString = makeEncString(encryptedData); + + function makeEncString(data: Uint8Array): EncString { + return new EncString(EncryptionType.Rsa2048_OaepSha1_B64, Utils.fromBufferToB64(data)); + } + + describe("rsaEncrypt", () => { + it("throws if no data is provided", () => { + return expect(encryptService.rsaEncrypt(null, publicKey)).rejects.toThrow("No data"); + }); + + it("throws if no public key is provided", () => { + return expect(encryptService.rsaEncrypt(data, null)).rejects.toThrow("No public key"); + }); + + it("encrypts data with provided key", async () => { + cryptoFunctionService.rsaEncrypt.mockResolvedValue(encryptedData); + + const actual = await encryptService.rsaEncrypt(data, publicKey); + + expect(cryptoFunctionService.rsaEncrypt).toBeCalledWith( + expect.toEqualBuffer(data), + expect.toEqualBuffer(publicKey), + "sha1", + ); + + expect(actual).toEqual(encString); + expect(actual.dataBytes).toEqualBuffer(encryptedData); + }); + }); + + describe("rsaDecrypt", () => { + it("throws if no data is provided", () => { + return expect(encryptService.rsaDecrypt(null, privateKey)).rejects.toThrow("No data"); + }); + + it("throws if no private key is provided", () => { + return expect(encryptService.rsaDecrypt(encString, null)).rejects.toThrow("No private key"); + }); + + it.each([EncryptionType.AesCbc256_B64, EncryptionType.AesCbc256_HmacSha256_B64])( + "throws if encryption type is %s", + async (encType) => { + encString.encryptionType = encType; + + await expect(encryptService.rsaDecrypt(encString, privateKey)).rejects.toThrow( + "Invalid encryption type", + ); + }, + ); + + it("decrypts data with provided key", async () => { + cryptoFunctionService.rsaDecrypt.mockResolvedValue(data); + + const actual = await encryptService.rsaDecrypt(makeEncString(data), privateKey); + + expect(cryptoFunctionService.rsaDecrypt).toBeCalledWith( + expect.toEqualBuffer(data), + expect.toEqualBuffer(privateKey), + "sha1", + ); + + expect(actual).toEqualBuffer(data); + }); + }); + }); + + describe("hash", () => { + it("hashes a string and returns b64", async () => { + cryptoFunctionService.hash.mockResolvedValue(Uint8Array.from([1, 2, 3])); + expect(await encryptService.hash("test", "sha256")).toEqual("AQID"); + expect(cryptoFunctionService.hash).toHaveBeenCalledWith("test", "sha256"); + }); + }); + + describe("decryptItems", () => { + it("returns empty array if no items are provided", async () => { + const key = mock(); + const actual = await encryptService.decryptItems(null, key); + expect(actual).toEqual([]); + }); + + it("returns items decrypted with provided key", async () => { + const key = mock(); + const decryptable = { + decrypt: jest.fn().mockResolvedValue("decrypted"), + }; + const items = [decryptable]; + const actual = await encryptService.decryptItems(items as any, key); + expect(actual).toEqual(["decrypted"]); + expect(decryptable.decrypt).toHaveBeenCalledWith(key); + }); + }); +}); diff --git a/libs/common/src/platform/services/cryptography/encrypt.worker.ts b/libs/common/src/key-management/crypto/services/encrypt.worker.ts similarity index 71% rename from libs/common/src/platform/services/cryptography/encrypt.worker.ts rename to libs/common/src/key-management/crypto/services/encrypt.worker.ts index a293e1c6bb0..84ffcf56934 100644 --- a/libs/common/src/platform/services/cryptography/encrypt.worker.ts +++ b/libs/common/src/key-management/crypto/services/encrypt.worker.ts @@ -2,14 +2,14 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; -import { Decryptable } from "../../interfaces/decryptable.interface"; -import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key"; -import { ConsoleLogService } from "../console-log.service"; -import { ContainerService } from "../container.service"; -import { WebCryptoFunctionService } from "../web-crypto-function.service"; +import { Decryptable } from "@bitwarden/common/platform/interfaces/decryptable.interface"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; +import { ContainerService } from "@bitwarden/common/platform/services/container.service"; +import { getClassInitializer } from "@bitwarden/common/platform/services/cryptography/get-class-initializer"; +import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service"; import { EncryptServiceImplementation } from "./encrypt.service.implementation"; -import { getClassInitializer } from "./get-class-initializer"; const workerApi: Worker = self as any; diff --git a/libs/common/src/platform/services/cryptography/fallback-bulk-encrypt.service.ts b/libs/common/src/key-management/crypto/services/fallback-bulk-encrypt.service.ts similarity index 68% rename from libs/common/src/platform/services/cryptography/fallback-bulk-encrypt.service.ts rename to libs/common/src/key-management/crypto/services/fallback-bulk-encrypt.service.ts index 7a4fd8f3c1d..80fdd27895d 100644 --- a/libs/common/src/platform/services/cryptography/fallback-bulk-encrypt.service.ts +++ b/libs/common/src/key-management/crypto/services/fallback-bulk-encrypt.service.ts @@ -1,10 +1,11 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { BulkEncryptService } from "../../abstractions/bulk-encrypt.service"; -import { EncryptService } from "../../abstractions/encrypt.service"; -import { Decryptable } from "../../interfaces/decryptable.interface"; -import { InitializerMetadata } from "../../interfaces/initializer-metadata.interface"; -import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key"; +import { BulkEncryptService } from "@bitwarden/common/key-management/crypto/abstractions/bulk-encrypt.service"; +import { Decryptable } from "@bitwarden/common/platform/interfaces/decryptable.interface"; +import { InitializerMetadata } from "@bitwarden/common/platform/interfaces/initializer-metadata.interface"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; + +import { EncryptService } from "../abstractions/encrypt.service"; /** * @deprecated For the feature flag from PM-4154, remove once feature is rolled out diff --git a/libs/common/src/platform/services/cryptography/multithread-encrypt.service.implementation.ts b/libs/common/src/key-management/crypto/services/multithread-encrypt.service.implementation.ts similarity index 81% rename from libs/common/src/platform/services/cryptography/multithread-encrypt.service.implementation.ts rename to libs/common/src/key-management/crypto/services/multithread-encrypt.service.implementation.ts index 100dcf152e6..0bf96851563 100644 --- a/libs/common/src/platform/services/cryptography/multithread-encrypt.service.implementation.ts +++ b/libs/common/src/key-management/crypto/services/multithread-encrypt.service.implementation.ts @@ -3,13 +3,13 @@ import { defaultIfEmpty, filter, firstValueFrom, fromEvent, map, Subject, takeUntil } from "rxjs"; import { Jsonify } from "type-fest"; -import { Utils } from "../../../platform/misc/utils"; -import { Decryptable } from "../../interfaces/decryptable.interface"; -import { InitializerMetadata } from "../../interfaces/initializer-metadata.interface"; -import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key"; +import { Decryptable } from "@bitwarden/common/platform/interfaces/decryptable.interface"; +import { InitializerMetadata } from "@bitwarden/common/platform/interfaces/initializer-metadata.interface"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { getClassInitializer } from "@bitwarden/common/platform/services/cryptography/get-class-initializer"; import { EncryptServiceImplementation } from "./encrypt.service.implementation"; -import { getClassInitializer } from "./get-class-initializer"; // TTL (time to live) is not strictly required but avoids tying up memory resources if inactive const workerTTL = 3 * 60000; // 3 minutes @@ -40,7 +40,7 @@ export class MultithreadEncryptServiceImplementation extends EncryptServiceImple this.worker ??= new Worker( new URL( /* webpackChunkName: 'encrypt-worker' */ - "@bitwarden/common/platform/services/cryptography/encrypt.worker.ts", + "@bitwarden/common/key-management/crypto/services/encrypt.worker.ts", import.meta.url, ), ); diff --git a/libs/common/src/auth/abstractions/device-trust.service.abstraction.ts b/libs/common/src/key-management/device-trust/abstractions/device-trust.service.abstraction.ts similarity index 60% rename from libs/common/src/auth/abstractions/device-trust.service.abstraction.ts rename to libs/common/src/key-management/device-trust/abstractions/device-trust.service.abstraction.ts index 13963b03bea..882625fa231 100644 --- a/libs/common/src/auth/abstractions/device-trust.service.abstraction.ts +++ b/libs/common/src/key-management/device-trust/abstractions/device-trust.service.abstraction.ts @@ -2,14 +2,30 @@ // @ts-strict-ignore import { Observable } from "rxjs"; -import { EncString } from "../../platform/models/domain/enc-string"; -import { UserId } from "../../types/guid"; -import { DeviceKey, UserKey } from "../../types/key"; - -import { DeviceResponse } from "./devices/responses/device.response"; +import { DeviceResponse } from "../../../auth/abstractions/devices/responses/device.response"; +import { EncString } from "../../../platform/models/domain/enc-string"; +import { UserId } from "../../../types/guid"; +import { DeviceKey, UserKey } from "../../../types/key"; export abstract class DeviceTrustServiceAbstraction { + /** + * @deprecated - use supportsDeviceTrustByUserId instead as active user state is being deprecated + * by Platform + * @description Checks if the device trust feature is supported for the active user. + */ supportsDeviceTrust$: Observable; + + /** + * Emits when a device has been trusted. This emission is specifically for the purpose of notifying + * the consuming component to display a toast informing the user the device has been trusted. + */ + deviceTrusted$: Observable; + + /** + * @description Checks if the device trust feature is supported for the given user. + */ + supportsDeviceTrustByUserId$: (userId: UserId) => Observable; + /** * @description Retrieves the users choice to trust the device which can only happen after decryption * Note: this value should only be used once and then reset diff --git a/libs/common/src/auth/services/device-trust.service.implementation.ts b/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts similarity index 82% rename from libs/common/src/auth/services/device-trust.service.implementation.ts rename to libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts index 409150552ad..579fe9360a6 100644 --- a/libs/common/src/auth/services/device-trust.service.implementation.ts +++ b/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts @@ -1,34 +1,34 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { firstValueFrom, map, Observable } from "rxjs"; +import { firstValueFrom, map, Observable, Subject } from "rxjs"; import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; +import { KeyService } from "@bitwarden/key-management"; -import { KeyService } from "../../../../key-management/src/abstractions/key.service"; -import { AppIdService } from "../../platform/abstractions/app-id.service"; -import { ConfigService } from "../../platform/abstractions/config/config.service"; -import { CryptoFunctionService } from "../../platform/abstractions/crypto-function.service"; -import { EncryptService } from "../../platform/abstractions/encrypt.service"; -import { I18nService } from "../../platform/abstractions/i18n.service"; -import { KeyGenerationService } from "../../platform/abstractions/key-generation.service"; -import { LogService } from "../../platform/abstractions/log.service"; -import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service"; -import { AbstractStorageService } from "../../platform/abstractions/storage.service"; -import { StorageLocation } from "../../platform/enums"; -import { EncString } from "../../platform/models/domain/enc-string"; -import { StorageOptions } from "../../platform/models/domain/storage-options"; -import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key"; -import { DEVICE_TRUST_DISK_LOCAL, StateProvider, UserKeyDefinition } from "../../platform/state"; -import { UserId } from "../../types/guid"; -import { UserKey, DeviceKey } from "../../types/key"; -import { DeviceTrustServiceAbstraction } from "../abstractions/device-trust.service.abstraction"; -import { DeviceResponse } from "../abstractions/devices/responses/device.response"; -import { DevicesApiServiceAbstraction } from "../abstractions/devices-api.service.abstraction"; -import { SecretVerificationRequest } from "../models/request/secret-verification.request"; +import { DeviceResponse } from "../../../auth/abstractions/devices/responses/device.response"; +import { DevicesApiServiceAbstraction } from "../../../auth/abstractions/devices-api.service.abstraction"; +import { SecretVerificationRequest } from "../../../auth/models/request/secret-verification.request"; import { DeviceKeysUpdateRequest, UpdateDevicesTrustRequest, -} from "../models/request/update-devices-trust.request"; +} from "../../../auth/models/request/update-devices-trust.request"; +import { AppIdService } from "../../../platform/abstractions/app-id.service"; +import { ConfigService } from "../../../platform/abstractions/config/config.service"; +import { CryptoFunctionService } from "../../../platform/abstractions/crypto-function.service"; +import { I18nService } from "../../../platform/abstractions/i18n.service"; +import { KeyGenerationService } from "../../../platform/abstractions/key-generation.service"; +import { LogService } from "../../../platform/abstractions/log.service"; +import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service"; +import { AbstractStorageService } from "../../../platform/abstractions/storage.service"; +import { StorageLocation } from "../../../platform/enums"; +import { EncString } from "../../../platform/models/domain/enc-string"; +import { StorageOptions } from "../../../platform/models/domain/storage-options"; +import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; +import { DEVICE_TRUST_DISK_LOCAL, StateProvider, UserKeyDefinition } from "../../../platform/state"; +import { UserId } from "../../../types/guid"; +import { UserKey, DeviceKey } from "../../../types/key"; +import { EncryptService } from "../../crypto/abstractions/encrypt.service"; +import { DeviceTrustServiceAbstraction } from "../abstractions/device-trust.service.abstraction"; /** Uses disk storage so that the device key can persist after log out and tab removal. */ export const DEVICE_KEY = new UserKeyDefinition( @@ -63,6 +63,10 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { supportsDeviceTrust$: Observable; + // Observable emission is used to trigger a toast in consuming components + private deviceTrustedSubject = new Subject(); + deviceTrusted$ = this.deviceTrustedSubject.asObservable(); + constructor( private keyGenerationService: KeyGenerationService, private cryptoFunctionService: CryptoFunctionService, @@ -79,7 +83,17 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { private configService: ConfigService, ) { this.supportsDeviceTrust$ = this.userDecryptionOptionsService.userDecryptionOptions$.pipe( - map((options) => options?.trustedDeviceOption != null ?? false), + map((options) => { + return options?.trustedDeviceOption != null ?? false; + }), + ); + } + + supportsDeviceTrustByUserId$(userId: UserId): Observable { + return this.userDecryptionOptionsService.userDecryptionOptionsById$(userId).pipe( + map((options) => { + return options?.trustedDeviceOption != null ?? false; + }), ); } @@ -167,7 +181,8 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { // store device key in local/secure storage if enc keys posted to server successfully await this.setDeviceKey(userId, deviceKey); - this.platformUtilsService.showToast("success", null, this.i18nService.t("deviceTrusted")); + // This emission will be picked up by consuming components to handle displaying a toast to the user + this.deviceTrustedSubject.next(); return deviceResponse; } @@ -335,6 +350,8 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { ); return new SymmetricCryptoKey(userKey) as UserKey; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // If either decryption effort fails, we want to remove the device key this.logService.error("Failed to decrypt using device key. Removing device key."); diff --git a/libs/common/src/auth/services/device-trust.service.spec.ts b/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts similarity index 88% rename from libs/common/src/auth/services/device-trust.service.spec.ts rename to libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts index 66a91a693e5..1893b097ec6 100644 --- a/libs/common/src/auth/services/device-trust.service.spec.ts +++ b/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts @@ -1,36 +1,40 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { matches, mock } from "jest-mock-extended"; -import { BehaviorSubject, of } from "rxjs"; +import { BehaviorSubject, firstValueFrom, of } from "rxjs"; -import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; +import { + UserDecryptionOptionsServiceAbstraction, + UserDecryptionOptions, +} from "@bitwarden/auth/common"; +import { KeyService } from "@bitwarden/key-management"; -import { UserDecryptionOptions } from "../../../../auth/src/common/models/domain/user-decryption-options"; -import { KeyService } from "../../../../key-management/src/abstractions/key.service"; -import { FakeAccountService, mockAccountServiceWith } from "../../../spec/fake-account-service"; -import { FakeActiveUserState } from "../../../spec/fake-state"; -import { FakeStateProvider } from "../../../spec/fake-state-provider"; -import { DeviceType } from "../../enums"; -import { AppIdService } from "../../platform/abstractions/app-id.service"; -import { ConfigService } from "../../platform/abstractions/config/config.service"; -import { CryptoFunctionService } from "../../platform/abstractions/crypto-function.service"; -import { EncryptService } from "../../platform/abstractions/encrypt.service"; -import { I18nService } from "../../platform/abstractions/i18n.service"; -import { KeyGenerationService } from "../../platform/abstractions/key-generation.service"; -import { LogService } from "../../platform/abstractions/log.service"; -import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service"; -import { AbstractStorageService } from "../../platform/abstractions/storage.service"; -import { StorageLocation } from "../../platform/enums"; -import { EncryptionType } from "../../platform/enums/encryption-type.enum"; -import { Utils } from "../../platform/misc/utils"; -import { EncString } from "../../platform/models/domain/enc-string"; -import { StorageOptions } from "../../platform/models/domain/storage-options"; -import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key"; -import { CsprngArray } from "../../types/csprng"; -import { UserId } from "../../types/guid"; -import { DeviceKey, UserKey } from "../../types/key"; -import { DeviceResponse } from "../abstractions/devices/responses/device.response"; -import { DevicesApiServiceAbstraction } from "../abstractions/devices-api.service.abstraction"; -import { UpdateDevicesTrustRequest } from "../models/request/update-devices-trust.request"; -import { ProtectedDeviceResponse } from "../models/response/protected-device.response"; +import { FakeAccountService, mockAccountServiceWith } from "../../../../spec/fake-account-service"; +import { FakeActiveUserState } from "../../../../spec/fake-state"; +import { FakeStateProvider } from "../../../../spec/fake-state-provider"; +import { DeviceResponse } from "../../../auth/abstractions/devices/responses/device.response"; +import { DevicesApiServiceAbstraction } from "../../../auth/abstractions/devices-api.service.abstraction"; +import { UpdateDevicesTrustRequest } from "../../../auth/models/request/update-devices-trust.request"; +import { ProtectedDeviceResponse } from "../../../auth/models/response/protected-device.response"; +import { DeviceType } from "../../../enums"; +import { AppIdService } from "../../../platform/abstractions/app-id.service"; +import { ConfigService } from "../../../platform/abstractions/config/config.service"; +import { CryptoFunctionService } from "../../../platform/abstractions/crypto-function.service"; +import { I18nService } from "../../../platform/abstractions/i18n.service"; +import { KeyGenerationService } from "../../../platform/abstractions/key-generation.service"; +import { LogService } from "../../../platform/abstractions/log.service"; +import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service"; +import { AbstractStorageService } from "../../../platform/abstractions/storage.service"; +import { StorageLocation } from "../../../platform/enums"; +import { EncryptionType } from "../../../platform/enums/encryption-type.enum"; +import { Utils } from "../../../platform/misc/utils"; +import { EncString } from "../../../platform/models/domain/enc-string"; +import { StorageOptions } from "../../../platform/models/domain/storage-options"; +import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; +import { CsprngArray } from "../../../types/csprng"; +import { UserId } from "../../../types/guid"; +import { DeviceKey, UserKey } from "../../../types/key"; +import { EncryptService } from "../../crypto/abstractions/encrypt.service"; import { SHOULD_TRUST_DEVICE, @@ -70,17 +74,56 @@ describe("deviceTrustService", () => { userId: mockUserId, }; + let userDecryptionOptions: UserDecryptionOptions; + beforeEach(() => { jest.clearAllMocks(); const supportsSecureStorage = false; // default to false; tests will override as needed // By default all the tests will have a mocked active user in state provider. deviceTrustService = createDeviceTrustService(mockUserId, supportsSecureStorage); + + userDecryptionOptions = new UserDecryptionOptions(); }); it("instantiates", () => { expect(deviceTrustService).not.toBeFalsy(); }); + describe("supportsDeviceTrustByUserId$", () => { + it("returns true when the user has a non-null trusted device decryption option", async () => { + // Arrange + userDecryptionOptions.trustedDeviceOption = { + hasAdminApproval: false, + hasLoginApprovingDevice: false, + hasManageResetPasswordPermission: false, + isTdeOffboarding: false, + }; + + userDecryptionOptionsService.userDecryptionOptionsById$.mockReturnValue( + new BehaviorSubject(userDecryptionOptions), + ); + + const result = await firstValueFrom( + deviceTrustService.supportsDeviceTrustByUserId$(mockUserId), + ); + expect(result).toBe(true); + }); + + it("returns false when the user has a null trusted device decryption option", async () => { + // Arrange + userDecryptionOptions.trustedDeviceOption = null; + + userDecryptionOptionsService.userDecryptionOptionsById$.mockReturnValue( + new BehaviorSubject(userDecryptionOptions), + ); + + const result = await firstValueFrom( + deviceTrustService.supportsDeviceTrustByUserId$(mockUserId), + ); + expect(result).toBe(false); + }); + }); + describe("User Trust Device Choice For Decryption", () => { describe("getShouldTrustDevice", () => { it("gets the user trust device choice for decryption", async () => { diff --git a/libs/common/src/auth/abstractions/key-connector.service.ts b/libs/common/src/key-management/key-connector/abstractions/key-connector.service.ts similarity index 79% rename from libs/common/src/auth/abstractions/key-connector.service.ts rename to libs/common/src/key-management/key-connector/abstractions/key-connector.service.ts index 9bcca1f5619..b88f7bc7cd6 100644 --- a/libs/common/src/auth/abstractions/key-connector.service.ts +++ b/libs/common/src/key-management/key-connector/abstractions/key-connector.service.ts @@ -1,8 +1,8 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Organization } from "../../admin-console/models/domain/organization"; -import { UserId } from "../../types/guid"; -import { IdentityTokenResponse } from "../models/response/identity-token.response"; +import { Organization } from "../../../admin-console/models/domain/organization"; +import { IdentityTokenResponse } from "../../../auth/models/response/identity-token.response"; +import { UserId } from "../../../types/guid"; export abstract class KeyConnectorService { setMasterKeyFromUrl: (url: string, userId: UserId) => Promise; diff --git a/libs/common/src/auth/models/request/key-connector-user-key.request.ts b/libs/common/src/key-management/key-connector/models/key-connector-user-key.request.ts similarity index 100% rename from libs/common/src/auth/models/request/key-connector-user-key.request.ts rename to libs/common/src/key-management/key-connector/models/key-connector-user-key.request.ts diff --git a/libs/common/src/auth/models/request/set-key-connector-key.request.ts b/libs/common/src/key-management/key-connector/models/set-key-connector-key.request.ts similarity index 100% rename from libs/common/src/auth/models/request/set-key-connector-key.request.ts rename to libs/common/src/key-management/key-connector/models/set-key-connector-key.request.ts diff --git a/libs/common/src/auth/services/key-connector.service.spec.ts b/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts similarity index 86% rename from libs/common/src/auth/services/key-connector.service.spec.ts rename to libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts index b1bf87693c1..fd3ce0c4777 100644 --- a/libs/common/src/auth/services/key-connector.service.spec.ts +++ b/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts @@ -1,28 +1,30 @@ import { mock } from "jest-mock-extended"; +import { of } from "rxjs"; -import { KeyService } from "../../../../key-management/src/abstractions/key.service"; -import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from "../../../spec"; -import { ApiService } from "../../abstractions/api.service"; -import { OrganizationService } from "../../admin-console/abstractions/organization/organization.service.abstraction"; -import { OrganizationData } from "../../admin-console/models/data/organization.data"; -import { Organization } from "../../admin-console/models/domain/organization"; -import { ProfileOrganizationResponse } from "../../admin-console/models/response/profile-organization.response"; -import { LogService } from "../../platform/abstractions/log.service"; -import { Utils } from "../../platform/misc/utils"; -import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key"; -import { KeyGenerationService } from "../../platform/services/key-generation.service"; -import { OrganizationId, UserId } from "../../types/guid"; -import { MasterKey } from "../../types/key"; -import { KeyConnectorUserKeyRequest } from "../models/request/key-connector-user-key.request"; -import { KeyConnectorUserKeyResponse } from "../models/response/key-connector-user-key.response"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { KeyService } from "@bitwarden/key-management"; + +import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from "../../../../spec"; +import { ApiService } from "../../../abstractions/api.service"; +import { OrganizationData } from "../../../admin-console/models/data/organization.data"; +import { Organization } from "../../../admin-console/models/domain/organization"; +import { ProfileOrganizationResponse } from "../../../admin-console/models/response/profile-organization.response"; +import { KeyConnectorUserKeyResponse } from "../../../auth/models/response/key-connector-user-key.response"; +import { TokenService } from "../../../auth/services/token.service"; +import { LogService } from "../../../platform/abstractions/log.service"; +import { Utils } from "../../../platform/misc/utils"; +import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; +import { KeyGenerationService } from "../../../platform/services/key-generation.service"; +import { OrganizationId, UserId } from "../../../types/guid"; +import { MasterKey } from "../../../types/key"; +import { FakeMasterPasswordService } from "../../master-password/services/fake-master-password.service"; +import { KeyConnectorUserKeyRequest } from "../models/key-connector-user-key.request"; import { USES_KEY_CONNECTOR, CONVERT_ACCOUNT_TO_KEY_CONNECTOR, KeyConnectorService, } from "./key-connector.service"; -import { FakeMasterPasswordService } from "./master-password/fake-master-password.service"; -import { TokenService } from "./token.service"; describe("KeyConnectorService", () => { let keyConnectorService: KeyConnectorService; @@ -93,7 +95,7 @@ describe("KeyConnectorService", () => { organizationData(true, false, "https://key-connector-url.com", 2, false), organizationData(true, true, "https://other-url.com", 2, false), ]; - organizationService.getAll.mockResolvedValue(orgs); + organizationService.organizations$.mockReturnValue(of(orgs)); // Act const result = await keyConnectorService.getManagingOrganization(); @@ -108,7 +110,7 @@ describe("KeyConnectorService", () => { organizationData(true, false, "https://key-connector-url.com", 2, false), organizationData(false, false, "https://key-connector-url.com", 2, false), ]; - organizationService.getAll.mockResolvedValue(orgs); + organizationService.organizations$.mockReturnValue(of(orgs)); // Act const result = await keyConnectorService.getManagingOrganization(); @@ -123,7 +125,7 @@ describe("KeyConnectorService", () => { organizationData(true, true, "https://key-connector-url.com", 0, false), organizationData(true, true, "https://key-connector-url.com", 1, false), ]; - organizationService.getAll.mockResolvedValue(orgs); + organizationService.organizations$.mockReturnValue(of(orgs)); // Act const result = await keyConnectorService.getManagingOrganization(); @@ -138,7 +140,7 @@ describe("KeyConnectorService", () => { organizationData(true, true, "https://key-connector-url.com", 2, true), organizationData(false, true, "https://key-connector-url.com", 2, true), ]; - organizationService.getAll.mockResolvedValue(orgs); + organizationService.organizations$.mockReturnValue(of(orgs)); // Act const result = await keyConnectorService.getManagingOrganization(); @@ -179,7 +181,7 @@ describe("KeyConnectorService", () => { // create organization object const data = organizationData(true, true, "https://key-connector-url.com", 2, false); - organizationService.getAll.mockResolvedValue([data]); + organizationService.organizations$.mockReturnValue(of([data])); // uses KeyConnector const state = stateProvider.activeUser.getFake(USES_KEY_CONNECTOR); @@ -193,7 +195,7 @@ describe("KeyConnectorService", () => { it("should return false if the user does not need migration", async () => { tokenService.getIsExternal.mockResolvedValue(false); const data = organizationData(false, false, "https://key-connector-url.com", 2, false); - organizationService.getAll.mockResolvedValue([data]); + organizationService.organizations$.mockReturnValue(of([data])); const state = stateProvider.activeUser.getFake(USES_KEY_CONNECTOR); state.nextState(true); @@ -273,7 +275,7 @@ describe("KeyConnectorService", () => { const masterKey = getMockMasterKey(); const keyConnectorRequest = new KeyConnectorUserKeyRequest(masterKey.encKeyB64); const error = new Error("Failed to post user key to key connector"); - organizationService.getAll.mockResolvedValue([organization]); + organizationService.organizations$.mockReturnValue(of([organization])); masterPasswordService.masterKeySubject.next(masterKey); jest.spyOn(keyConnectorService, "getManagingOrganization").mockResolvedValue(organization); @@ -364,6 +366,7 @@ describe("KeyConnectorService", () => { accessSecretsManager: false, limitCollectionCreation: true, limitCollectionDeletion: true, + limitItemDeletion: true, allowAdminAccessToAllCollectionItems: true, flexibleCollections: false, object: "profileOrganization", diff --git a/libs/common/src/auth/services/key-connector.service.ts b/libs/common/src/key-management/key-connector/services/key-connector.service.ts similarity index 82% rename from libs/common/src/auth/services/key-connector.service.ts rename to libs/common/src/key-management/key-connector/services/key-connector.service.ts index f798413162e..91b8e9100ac 100644 --- a/libs/common/src/auth/services/key-connector.service.ts +++ b/libs/common/src/key-management/key-connector/services/key-connector.service.ts @@ -11,30 +11,30 @@ import { KdfType, } from "@bitwarden/key-management"; -import { ApiService } from "../../abstractions/api.service"; -import { OrganizationService } from "../../admin-console/abstractions/organization/organization.service.abstraction"; -import { OrganizationUserType } from "../../admin-console/enums"; -import { Organization } from "../../admin-console/models/domain/organization"; -import { KeysRequest } from "../../models/request/keys.request"; -import { KeyGenerationService } from "../../platform/abstractions/key-generation.service"; -import { LogService } from "../../platform/abstractions/log.service"; -import { Utils } from "../../platform/misc/utils"; -import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key"; +import { ApiService } from "../../../abstractions/api.service"; +import { OrganizationService } from "../../../admin-console/abstractions/organization/organization.service.abstraction"; +import { OrganizationUserType } from "../../../admin-console/enums"; +import { Organization } from "../../../admin-console/models/domain/organization"; +import { AccountService } from "../../../auth/abstractions/account.service"; +import { TokenService } from "../../../auth/abstractions/token.service"; +import { IdentityTokenResponse } from "../../../auth/models/response/identity-token.response"; +import { KeysRequest } from "../../../models/request/keys.request"; +import { KeyGenerationService } from "../../../platform/abstractions/key-generation.service"; +import { LogService } from "../../../platform/abstractions/log.service"; +import { Utils } from "../../../platform/misc/utils"; +import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { ActiveUserState, KEY_CONNECTOR_DISK, StateProvider, UserKeyDefinition, -} from "../../platform/state"; -import { UserId } from "../../types/guid"; -import { MasterKey } from "../../types/key"; -import { AccountService } from "../abstractions/account.service"; +} from "../../../platform/state"; +import { UserId } from "../../../types/guid"; +import { MasterKey } from "../../../types/key"; +import { InternalMasterPasswordServiceAbstraction } from "../../master-password/abstractions/master-password.service.abstraction"; import { KeyConnectorService as KeyConnectorServiceAbstraction } from "../abstractions/key-connector.service"; -import { InternalMasterPasswordServiceAbstraction } from "../abstractions/master-password.service.abstraction"; -import { TokenService } from "../abstractions/token.service"; -import { KeyConnectorUserKeyRequest } from "../models/request/key-connector-user-key.request"; -import { SetKeyConnectorKeyRequest } from "../models/request/set-key-connector-key.request"; -import { IdentityTokenResponse } from "../models/response/identity-token.response"; +import { KeyConnectorUserKeyRequest } from "../models/key-connector-user-key.request"; +import { SetKeyConnectorKeyRequest } from "../models/set-key-connector-key.request"; export const USES_KEY_CONNECTOR = new UserKeyDefinition( KEY_CONNECTOR_DISK, @@ -122,7 +122,7 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction { } async getManagingOrganization(userId?: UserId): Promise { - const orgs = await this.organizationService.getAll(userId); + const orgs = await firstValueFrom(this.organizationService.organizations$(userId)); return orgs.find( (o) => o.keyConnectorEnabled && diff --git a/libs/common/src/auth/abstractions/master-password.service.abstraction.ts b/libs/common/src/key-management/master-password/abstractions/master-password.service.abstraction.ts similarity index 92% rename from libs/common/src/auth/abstractions/master-password.service.abstraction.ts rename to libs/common/src/key-management/master-password/abstractions/master-password.service.abstraction.ts index c3a0f135a06..221ce8ed6ef 100644 --- a/libs/common/src/auth/abstractions/master-password.service.abstraction.ts +++ b/libs/common/src/key-management/master-password/abstractions/master-password.service.abstraction.ts @@ -1,9 +1,9 @@ import { Observable } from "rxjs"; -import { EncString } from "../../platform/models/domain/enc-string"; -import { UserId } from "../../types/guid"; -import { MasterKey, UserKey } from "../../types/key"; -import { ForceSetPasswordReason } from "../models/domain/force-set-password-reason"; +import { ForceSetPasswordReason } from "../../../auth/models/domain/force-set-password-reason"; +import { EncString } from "../../../platform/models/domain/enc-string"; +import { UserId } from "../../../types/guid"; +import { MasterKey, UserKey } from "../../../types/key"; export abstract class MasterPasswordServiceAbstraction { /** diff --git a/libs/common/src/auth/services/master-password/fake-master-password.service.ts b/libs/common/src/key-management/master-password/services/fake-master-password.service.ts similarity index 88% rename from libs/common/src/auth/services/master-password/fake-master-password.service.ts rename to libs/common/src/key-management/master-password/services/fake-master-password.service.ts index 2809659cb01..73611bbdc1b 100644 --- a/libs/common/src/auth/services/master-password/fake-master-password.service.ts +++ b/libs/common/src/key-management/master-password/services/fake-master-password.service.ts @@ -3,19 +3,19 @@ import { mock } from "jest-mock-extended"; import { ReplaySubject, Observable } from "rxjs"; +import { ForceSetPasswordReason } from "../../../auth/models/domain/force-set-password-reason"; import { EncString } from "../../../platform/models/domain/enc-string"; import { UserId } from "../../../types/guid"; import { MasterKey, UserKey } from "../../../types/key"; -import { InternalMasterPasswordServiceAbstraction } from "../../abstractions/master-password.service.abstraction"; -import { ForceSetPasswordReason } from "../../models/domain/force-set-password-reason"; +import { InternalMasterPasswordServiceAbstraction } from "../abstractions/master-password.service.abstraction"; export class FakeMasterPasswordService implements InternalMasterPasswordServiceAbstraction { mock = mock(); // eslint-disable-next-line rxjs/no-exposed-subjects -- test class - masterKeySubject = new ReplaySubject(1); + masterKeySubject = new ReplaySubject(1); // eslint-disable-next-line rxjs/no-exposed-subjects -- test class - masterKeyHashSubject = new ReplaySubject(1); + masterKeyHashSubject = new ReplaySubject(1); // eslint-disable-next-line rxjs/no-exposed-subjects -- test class forceSetPasswordReasonSubject = new ReplaySubject(1); diff --git a/libs/common/src/auth/services/master-password/master-password.service.ts b/libs/common/src/key-management/master-password/services/master-password.service.ts similarity index 94% rename from libs/common/src/auth/services/master-password/master-password.service.ts rename to libs/common/src/key-management/master-password/services/master-password.service.ts index 3ac00adf8e5..72987b13827 100644 --- a/libs/common/src/auth/services/master-password/master-password.service.ts +++ b/libs/common/src/key-management/master-password/services/master-password.service.ts @@ -2,10 +2,9 @@ // @ts-strict-ignore import { firstValueFrom, map, Observable } from "rxjs"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; - -import { EncryptService } from "../../../platform/abstractions/encrypt.service"; +import { ForceSetPasswordReason } from "../../../auth/models/domain/force-set-password-reason"; import { KeyGenerationService } from "../../../platform/abstractions/key-generation.service"; +import { LogService } from "../../../platform/abstractions/log.service"; import { StateService } from "../../../platform/abstractions/state.service"; import { EncryptionType } from "../../../platform/enums"; import { EncryptedString, EncString } from "../../../platform/models/domain/enc-string"; @@ -18,8 +17,8 @@ import { } from "../../../platform/state"; import { UserId } from "../../../types/guid"; import { MasterKey, UserKey } from "../../../types/key"; -import { InternalMasterPasswordServiceAbstraction } from "../../abstractions/master-password.service.abstraction"; -import { ForceSetPasswordReason } from "../../models/domain/force-set-password-reason"; +import { EncryptService } from "../../crypto/abstractions/encrypt.service"; +import { InternalMasterPasswordServiceAbstraction } from "../abstractions/master-password.service.abstraction"; /** Memory since master key shouldn't be available on lock */ const MASTER_KEY = new UserKeyDefinition(MASTER_PASSWORD_MEMORY, "masterKey", { diff --git a/libs/common/src/key-management/services/default-process-reload.service.ts b/libs/common/src/key-management/services/default-process-reload.service.ts index 8c1d1117c89..860dac54855 100644 --- a/libs/common/src/key-management/services/default-process-reload.service.ts +++ b/libs/common/src/key-management/services/default-process-reload.service.ts @@ -2,16 +2,18 @@ // @ts-strict-ignore import { firstValueFrom, map, timeout } from "rxjs"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { BiometricStateService } from "@bitwarden/key-management"; -import { PinServiceAbstraction } from "../../../../auth/src/common/abstractions"; -import { VaultTimeoutSettingsService } from "../../abstractions/vault-timeout/vault-timeout-settings.service"; import { AccountService } from "../../auth/abstractions/account.service"; import { AuthService } from "../../auth/abstractions/auth.service"; import { AuthenticationStatus } from "../../auth/enums/authentication-status"; -import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; +import { + VaultTimeoutAction, + VaultTimeoutSettingsService, +} from "../../key-management/vault-timeout"; +import { LogService } from "../../platform/abstractions/log.service"; +import { MessagingService } from "../../platform/abstractions/messaging.service"; import { UserId } from "../../types/guid"; import { ProcessReloadServiceAbstraction } from "../abstractions/process-reload.service"; @@ -37,6 +39,9 @@ export class DefaultProcessReloadService implements ProcessReloadServiceAbstract let status = await firstValueFrom(authService.authStatusFor$(userId as UserId)); status = await authService.getAuthStatus(userId); if (status === AuthenticationStatus.Unlocked) { + this.logService.info( + "[Process Reload Service] User unlocked, preventing process reload", + ); return; } } @@ -53,6 +58,9 @@ export class DefaultProcessReloadService implements ProcessReloadServiceAbstract if (userId != null) { const ephemeralPin = await this.pinService.getPinKeyEncryptedUserKeyEphemeral(userId); if (ephemeralPin != null) { + this.logService.info( + "[Process Reload Service] Ephemeral pin active, preventing process reload", + ); return; } } @@ -95,7 +103,12 @@ export class DefaultProcessReloadService implements ProcessReloadServiceAbstract await this.reloadCallback(); } return; + } else { + this.logService.info( + "[Process Reload Service] Desktop ipc fingerprint validated, preventing process reload", + ); } + if (this.reloadInterval == null) { this.reloadInterval = setInterval(async () => await this.executeProcessReload(), 1000); } diff --git a/libs/common/src/abstractions/vault-timeout/vault-timeout-settings.service.ts b/libs/common/src/key-management/vault-timeout/abstractions/vault-timeout-settings.service.ts similarity index 92% rename from libs/common/src/abstractions/vault-timeout/vault-timeout-settings.service.ts rename to libs/common/src/key-management/vault-timeout/abstractions/vault-timeout-settings.service.ts index e1d91462876..7094a2c2f83 100644 --- a/libs/common/src/abstractions/vault-timeout/vault-timeout-settings.service.ts +++ b/libs/common/src/key-management/vault-timeout/abstractions/vault-timeout-settings.service.ts @@ -2,9 +2,9 @@ // @ts-strict-ignore import { Observable } from "rxjs"; -import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; -import { UserId } from "../../types/guid"; -import { VaultTimeout } from "../../types/vault-timeout.type"; +import { UserId } from "../../../types/guid"; +import { VaultTimeoutAction } from "../enums/vault-timeout-action.enum"; +import { VaultTimeout } from "../types/vault-timeout.type"; export abstract class VaultTimeoutSettingsService { /** diff --git a/libs/common/src/abstractions/vault-timeout/vault-timeout.service.ts b/libs/common/src/key-management/vault-timeout/abstractions/vault-timeout.service.ts similarity index 100% rename from libs/common/src/abstractions/vault-timeout/vault-timeout.service.ts rename to libs/common/src/key-management/vault-timeout/abstractions/vault-timeout.service.ts diff --git a/libs/common/src/enums/vault-timeout-action.enum.ts b/libs/common/src/key-management/vault-timeout/enums/vault-timeout-action.enum.ts similarity index 100% rename from libs/common/src/enums/vault-timeout-action.enum.ts rename to libs/common/src/key-management/vault-timeout/enums/vault-timeout-action.enum.ts diff --git a/libs/common/src/key-management/vault-timeout/index.ts b/libs/common/src/key-management/vault-timeout/index.ts new file mode 100644 index 00000000000..7741752a351 --- /dev/null +++ b/libs/common/src/key-management/vault-timeout/index.ts @@ -0,0 +1,10 @@ +export { VaultTimeoutSettingsService } from "./abstractions/vault-timeout-settings.service"; +export { VaultTimeoutSettingsService as DefaultVaultTimeoutSettingsService } from "./services/vault-timeout-settings.service"; +export { VaultTimeoutService } from "./abstractions/vault-timeout.service"; +export { VaultTimeoutService as DefaultVaultTimeoutService } from "./services/vault-timeout.service"; +export { VaultTimeoutAction } from "./enums/vault-timeout-action.enum"; +export { + VaultTimeout, + VaultTimeoutOption, + VaultTimeoutStringType, +} from "./types/vault-timeout.type"; diff --git a/libs/common/src/services/vault-timeout/vault-timeout-settings.service.spec.ts b/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.spec.ts similarity index 93% rename from libs/common/src/services/vault-timeout/vault-timeout-settings.service.spec.ts rename to libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.spec.ts index 540f26bba2d..454a748344f 100644 --- a/libs/common/src/services/vault-timeout/vault-timeout-settings.service.spec.ts +++ b/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.spec.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject, firstValueFrom, map, of } from "rxjs"; @@ -6,25 +8,21 @@ import { FakeUserDecryptionOptions as UserDecryptionOptions, UserDecryptionOptionsServiceAbstraction, } from "@bitwarden/auth/common"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { UserId } from "@bitwarden/common/types/guid"; -import { BiometricStateService } from "@bitwarden/key-management"; +import { BiometricStateService, KeyService } from "@bitwarden/key-management"; -import { KeyService } from "../../../../key-management/src/abstractions/key.service"; -import { FakeAccountService, mockAccountServiceWith, FakeStateProvider } from "../../../spec"; -import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "../../abstractions/vault-timeout/vault-timeout-settings.service"; -import { PolicyService } from "../../admin-console/abstractions/policy/policy.service.abstraction"; -import { Policy } from "../../admin-console/models/domain/policy"; -import { TokenService } from "../../auth/abstractions/token.service"; -import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; -import { LogService } from "../../platform/abstractions/log.service"; -import { - VAULT_TIMEOUT, - VAULT_TIMEOUT_ACTION, -} from "../../services/vault-timeout/vault-timeout-settings.state"; -import { VaultTimeout, VaultTimeoutStringType } from "../../types/vault-timeout.type"; +import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from "../../../../spec"; +import { PolicyService } from "../../../admin-console/abstractions/policy/policy.service.abstraction"; +import { Policy } from "../../../admin-console/models/domain/policy"; +import { TokenService } from "../../../auth/services/token.service"; +import { LogService } from "../../../platform/abstractions/log.service"; +import { Utils } from "../../../platform/misc/utils"; +import { UserId } from "../../../types/guid"; +import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "../abstractions/vault-timeout-settings.service"; +import { VaultTimeoutAction } from "../enums/vault-timeout-action.enum"; +import { VaultTimeout, VaultTimeoutStringType } from "../types/vault-timeout.type"; import { VaultTimeoutSettingsService } from "./vault-timeout-settings.service"; +import { VAULT_TIMEOUT, VAULT_TIMEOUT_ACTION } from "./vault-timeout-settings.state"; describe("VaultTimeoutSettingsService", () => { let accountService: FakeAccountService; diff --git a/libs/common/src/services/vault-timeout/vault-timeout-settings.service.ts b/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.ts similarity index 91% rename from libs/common/src/services/vault-timeout/vault-timeout-settings.service.ts rename to libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.ts index 0f5bdca93da..f29687bd9cf 100644 --- a/libs/common/src/services/vault-timeout/vault-timeout-settings.service.ts +++ b/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.ts @@ -19,20 +19,19 @@ import { PinServiceAbstraction, UserDecryptionOptionsServiceAbstraction, } from "@bitwarden/auth/common"; -import { BiometricStateService } from "@bitwarden/key-management"; +import { BiometricStateService, KeyService } from "@bitwarden/key-management"; -import { KeyService } from "../../../../key-management/src/abstractions/key.service"; -import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "../../abstractions/vault-timeout/vault-timeout-settings.service"; -import { PolicyService } from "../../admin-console/abstractions/policy/policy.service.abstraction"; -import { PolicyType } from "../../admin-console/enums"; -import { Policy } from "../../admin-console/models/domain/policy"; -import { AccountService } from "../../auth/abstractions/account.service"; -import { TokenService } from "../../auth/abstractions/token.service"; -import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; -import { LogService } from "../../platform/abstractions/log.service"; -import { StateProvider } from "../../platform/state"; -import { UserId } from "../../types/guid"; -import { VaultTimeout, VaultTimeoutStringType } from "../../types/vault-timeout.type"; +import { PolicyService } from "../../../admin-console/abstractions/policy/policy.service.abstraction"; +import { PolicyType } from "../../../admin-console/enums"; +import { Policy } from "../../../admin-console/models/domain/policy"; +import { AccountService } from "../../../auth/abstractions/account.service"; +import { TokenService } from "../../../auth/abstractions/token.service"; +import { LogService } from "../../../platform/abstractions/log.service"; +import { StateProvider } from "../../../platform/state"; +import { UserId } from "../../../types/guid"; +import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "../abstractions/vault-timeout-settings.service"; +import { VaultTimeoutAction } from "../enums/vault-timeout-action.enum"; +import { VaultTimeout, VaultTimeoutStringType } from "../types/vault-timeout.type"; import { VAULT_TIMEOUT, VAULT_TIMEOUT_ACTION } from "./vault-timeout-settings.state"; diff --git a/libs/common/src/services/vault-timeout/vault-timeout-settings.state.spec.ts b/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.state.spec.ts similarity index 84% rename from libs/common/src/services/vault-timeout/vault-timeout-settings.state.spec.ts rename to libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.state.spec.ts index 42a82e67ee3..fa8df244793 100644 --- a/libs/common/src/services/vault-timeout/vault-timeout-settings.state.spec.ts +++ b/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.state.spec.ts @@ -1,6 +1,6 @@ -import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; -import { UserKeyDefinition } from "../../platform/state"; -import { VaultTimeout } from "../../types/vault-timeout.type"; +import { UserKeyDefinition } from "../../../platform/state"; +import { VaultTimeoutAction } from "../enums/vault-timeout-action.enum"; +import { VaultTimeout } from "../types/vault-timeout.type"; import { VAULT_TIMEOUT, VAULT_TIMEOUT_ACTION } from "./vault-timeout-settings.state"; diff --git a/libs/common/src/services/vault-timeout/vault-timeout-settings.state.ts b/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.state.ts similarity index 83% rename from libs/common/src/services/vault-timeout/vault-timeout-settings.state.ts rename to libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.state.ts index 46097d6a4cc..4d8a0de654a 100644 --- a/libs/common/src/services/vault-timeout/vault-timeout-settings.state.ts +++ b/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.state.ts @@ -1,6 +1,6 @@ -import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; -import { UserKeyDefinition, VAULT_TIMEOUT_SETTINGS_DISK_LOCAL } from "../../platform/state"; -import { VaultTimeout } from "../../types/vault-timeout.type"; +import { UserKeyDefinition, VAULT_TIMEOUT_SETTINGS_DISK_LOCAL } from "../../../platform/state"; +import { VaultTimeoutAction } from "../enums/vault-timeout-action.enum"; +import { VaultTimeout } from "../types/vault-timeout.type"; /** * Settings use disk storage and local storage on web so settings can persist after logout diff --git a/libs/common/src/services/vault-timeout/vault-timeout.service.spec.ts b/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.spec.ts similarity index 91% rename from libs/common/src/services/vault-timeout/vault-timeout.service.spec.ts rename to libs/common/src/key-management/vault-timeout/services/vault-timeout.service.spec.ts index 8a166e63a1f..5fdae07b2d7 100644 --- a/libs/common/src/services/vault-timeout/vault-timeout.service.spec.ts +++ b/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.spec.ts @@ -1,30 +1,32 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { MockProxy, any, mock } from "jest-mock-extended"; import { BehaviorSubject, from, of } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; import { LogoutReason } from "@bitwarden/auth/common"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { TaskSchedulerService } from "@bitwarden/common/platform/scheduling"; import { BiometricsService } from "@bitwarden/key-management"; -import { FakeAccountService, mockAccountServiceWith } from "../../../spec/fake-account-service"; -import { SearchService } from "../../abstractions/search.service"; -import { VaultTimeoutSettingsService } from "../../abstractions/vault-timeout/vault-timeout-settings.service"; -import { AccountInfo } from "../../auth/abstractions/account.service"; -import { AuthService } from "../../auth/abstractions/auth.service"; -import { AuthenticationStatus } from "../../auth/enums/authentication-status"; -import { FakeMasterPasswordService } from "../../auth/services/master-password/fake-master-password.service"; -import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; -import { MessagingService } from "../../platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service"; -import { StateService } from "../../platform/abstractions/state.service"; -import { Utils } from "../../platform/misc/utils"; -import { StateEventRunnerService } from "../../platform/state"; -import { UserId } from "../../types/guid"; -import { VaultTimeout, VaultTimeoutStringType } from "../../types/vault-timeout.type"; -import { CipherService } from "../../vault/abstractions/cipher.service"; -import { FolderService } from "../../vault/abstractions/folder/folder.service.abstraction"; +import { FakeAccountService, mockAccountServiceWith } from "../../../../spec"; +import { SearchService } from "../../../abstractions/search.service"; +import { AccountInfo } from "../../../auth/abstractions/account.service"; +import { AuthService } from "../../../auth/abstractions/auth.service"; +import { AuthenticationStatus } from "../../../auth/enums/authentication-status"; +import { LogService } from "../../../platform/abstractions/log.service"; +import { MessagingService } from "../../../platform/abstractions/messaging.service"; +import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service"; +import { StateService } from "../../../platform/abstractions/state.service"; +import { Utils } from "../../../platform/misc/utils"; +import { TaskSchedulerService } from "../../../platform/scheduling"; +import { StateEventRunnerService } from "../../../platform/state"; +import { UserId } from "../../../types/guid"; +import { CipherService } from "../../../vault/abstractions/cipher.service"; +import { FolderService } from "../../../vault/abstractions/folder/folder.service.abstraction"; +import { FakeMasterPasswordService } from "../../master-password/services/fake-master-password.service"; +import { VaultTimeoutAction } from "../enums/vault-timeout-action.enum"; +import { VaultTimeout, VaultTimeoutStringType } from "../types/vault-timeout.type"; +import { VaultTimeoutSettingsService } from "./vault-timeout-settings.service"; import { VaultTimeoutService } from "./vault-timeout.service"; describe("VaultTimeoutService", () => { diff --git a/libs/common/src/services/vault-timeout/vault-timeout.service.ts b/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.ts similarity index 83% rename from libs/common/src/services/vault-timeout/vault-timeout.service.ts rename to libs/common/src/key-management/vault-timeout/services/vault-timeout.service.ts index 8ab10b44b24..1762b9156db 100644 --- a/libs/common/src/services/vault-timeout/vault-timeout.service.ts +++ b/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.ts @@ -4,25 +4,25 @@ import { combineLatest, concatMap, filter, firstValueFrom, map, timeout } from " import { CollectionService } from "@bitwarden/admin-console/common"; import { LogoutReason } from "@bitwarden/auth/common"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { TaskSchedulerService, ScheduledTaskNames } from "@bitwarden/common/platform/scheduling"; import { BiometricsService } from "@bitwarden/key-management"; -import { SearchService } from "../../abstractions/search.service"; -import { VaultTimeoutSettingsService } from "../../abstractions/vault-timeout/vault-timeout-settings.service"; -import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "../../abstractions/vault-timeout/vault-timeout.service"; -import { AccountService } from "../../auth/abstractions/account.service"; -import { AuthService } from "../../auth/abstractions/auth.service"; -import { InternalMasterPasswordServiceAbstraction } from "../../auth/abstractions/master-password.service.abstraction"; -import { AuthenticationStatus } from "../../auth/enums/authentication-status"; -import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; -import { MessagingService } from "../../platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service"; -import { StateService } from "../../platform/abstractions/state.service"; -import { StateEventRunnerService } from "../../platform/state"; -import { UserId } from "../../types/guid"; -import { CipherService } from "../../vault/abstractions/cipher.service"; -import { FolderService } from "../../vault/abstractions/folder/folder.service.abstraction"; +import { SearchService } from "../../../abstractions/search.service"; +import { AccountService } from "../../../auth/abstractions/account.service"; +import { AuthService } from "../../../auth/abstractions/auth.service"; +import { AuthenticationStatus } from "../../../auth/enums/authentication-status"; +import { LogService } from "../../../platform/abstractions/log.service"; +import { MessagingService } from "../../../platform/abstractions/messaging.service"; +import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service"; +import { StateService } from "../../../platform/abstractions/state.service"; +import { TaskSchedulerService, ScheduledTaskNames } from "../../../platform/scheduling"; +import { StateEventRunnerService } from "../../../platform/state"; +import { UserId } from "../../../types/guid"; +import { CipherService } from "../../../vault/abstractions/cipher.service"; +import { FolderService } from "../../../vault/abstractions/folder/folder.service.abstraction"; +import { InternalMasterPasswordServiceAbstraction } from "../../master-password/abstractions/master-password.service.abstraction"; +import { VaultTimeoutSettingsService } from "../abstractions/vault-timeout-settings.service"; +import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "../abstractions/vault-timeout.service"; +import { VaultTimeoutAction } from "../enums/vault-timeout-action.enum"; export class VaultTimeoutService implements VaultTimeoutServiceAbstraction { private inited = false; @@ -138,10 +138,11 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction { ); if (userId == null || userId === currentUserId) { - await this.searchService.clearIndex(); await this.collectionService.clearActiveUserCache(); } + await this.searchService.clearIndex(lockingUserId); + await this.folderService.clearDecryptedFolderState(lockingUserId); await this.masterPasswordService.clearMasterKey(lockingUserId); diff --git a/libs/common/src/types/vault-timeout.type.ts b/libs/common/src/key-management/vault-timeout/types/vault-timeout.type.ts similarity index 100% rename from libs/common/src/types/vault-timeout.type.ts rename to libs/common/src/key-management/vault-timeout/types/vault-timeout.type.ts diff --git a/libs/common/src/models/export/cipher.export.ts b/libs/common/src/models/export/cipher.export.ts index e542d0dfc1f..7d0ef9e9c34 100644 --- a/libs/common/src/models/export/cipher.export.ts +++ b/libs/common/src/models/export/cipher.export.ts @@ -73,6 +73,7 @@ export class CipherExport { break; case CipherType.SshKey: view.sshKey = SshKeyExport.toView(req.sshKey); + break; } if (req.passwordHistory != null) { diff --git a/libs/common/src/models/export/ssh-key.export.ts b/libs/common/src/models/export/ssh-key.export.ts index c502ddafd47..5387daf7dd0 100644 --- a/libs/common/src/models/export/ssh-key.export.ts +++ b/libs/common/src/models/export/ssh-key.export.ts @@ -1,9 +1,10 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { SshKeyView as SshKeyView } from "@bitwarden/common/vault/models/view/ssh-key.view"; +import { import_ssh_key } from "@bitwarden/sdk-internal"; import { EncString } from "../../platform/models/domain/enc-string"; import { SshKey as SshKeyDomain } from "../../vault/models/domain/ssh-key"; +import { SshKeyView as SshKeyView } from "../../vault/models/view/ssh-key.view"; import { safeGetString } from "./utils"; @@ -17,16 +18,18 @@ export class SshKeyExport { } static toView(req: SshKeyExport, view = new SshKeyView()) { - view.privateKey = req.privateKey; - view.publicKey = req.publicKey; - view.keyFingerprint = req.keyFingerprint; + const parsedKey = import_ssh_key(req.privateKey); + view.privateKey = parsedKey.privateKey; + view.publicKey = parsedKey.publicKey; + view.keyFingerprint = parsedKey.fingerprint; return view; } static toDomain(req: SshKeyExport, domain = new SshKeyDomain()) { - domain.privateKey = req.privateKey != null ? new EncString(req.privateKey) : null; - domain.publicKey = req.publicKey != null ? new EncString(req.publicKey) : null; - domain.keyFingerprint = req.keyFingerprint != null ? new EncString(req.keyFingerprint) : null; + const parsedKey = import_ssh_key(req.privateKey); + domain.privateKey = new EncString(parsedKey.privateKey); + domain.publicKey = new EncString(parsedKey.publicKey); + domain.keyFingerprint = new EncString(parsedKey.fingerprint); return domain; } diff --git a/libs/common/src/models/export/utils.ts b/libs/common/src/models/export/utils.ts index 630b4898503..b7e0b74b611 100644 --- a/libs/common/src/models/export/utils.ts +++ b/libs/common/src/models/export/utils.ts @@ -1,4 +1,4 @@ -import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { EncString } from "../../platform/models/domain/enc-string"; export function safeGetString(value: string | EncString) { if (value == null) { diff --git a/libs/common/src/models/response/notification.response.ts b/libs/common/src/models/response/notification.response.ts index 894a00ee885..aa0ecc97b58 100644 --- a/libs/common/src/models/response/notification.response.ts +++ b/libs/common/src/models/response/notification.response.ts @@ -12,7 +12,16 @@ export class NotificationResponse extends BaseResponse { this.contextId = this.getResponseProperty("ContextId"); this.type = this.getResponseProperty("Type"); - const payload = this.getResponseProperty("Payload"); + let payload = this.getResponseProperty("Payload"); + + if (typeof payload === "string") { + try { + payload = JSON.parse(payload); + } catch { + // guess it was a string + } + } + switch (this.type) { case NotificationType.SyncCipherCreate: case NotificationType.SyncCipherDelete: diff --git a/libs/common/src/models/response/profile.response.ts b/libs/common/src/models/response/profile.response.ts index 6b6555fc566..9aee5acbce8 100644 --- a/libs/common/src/models/response/profile.response.ts +++ b/libs/common/src/models/response/profile.response.ts @@ -21,6 +21,7 @@ export class ProfileResponse extends BaseResponse { securityStamp: string; forcePasswordReset: boolean; usesKeyConnector: boolean; + verifyDevices: boolean; organizations: ProfileOrganizationResponse[] = []; providers: ProfileProviderResponse[] = []; providerOrganizations: ProfileProviderOrganizationResponse[] = []; @@ -42,6 +43,7 @@ export class ProfileResponse extends BaseResponse { this.securityStamp = this.getResponseProperty("SecurityStamp"); this.forcePasswordReset = this.getResponseProperty("ForcePasswordReset") ?? false; this.usesKeyConnector = this.getResponseProperty("UsesKeyConnector") ?? false; + this.verifyDevices = this.getResponseProperty("VerifyDevices") ?? true; const organizations = this.getResponseProperty("Organizations"); if (organizations != null) { diff --git a/libs/common/src/platform/abstractions/bulk-encrypt.service.ts b/libs/common/src/platform/abstractions/bulk-encrypt.service.ts deleted file mode 100644 index 4cdff0c769a..00000000000 --- a/libs/common/src/platform/abstractions/bulk-encrypt.service.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Decryptable } from "../interfaces/decryptable.interface"; -import { InitializerMetadata } from "../interfaces/initializer-metadata.interface"; -import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; - -export abstract class BulkEncryptService { - abstract decryptItems( - items: Decryptable[], - key: SymmetricCryptoKey, - ): Promise; -} diff --git a/libs/common/src/platform/abstractions/config/server-config.ts b/libs/common/src/platform/abstractions/config/server-config.ts index f77239b3016..8e08cc4e16c 100644 --- a/libs/common/src/platform/abstractions/config/server-config.ts +++ b/libs/common/src/platform/abstractions/config/server-config.ts @@ -3,6 +3,7 @@ import { Jsonify } from "type-fest"; import { AllowedFeatureFlagTypes } from "../../../enums/feature-flag.enum"; +import { PushTechnology } from "../../../enums/push-technology.enum"; import { ServerConfigData, ThirdPartyServerConfigData, @@ -10,6 +11,11 @@ import { } from "../../models/data/server-config.data"; import { ServerSettings } from "../../models/domain/server-settings"; +type PushConfig = + | { pushTechnology: PushTechnology.SignalR } + | { pushTechnology: PushTechnology.WebPush; vapidPublicKey: string } + | undefined; + const dayInMilliseconds = 24 * 3600 * 1000; export class ServerConfig { @@ -19,6 +25,7 @@ export class ServerConfig { environment?: EnvironmentServerConfigData; utcDate: Date; featureStates: { [key: string]: AllowedFeatureFlagTypes } = {}; + push: PushConfig; settings: ServerSettings; constructor(serverConfigData: ServerConfigData) { @@ -28,6 +35,15 @@ export class ServerConfig { this.utcDate = new Date(serverConfigData.utcDate); this.environment = serverConfigData.environment; this.featureStates = serverConfigData.featureStates; + this.push = + serverConfigData.push == null + ? { + pushTechnology: PushTechnology.SignalR, + } + : { + pushTechnology: serverConfigData.push.pushTechnology, + vapidPublicKey: serverConfigData.push.vapidPublicKey, + }; this.settings = serverConfigData.settings; if (this.server?.name == null && this.server?.url == null) { diff --git a/libs/common/src/platform/abstractions/environment.service.ts b/libs/common/src/platform/abstractions/environment.service.ts index 8d32fc4231d..4a10f856893 100644 --- a/libs/common/src/platform/abstractions/environment.service.ts +++ b/libs/common/src/platform/abstractions/environment.service.ts @@ -128,7 +128,7 @@ export abstract class EnvironmentService { /** * Get the environment from state. Useful if you need to get the environment for another user. */ - abstract getEnvironment$(userId?: string): Observable; + abstract getEnvironment$(userId: UserId): Observable; /** * @deprecated Use {@link getEnvironment$} instead. diff --git a/libs/common/src/platform/abstractions/fido2/fido2-user-interface.service.abstraction.ts b/libs/common/src/platform/abstractions/fido2/fido2-user-interface.service.abstraction.ts index 7beefc3b4cc..1f871f6c70f 100644 --- a/libs/common/src/platform/abstractions/fido2/fido2-user-interface.service.abstraction.ts +++ b/libs/common/src/platform/abstractions/fido2/fido2-user-interface.service.abstraction.ts @@ -82,7 +82,7 @@ export abstract class Fido2UserInterfaceSession { * * @param params The parameters to use when asking the user to pick a credential. * @param abortController An abort controller that can be used to cancel/close the session. - * @returns The ID of the cipher that contains the credentials the user picked. + * @returns The ID of the cipher that contains the credentials the user picked. If not cipher was picked, return cipherId = undefined to to let the authenticator throw the error. */ pickCredential: ( params: PickCredentialParams, diff --git a/libs/common/src/platform/abstractions/sdk/sdk-client-factory.ts b/libs/common/src/platform/abstractions/sdk/sdk-client-factory.ts index d684561dacd..6a1b7b67b42 100644 --- a/libs/common/src/platform/abstractions/sdk/sdk-client-factory.ts +++ b/libs/common/src/platform/abstractions/sdk/sdk-client-factory.ts @@ -4,6 +4,10 @@ import type { BitwardenClient } from "@bitwarden/sdk-internal"; * Factory for creating SDK clients. */ export abstract class SdkClientFactory { + /** + * Creates a new BitwardenClient. Assumes the SDK is already loaded. + * @param args Bitwarden client constructor parameters + */ abstract createSdkClient( ...args: ConstructorParameters ): Promise; diff --git a/libs/common/src/platform/abstractions/sdk/sdk-load.service.ts b/libs/common/src/platform/abstractions/sdk/sdk-load.service.ts new file mode 100644 index 00000000000..fb443d61777 --- /dev/null +++ b/libs/common/src/platform/abstractions/sdk/sdk-load.service.ts @@ -0,0 +1,52 @@ +import { init_sdk } from "@bitwarden/sdk-internal"; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars -- used in docs +import type { SdkService } from "./sdk.service"; + +export class SdkLoadFailedError extends Error { + constructor(error: unknown) { + super(`SDK loading failed: ${error}`); + } +} + +export abstract class SdkLoadService { + private static markAsReady: () => void; + private static markAsFailed: (error: unknown) => void; + + /** + * This promise is resolved when the SDK is ready to be used. Use it when your code might run early and/or is not able to use DI. + * Beware that WASM always requires a load step which makes it tricky to use functions and classes directly, it is therefore recommended + * to use the SDK through the {@link SdkService}. Only use this promise in advanced scenarios! + * + * @example + * ```typescript + * import { pureFunction } from "@bitwarden/sdk-internal"; + * + * async function myFunction() { + * await SdkLoadService.Ready; + * pureFunction(); + * } + * ``` + */ + static readonly Ready = new Promise((resolve, reject) => { + SdkLoadService.markAsReady = resolve; + SdkLoadService.markAsFailed = (error: unknown) => reject(new SdkLoadFailedError(error)); + }); + + /** + * Load WASM and initalize SDK-JS integrations such as logging. + * This method should be called once at the start of the application. + * Raw functions and classes from the SDK can be used after this method resolves. + */ + async loadAndInit(): Promise { + try { + await this.load(); + init_sdk(); + SdkLoadService.markAsReady(); + } catch (error) { + SdkLoadService.markAsFailed(error); + } + } + + protected abstract load(): Promise; +} diff --git a/libs/common/src/platform/abstractions/sdk/sdk.service.ts b/libs/common/src/platform/abstractions/sdk/sdk.service.ts index d44d38c36ab..3adf3291bbf 100644 --- a/libs/common/src/platform/abstractions/sdk/sdk.service.ts +++ b/libs/common/src/platform/abstractions/sdk/sdk.service.ts @@ -1,22 +1,27 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Observable } from "rxjs"; import { BitwardenClient } from "@bitwarden/sdk-internal"; import { UserId } from "../../../types/guid"; +import { Rc } from "../../misc/reference-counting/rc"; + +export class UserNotLoggedInError extends Error { + constructor(userId: UserId) { + super(`User (${userId}) is not logged in`); + } +} export abstract class SdkService { /** * Retrieve the version of the SDK. */ - version$: Observable; + abstract version$: Observable; /** * Retrieve a client initialized without a user. * This client can only be used for operations that don't require a user context. */ - client$: Observable; + abstract client$: Observable; /** * Retrieve a client initialized for a specific user. @@ -27,7 +32,20 @@ export abstract class SdkService { * The client will be destroyed when the observable is no longer subscribed to. * Please let platform know if you need a client that is not destroyed when the observable is no longer subscribed to. * - * @param userId + * @param userId The user id for which to retrieve the client + * + * @throws {UserNotLoggedInError} If the user is not logged in */ - abstract userClient$(userId: UserId): Observable; + abstract userClient$(userId: UserId): Observable | undefined>; + + /** + * This method is used during/after an authentication procedure to set a new client for a specific user. + * It can also be used to unset the client when a user logs out, this will result in: + * - The client being disposed of + * - All subscriptions to the client being completed + * - Any new subscribers receiving an error + * @param userId The user id for which to set the client + * @param client The client to set for the user. If undefined, the client will be unset. + */ + abstract setClient(userId: UserId, client: BitwardenClient | undefined): void; } diff --git a/libs/common/src/platform/abstractions/state.service.ts b/libs/common/src/platform/abstractions/state.service.ts index 123c70c298a..3b0ce07623b 100644 --- a/libs/common/src/platform/abstractions/state.service.ts +++ b/libs/common/src/platform/abstractions/state.service.ts @@ -30,7 +30,7 @@ export abstract class StateService { /** * Sets the user's auto key */ - setUserKeyAutoUnlock: (value: string, options?: StorageOptions) => Promise; + setUserKeyAutoUnlock: (value: string | null, options?: StorageOptions) => Promise; /** * Gets the user's biometric key */ @@ -57,7 +57,7 @@ export abstract class StateService { /** * @deprecated For migration purposes only, use setUserKeyAuto instead */ - setCryptoMasterKeyAuto: (value: string, options?: StorageOptions) => Promise; + setCryptoMasterKeyAuto: (value: string | null, options?: StorageOptions) => Promise; getDuckDuckGoSharedKey: (options?: StorageOptions) => Promise; setDuckDuckGoSharedKey: (value: string, options?: StorageOptions) => Promise; diff --git a/libs/common/src/platform/enums/encryption-type.enum.ts b/libs/common/src/platform/enums/encryption-type.enum.ts index a0ffe679279..fd484dc2fdf 100644 --- a/libs/common/src/platform/enums/encryption-type.enum.ts +++ b/libs/common/src/platform/enums/encryption-type.enum.ts @@ -1,6 +1,6 @@ export enum EncryptionType { AesCbc256_B64 = 0, - AesCbc128_HmacSha256_B64 = 1, + // Type 1 was the unused and removed AesCbc128_HmacSha256_B64 AesCbc256_HmacSha256_B64 = 2, Rsa2048_OaepSha256_B64 = 3, Rsa2048_OaepSha1_B64 = 4, @@ -17,12 +17,10 @@ export function encryptionTypeToString(encryptionType: EncryptionType): string { } /** The expected number of parts to a serialized EncString of the given encryption type. - * For example, an EncString of type AesCbc256_B64 will have 2 parts, and an EncString of type - * AesCbc128_HmacSha256_B64 will have 3 parts. + * For example, an EncString of type AesCbc256_B64 will have 2 parts * * Example of annotated serialized EncStrings: * 0.iv|data - * 1.iv|data|mac * 2.iv|data|mac * 3.data * 4.data @@ -33,7 +31,6 @@ export function encryptionTypeToString(encryptionType: EncryptionType): string { */ export const EXPECTED_NUM_PARTS_BY_ENCRYPTION_TYPE = { [EncryptionType.AesCbc256_B64]: 2, - [EncryptionType.AesCbc128_HmacSha256_B64]: 3, [EncryptionType.AesCbc256_HmacSha256_B64]: 3, [EncryptionType.Rsa2048_OaepSha256_B64]: 1, [EncryptionType.Rsa2048_OaepSha1_B64]: 1, diff --git a/libs/common/src/platform/enums/theme-type.enum.ts b/libs/common/src/platform/enums/theme-type.enum.ts index 5e1a0c21c36..d1767c4990a 100644 --- a/libs/common/src/platform/enums/theme-type.enum.ts +++ b/libs/common/src/platform/enums/theme-type.enum.ts @@ -5,16 +5,12 @@ export enum ThemeType { System = "system", Light = "light", Dark = "dark", - Nord = "nord", - SolarizedDark = "solarizedDark", } export const ThemeTypes = { System: "system", Light: "light", Dark: "dark", - Nord: "nord", - SolarizedDark: "solarizedDark", } as const; export type Theme = (typeof ThemeTypes)[keyof typeof ThemeTypes]; diff --git a/libs/common/src/platform/misc/convert-values.ts b/libs/common/src/platform/misc/convert-values.ts index dd097c6096d..49864994148 100644 --- a/libs/common/src/platform/misc/convert-values.ts +++ b/libs/common/src/platform/misc/convert-values.ts @@ -8,7 +8,7 @@ import { ObservableInput, OperatorFunction, map } from "rxjs"; */ export function convertValues( project: (key: TKey, value: TInput) => ObservableInput, -): OperatorFunction, Record>> { +): OperatorFunction | null, Record>> { return map((inputRecord) => { if (inputRecord == null) { return null; diff --git a/libs/common/src/platform/misc/flags.ts b/libs/common/src/platform/misc/flags.ts index 8ed19ce57fc..30531b6799e 100644 --- a/libs/common/src/platform/misc/flags.ts +++ b/libs/common/src/platform/misc/flags.ts @@ -1,14 +1,12 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type SharedFlags = { sdk?: boolean; prereleaseBuild?: boolean; }; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type SharedDevFlags = { noopNotifications: boolean; skipWelcomeOnInstall: boolean; diff --git a/libs/common/src/platform/misc/reference-counting/rc.spec.ts b/libs/common/src/platform/misc/reference-counting/rc.spec.ts new file mode 100644 index 00000000000..f8767242ba5 --- /dev/null +++ b/libs/common/src/platform/misc/reference-counting/rc.spec.ts @@ -0,0 +1,85 @@ +import { Rc } from "./rc"; + +export class FreeableTestValue { + isFreed = false; + + free() { + this.isFreed = true; + } +} + +describe("Rc", () => { + let value: FreeableTestValue; + let rc: Rc; + + beforeEach(() => { + value = new FreeableTestValue(); + rc = new Rc(value); + }); + + it("should increase refCount when taken", () => { + rc.take(); + + expect(rc["refCount"]).toBe(1); + }); + + it("should return value on take", () => { + // eslint-disable-next-line @bitwarden/platform/required-using + const reference = rc.take(); + + expect(reference.value).toBe(value); + }); + + it("should decrease refCount when disposing reference", () => { + // eslint-disable-next-line @bitwarden/platform/required-using + const reference = rc.take(); + + reference[Symbol.dispose](); + + expect(rc["refCount"]).toBe(0); + }); + + it("should automatically decrease refCount when reference goes out of scope", () => { + { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + using reference = rc.take(); + } + + expect(rc["refCount"]).toBe(0); + }); + + it("should not free value when refCount reaches 0 if not marked for disposal", () => { + // eslint-disable-next-line @bitwarden/platform/required-using + const reference = rc.take(); + + reference[Symbol.dispose](); + + expect(value.isFreed).toBe(false); + }); + + it("should free value when refCount reaches 0 and rc is marked for disposal", () => { + // eslint-disable-next-line @bitwarden/platform/required-using + const reference = rc.take(); + rc.markForDisposal(); + + reference[Symbol.dispose](); + + expect(value.isFreed).toBe(true); + }); + + it("should free value when marked for disposal if refCount is 0", () => { + // eslint-disable-next-line @bitwarden/platform/required-using + const reference = rc.take(); + reference[Symbol.dispose](); + + rc.markForDisposal(); + + expect(value.isFreed).toBe(true); + }); + + it("should throw error when trying to take a disposed reference", () => { + rc.markForDisposal(); + + expect(() => rc.take()).toThrow(); + }); +}); diff --git a/libs/common/src/platform/misc/reference-counting/rc.ts b/libs/common/src/platform/misc/reference-counting/rc.ts new file mode 100644 index 00000000000..9be102b43d3 --- /dev/null +++ b/libs/common/src/platform/misc/reference-counting/rc.ts @@ -0,0 +1,76 @@ +import { UsingRequired } from "../using-required"; + +export type Freeable = { free: () => void }; + +/** + * Reference counted disposable value. + * This class is used to manage the lifetime of a value that needs to be + * freed of at a specific time but might still be in-use when that happens. + */ +export class Rc { + private markedForDisposal = false; + private refCount = 0; + private value: T; + + constructor(value: T) { + this.value = value; + } + + /** + * Use this function when you want to use the underlying object. + * This will guarantee that you have a reference to the object + * and that it won't be freed until your reference goes out of scope. + * + * This function must be used with the `using` keyword. + * + * @example + * ```typescript + * function someFunction(rc: Rc) { + * using reference = rc.take(); + * reference.value.doSomething(); + * // reference is automatically disposed here + * } + * ``` + * + * @returns The value. + */ + take(): Ref { + if (this.markedForDisposal) { + throw new Error("Cannot take a reference to a value marked for disposal"); + } + + this.refCount++; + return new Ref(() => this.release(), this.value); + } + + /** + * Mark this Rc for disposal. When the refCount reaches 0, the value + * will be freed. + */ + markForDisposal() { + this.markedForDisposal = true; + this.freeIfPossible(); + } + + private release() { + this.refCount--; + this.freeIfPossible(); + } + + private freeIfPossible() { + if (this.refCount === 0 && this.markedForDisposal) { + this.value.free(); + } + } +} + +export class Ref implements UsingRequired { + constructor( + private readonly release: () => void, + readonly value: T, + ) {} + + [Symbol.dispose]() { + this.release(); + } +} diff --git a/libs/common/src/platform/misc/support-status.ts b/libs/common/src/platform/misc/support-status.ts new file mode 100644 index 00000000000..6e02a10c8d8 --- /dev/null +++ b/libs/common/src/platform/misc/support-status.ts @@ -0,0 +1,48 @@ +import { ObservableInput, OperatorFunction, switchMap } from "rxjs"; + +/** + * Indicates that the given set of actions is not supported and there is + * not anything the user can do to make it supported. The reason property + * should contain a documented and machine readable string so more in + * depth details can be shown to the user. + */ +export type NotSupported = { type: "not-supported"; reason: string }; + +/** + * Indicates that the given set of actions does not currently work but + * could be supported if configuration, either inside Bitwarden or outside, + * is done. The reason property should contain a documented and + * machine readable string so further instruction can be supplied to the caller. + */ +export type NeedsConfiguration = { type: "needs-configuration"; reason: string }; + +/** + * Indicates that the actions in the service property are supported. + */ +export type Supported = { type: "supported"; service: T }; + +/** + * A type encapsulating the status of support for a service. + */ +export type SupportStatus = Supported | NeedsConfiguration | NotSupported; + +/** + * Projects each source value to one of the given projects defined in `selectors`. + * + * @param selectors.supported The function to run when the given item reports that it is supported + * @param selectors.notSupported The function to run when the given item reports that it is either not-supported + * or needs-configuration. + * @returns A function that returns an Observable that emits the result of one of the given projection functions. + */ +export function supportSwitch(selectors: { + supported: (service: TService, index: number) => ObservableInput; + notSupported: (reason: string, index: number) => ObservableInput; +}): OperatorFunction, TSupported | TNotSupported> { + return switchMap((supportStatus, index) => { + if (supportStatus.type === "supported") { + return selectors.supported(supportStatus.service, index); + } + + return selectors.notSupported(supportStatus.reason, index); + }); +} diff --git a/libs/common/src/platform/misc/using-required.ts b/libs/common/src/platform/misc/using-required.ts new file mode 100644 index 00000000000..f641b9f312f --- /dev/null +++ b/libs/common/src/platform/misc/using-required.ts @@ -0,0 +1,11 @@ +export type Disposable = { [Symbol.dispose]: () => void }; + +/** + * Types implementing this type must be used together with the `using` keyword + * + * @example using ref = rc.take(); + */ +// We want to use `interface` here because it creates a separate type. +// Type aliasing would not expose `UsingRequired` to the linter. +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface UsingRequired extends Disposable {} diff --git a/libs/common/src/platform/misc/utils.ts b/libs/common/src/platform/misc/utils.ts index d2bf11a20ce..ef65d2130a0 100644 --- a/libs/common/src/platform/misc/utils.ts +++ b/libs/common/src/platform/misc/utils.ts @@ -8,10 +8,13 @@ import { Observable, of, switchMap } from "rxjs"; import { getHostname, parse } from "tldts"; import { Merge } from "type-fest"; -import { KeyService } from "../../../../key-management/src/abstractions/key.service"; -import { EncryptService } from "../abstractions/encrypt.service"; +import { KeyService } from "@bitwarden/key-management"; + +import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service"; import { I18nService } from "../abstractions/i18n.service"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports const nodeURL = typeof self === "undefined" ? require("url") : null; declare global { @@ -608,6 +611,8 @@ export class Utils { } return new URL(uriString); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // Ignore error } diff --git a/libs/common/src/platform/models/data/server-config.data.spec.ts b/libs/common/src/platform/models/data/server-config.data.spec.ts index 13d14204085..d71e76657fd 100644 --- a/libs/common/src/platform/models/data/server-config.data.spec.ts +++ b/libs/common/src/platform/models/data/server-config.data.spec.ts @@ -1,3 +1,4 @@ +import { PushTechnology } from "../../../enums/push-technology.enum"; import { Region } from "../../abstractions/environment.service"; import { @@ -29,6 +30,9 @@ describe("ServerConfigData", () => { }, utcDate: "2020-01-01T00:00:00.000Z", featureStates: { feature: "state" }, + push: { + pushTechnology: PushTechnology.SignalR, + }, }; const serverConfigData = ServerConfigData.fromJSON(json); diff --git a/libs/common/src/platform/models/data/server-config.data.ts b/libs/common/src/platform/models/data/server-config.data.ts index 6ed51d2f5ce..af99f1d4a6d 100644 --- a/libs/common/src/platform/models/data/server-config.data.ts +++ b/libs/common/src/platform/models/data/server-config.data.ts @@ -9,6 +9,7 @@ import { ServerConfigResponse, ThirdPartyServerConfigResponse, EnvironmentServerConfigResponse, + PushSettingsConfigResponse, } from "../response/server-config.response"; export class ServerConfigData { @@ -18,6 +19,7 @@ export class ServerConfigData { environment?: EnvironmentServerConfigData; utcDate: string; featureStates: { [key: string]: AllowedFeatureFlagTypes } = {}; + push: PushSettingsConfigData; settings: ServerSettings; constructor(serverConfigResponse: Partial) { @@ -32,6 +34,9 @@ export class ServerConfigData { : null; this.featureStates = serverConfigResponse?.featureStates; this.settings = new ServerSettings(serverConfigResponse.settings); + this.push = serverConfigResponse?.push + ? new PushSettingsConfigData(serverConfigResponse.push) + : null; } static fromJSON(obj: Jsonify): ServerConfigData { @@ -42,6 +47,20 @@ export class ServerConfigData { } } +export class PushSettingsConfigData { + pushTechnology: number; + vapidPublicKey?: string; + + constructor(response: Partial) { + this.pushTechnology = response.pushTechnology; + this.vapidPublicKey = response.vapidPublicKey; + } + + static fromJSON(obj: Jsonify): PushSettingsConfigData { + return Object.assign(new PushSettingsConfigData({}), obj); + } +} + export class ThirdPartyServerConfigData { name: string; url: string; diff --git a/libs/common/src/platform/models/domain/domain-base.spec.ts b/libs/common/src/platform/models/domain/domain-base.spec.ts index 0bdee21e3c1..0c13f9a2119 100644 --- a/libs/common/src/platform/models/domain/domain-base.spec.ts +++ b/libs/common/src/platform/models/domain/domain-base.spec.ts @@ -1,7 +1,7 @@ import { mock, MockProxy } from "jest-mock-extended"; import { makeEncString, makeSymmetricCryptoKey } from "../../../../spec"; -import { EncryptService } from "../../abstractions/encrypt.service"; +import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service"; import { Utils } from "../../misc/utils"; import Domain from "./domain-base"; @@ -67,9 +67,13 @@ describe("DomainBase", () => { ); // @ts-expect-error -- encString2 was not decrypted + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions decrypted as { encToString: string; encString2: string; plainText: string }; // encString2 was not decrypted, so it's still an EncString + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions decrypted as { encToString: string; encString2: EncString; plainText: string }; }); diff --git a/libs/common/src/platform/models/domain/domain-base.ts b/libs/common/src/platform/models/domain/domain-base.ts index 688cf52d4c0..11d0193accc 100644 --- a/libs/common/src/platform/models/domain/domain-base.ts +++ b/libs/common/src/platform/models/domain/domain-base.ts @@ -1,20 +1,31 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { ConditionalExcept, ConditionalKeys, Constructor } from "type-fest"; +import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service"; import { View } from "../../../models/view/view"; -import { EncryptService } from "../../abstractions/encrypt.service"; import { EncString } from "./enc-string"; import { SymmetricCryptoKey } from "./symmetric-crypto-key"; -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type type EncStringKeys = ConditionalKeys, EncString>; export type DecryptedObject< TEncryptedObject, TDecryptedKeys extends EncStringKeys, > = Record & Omit; +// extracts shared keys from the domain and view types +type EncryptableKeys = (keyof D & + ConditionalKeys) & + (keyof V & ConditionalKeys); + +type DomainEncryptableKeys = { + [key in ConditionalKeys]: EncString | null; +}; + +type ViewEncryptableKeys = { + [key in ConditionalKeys]: string | null; +}; + // https://contributing.bitwarden.com/architecture/clients/data-model#domain export default class Domain { protected buildDomainModel( @@ -37,6 +48,7 @@ export default class Domain { } } } + protected buildDataModel( domain: D, dataObj: any, @@ -58,44 +70,24 @@ export default class Domain { } } - protected async decryptObj( - viewModel: T, - map: any, - orgId: string, - key: SymmetricCryptoKey = null, + protected async decryptObj( + domain: DomainEncryptableKeys, + viewModel: ViewEncryptableKeys, + props: EncryptableKeys[], + orgId: string | null, + key: SymmetricCryptoKey | null = null, objectContext: string = "No Domain Context", - ): Promise { - const promises = []; - const self: any = this; - - for (const prop in map) { - // eslint-disable-next-line - if (!map.hasOwnProperty(prop)) { - continue; - } - - (function (theProp) { - const p = Promise.resolve() - .then(() => { - const mapProp = map[theProp] || theProp; - if (self[mapProp]) { - return self[mapProp].decrypt( - orgId, - key, - `Property: ${prop}; ObjectContext: ${objectContext}`, - ); - } - return null; - }) - .then((val: any) => { - (viewModel as any)[theProp] = val; - }); - promises.push(p); - })(prop); + ): Promise { + for (const prop of props) { + viewModel[prop] = + (await domain[prop]?.decrypt( + orgId, + key, + `Property: ${prop as string}; ObjectContext: ${objectContext}`, + )) ?? null; } - await Promise.all(promises); - return viewModel; + return viewModel as V; } /** @@ -121,22 +113,20 @@ export default class Domain { _: Constructor = this.constructor as Constructor, objectContext: string = "No Domain Context", ): Promise> { - const promises = []; + const decryptedObjects = []; for (const prop of encryptedProperties) { - const value = (this as any)[prop] as EncString; - promises.push( - this.decryptProperty( - prop, - value, - key, - encryptService, - `Property: ${prop.toString()}; ObjectContext: ${objectContext}`, - ), + const value = this[prop] as EncString; + const decrypted = await this.decryptProperty( + prop, + value, + key, + encryptService, + `Property: ${prop.toString()}; ObjectContext: ${objectContext}`, ); + decryptedObjects.push(decrypted); } - const decryptedObjects = await Promise.all(promises); const decryptedObject = decryptedObjects.reduce( (acc, obj) => { return { ...acc, ...obj }; @@ -153,11 +143,9 @@ export default class Domain { encryptService: EncryptService, decryptTrace: string, ) { - let decrypted: string = null; + let decrypted: string | null = null; if (value) { decrypted = await value.decryptWithKey(key, encryptService, decryptTrace); - } else { - decrypted = null; } return { [propertyKey]: decrypted, diff --git a/libs/common/src/platform/models/domain/enc-array-buffer.spec.ts b/libs/common/src/platform/models/domain/enc-array-buffer.spec.ts index 45a45ffe087..de0fea58e36 100644 --- a/libs/common/src/platform/models/domain/enc-array-buffer.spec.ts +++ b/libs/common/src/platform/models/domain/enc-array-buffer.spec.ts @@ -5,28 +5,28 @@ import { EncArrayBuffer } from "./enc-array-buffer"; describe("encArrayBuffer", () => { describe("parses the buffer", () => { - test.each([ - [EncryptionType.AesCbc128_HmacSha256_B64, "AesCbc128_HmacSha256_B64"], - [EncryptionType.AesCbc256_HmacSha256_B64, "AesCbc256_HmacSha256_B64"], - ])("with %c%s", (encType: EncryptionType) => { - const iv = makeStaticByteArray(16, 10); - const mac = makeStaticByteArray(32, 20); - // We use the minimum data length of 1 to test the boundary of valid lengths - const data = makeStaticByteArray(1, 100); + test.each([[EncryptionType.AesCbc256_HmacSha256_B64, "AesCbc256_HmacSha256_B64"]])( + "with %c%s", + (encType: EncryptionType) => { + const iv = makeStaticByteArray(16, 10); + const mac = makeStaticByteArray(32, 20); + // We use the minimum data length of 1 to test the boundary of valid lengths + const data = makeStaticByteArray(1, 100); - const array = new Uint8Array(1 + iv.byteLength + mac.byteLength + data.byteLength); - array.set([encType]); - array.set(iv, 1); - array.set(mac, 1 + iv.byteLength); - array.set(data, 1 + iv.byteLength + mac.byteLength); + const array = new Uint8Array(1 + iv.byteLength + mac.byteLength + data.byteLength); + array.set([encType]); + array.set(iv, 1); + array.set(mac, 1 + iv.byteLength); + array.set(data, 1 + iv.byteLength + mac.byteLength); - const actual = new EncArrayBuffer(array); + const actual = new EncArrayBuffer(array); - expect(actual.encryptionType).toEqual(encType); - expect(actual.ivBytes).toEqualBuffer(iv); - expect(actual.macBytes).toEqualBuffer(mac); - expect(actual.dataBytes).toEqualBuffer(data); - }); + expect(actual.encryptionType).toEqual(encType); + expect(actual.ivBytes).toEqualBuffer(iv); + expect(actual.macBytes).toEqualBuffer(mac); + expect(actual.dataBytes).toEqualBuffer(data); + }, + ); it("with AesCbc256_B64", () => { const encType = EncryptionType.AesCbc256_B64; @@ -50,7 +50,6 @@ describe("encArrayBuffer", () => { describe("throws if the buffer has an invalid length", () => { test.each([ - [EncryptionType.AesCbc128_HmacSha256_B64, 50, "AesCbc128_HmacSha256_B64"], [EncryptionType.AesCbc256_HmacSha256_B64, 50, "AesCbc256_HmacSha256_B64"], [EncryptionType.AesCbc256_B64, 18, "AesCbc256_B64"], ])("with %c%c%s", (encType: EncryptionType, minLength: number) => { diff --git a/libs/common/src/platform/models/domain/enc-array-buffer.ts b/libs/common/src/platform/models/domain/enc-array-buffer.ts index 305504f57b7..8b69cb347ba 100644 --- a/libs/common/src/platform/models/domain/enc-array-buffer.ts +++ b/libs/common/src/platform/models/domain/enc-array-buffer.ts @@ -20,7 +20,6 @@ export class EncArrayBuffer implements Encrypted { const encType = encBytes[0]; switch (encType) { - case EncryptionType.AesCbc128_HmacSha256_B64: case EncryptionType.AesCbc256_HmacSha256_B64: { const minimumLength = ENC_TYPE_LENGTH + IV_LENGTH + MAC_LENGTH + MIN_DATA_LENGTH; if (encBytes.length < minimumLength) { diff --git a/libs/common/src/platform/models/domain/enc-string.spec.ts b/libs/common/src/platform/models/domain/enc-string.spec.ts index 85108a9609b..c3f257d442a 100644 --- a/libs/common/src/platform/models/domain/enc-string.spec.ts +++ b/libs/common/src/platform/models/domain/enc-string.spec.ts @@ -1,8 +1,9 @@ import { mock, MockProxy } from "jest-mock-extended"; -import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; +import { KeyService } from "@bitwarden/key-management"; + import { makeEncString, makeStaticByteArray } from "../../../../spec"; -import { EncryptService } from "../../../platform/abstractions/encrypt.service"; +import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { UserKey, OrgKey } from "../../../types/key"; import { EncryptionType } from "../../enums"; @@ -59,9 +60,7 @@ describe("EncString", () => { const cases = [ "aXY=|Y3Q=", // AesCbc256_B64 w/out header - "aXY=|Y3Q=|cnNhQ3Q=", // AesCbc128_HmacSha256_B64 w/out header "0.QmFzZTY0UGFydA==|QmFzZTY0UGFydA==", // AesCbc256_B64 with header - "1.QmFzZTY0UGFydA==|QmFzZTY0UGFydA==|QmFzZTY0UGFydA==", // AesCbc128_HmacSha256_B64 "2.QmFzZTY0UGFydA==|QmFzZTY0UGFydA==|QmFzZTY0UGFydA==", // AesCbc256_HmacSha256_B64 "3.QmFzZTY0UGFydA==", // Rsa2048_OaepSha256_B64 "4.QmFzZTY0UGFydA==", // Rsa2048_OaepSha1_B64 diff --git a/libs/common/src/platform/models/domain/enc-string.ts b/libs/common/src/platform/models/domain/enc-string.ts index b8e0006942a..b0b03e0fb3c 100644 --- a/libs/common/src/platform/models/domain/enc-string.ts +++ b/libs/common/src/platform/models/domain/enc-string.ts @@ -2,7 +2,7 @@ // @ts-strict-ignore import { Jsonify, Opaque } from "type-fest"; -import { EncryptService } from "../../abstractions/encrypt.service"; +import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service"; import { EncryptionType, EXPECTED_NUM_PARTS_BY_ENCRYPTION_TYPE } from "../../enums"; import { Encrypted } from "../../interfaces/encrypted"; import { Utils } from "../../misc/utils"; @@ -89,7 +89,6 @@ export class EncString implements Encrypted { } switch (encType) { - case EncryptionType.AesCbc128_HmacSha256_B64: case EncryptionType.AesCbc256_HmacSha256_B64: this.iv = encPieces[0]; this.data = encPieces[1]; @@ -125,15 +124,14 @@ export class EncString implements Encrypted { try { encType = parseInt(headerPieces[0], null); encPieces = headerPieces[1].split("|"); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return { encType: NaN, encPieces: [] }; } } else { encPieces = encryptedString.split("|"); - encType = - encPieces.length === 3 - ? EncryptionType.AesCbc128_HmacSha256_B64 - : EncryptionType.AesCbc256_B64; + encType = EncryptionType.AesCbc256_B64; } return { @@ -156,7 +154,11 @@ export class EncString implements Encrypted { return EXPECTED_NUM_PARTS_BY_ENCRYPTION_TYPE[encType] === encPieces.length; } - async decrypt(orgId: string, key: SymmetricCryptoKey = null, context?: string): Promise { + async decrypt( + orgId: string | null, + key: SymmetricCryptoKey | null = null, + context?: string, + ): Promise { if (this.decryptedValue != null) { return this.decryptedValue; } @@ -186,6 +188,8 @@ export class EncString implements Encrypted { key, decryptTrace == null ? context : `${decryptTrace}${context || ""}`, ); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.decryptedValue = DECRYPT_ERROR; } @@ -203,6 +207,8 @@ export class EncString implements Encrypted { } this.decryptedValue = await encryptService.decryptToUtf8(this, key, decryptTrace); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.decryptedValue = DECRYPT_ERROR; } diff --git a/libs/common/src/platform/models/domain/symmetric-crypto-key.spec.ts b/libs/common/src/platform/models/domain/symmetric-crypto-key.spec.ts index f39b298a27d..58c902ebab6 100644 --- a/libs/common/src/platform/models/domain/symmetric-crypto-key.spec.ts +++ b/libs/common/src/platform/models/domain/symmetric-crypto-key.spec.ts @@ -20,28 +20,13 @@ describe("SymmetricCryptoKey", () => { expect(cryptoKey).toEqual({ encKey: key, encKeyB64: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=", - encType: 0, + encType: EncryptionType.AesCbc256_B64, key: key, keyB64: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=", macKey: null, }); }); - it("AesCbc128_HmacSha256_B64", () => { - const key = makeStaticByteArray(32); - const cryptoKey = new SymmetricCryptoKey(key, EncryptionType.AesCbc128_HmacSha256_B64); - - expect(cryptoKey).toEqual({ - encKey: key.slice(0, 16), - encKeyB64: "AAECAwQFBgcICQoLDA0ODw==", - encType: 1, - key: key, - keyB64: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=", - macKey: key.slice(16, 32), - macKeyB64: "EBESExQVFhcYGRobHB0eHw==", - }); - }); - it("AesCbc256_HmacSha256_B64", () => { const key = makeStaticByteArray(64); const cryptoKey = new SymmetricCryptoKey(key); @@ -49,7 +34,7 @@ describe("SymmetricCryptoKey", () => { expect(cryptoKey).toEqual({ encKey: key.slice(0, 32), encKeyB64: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=", - encType: 2, + encType: EncryptionType.AesCbc256_HmacSha256_B64, key: key, keyB64: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+Pw==", @@ -83,4 +68,21 @@ describe("SymmetricCryptoKey", () => { expect(actual).toEqual(expected); expect(actual).toBeInstanceOf(SymmetricCryptoKey); }); + + describe("fromString", () => { + it("null string returns null", () => { + const actual = SymmetricCryptoKey.fromString(null); + + expect(actual).toBeNull(); + }); + + it("base64 string creates object", () => { + const key = makeStaticByteArray(64); + const expected = new SymmetricCryptoKey(key); + const actual = SymmetricCryptoKey.fromString(expected.keyB64); + + expect(actual).toEqual(expected); + expect(actual).toBeInstanceOf(SymmetricCryptoKey); + }); + }); }); diff --git a/libs/common/src/platform/models/domain/symmetric-crypto-key.ts b/libs/common/src/platform/models/domain/symmetric-crypto-key.ts index eab4c7b2114..372b869fd9c 100644 --- a/libs/common/src/platform/models/domain/symmetric-crypto-key.ts +++ b/libs/common/src/platform/models/domain/symmetric-crypto-key.ts @@ -38,9 +38,6 @@ export class SymmetricCryptoKey { if (encType === EncryptionType.AesCbc256_B64 && key.byteLength === 32) { this.encKey = key; this.macKey = null; - } else if (encType === EncryptionType.AesCbc128_HmacSha256_B64 && key.byteLength === 32) { - this.encKey = key.slice(0, 16); - this.macKey = key.slice(16, 32); } else if (encType === EncryptionType.AesCbc256_HmacSha256_B64 && key.byteLength === 64) { this.encKey = key.slice(0, 32); this.macKey = key.slice(32, 64); diff --git a/libs/common/src/platform/models/response/server-config.response.ts b/libs/common/src/platform/models/response/server-config.response.ts index cae0603ea1e..afe98c2c349 100644 --- a/libs/common/src/platform/models/response/server-config.response.ts +++ b/libs/common/src/platform/models/response/server-config.response.ts @@ -11,6 +11,7 @@ export class ServerConfigResponse extends BaseResponse { server: ThirdPartyServerConfigResponse; environment: EnvironmentServerConfigResponse; featureStates: { [key: string]: AllowedFeatureFlagTypes } = {}; + push: PushSettingsConfigResponse; settings: ServerSettings; constructor(response: any) { @@ -25,10 +26,27 @@ export class ServerConfigResponse extends BaseResponse { this.server = new ThirdPartyServerConfigResponse(this.getResponseProperty("Server")); this.environment = new EnvironmentServerConfigResponse(this.getResponseProperty("Environment")); this.featureStates = this.getResponseProperty("FeatureStates"); + this.push = new PushSettingsConfigResponse(this.getResponseProperty("Push")); this.settings = new ServerSettings(this.getResponseProperty("Settings")); } } +export class PushSettingsConfigResponse extends BaseResponse { + pushTechnology: number; + vapidPublicKey: string; + + constructor(data: any = null) { + super(data); + + if (data == null) { + return; + } + + this.pushTechnology = this.getResponseProperty("PushTechnology"); + this.vapidPublicKey = this.getResponseProperty("VapidPublicKey"); + } +} + export class EnvironmentServerConfigResponse extends BaseResponse { cloudRegion: Region; vault: string; diff --git a/libs/common/src/platform/notifications/index.ts b/libs/common/src/platform/notifications/index.ts new file mode 100644 index 00000000000..b1b842f5152 --- /dev/null +++ b/libs/common/src/platform/notifications/index.ts @@ -0,0 +1 @@ +export { NotificationsService } from "./notifications.service"; diff --git a/libs/common/src/platform/notifications/internal/default-notifications.service.spec.ts b/libs/common/src/platform/notifications/internal/default-notifications.service.spec.ts new file mode 100644 index 00000000000..bf834e8dd93 --- /dev/null +++ b/libs/common/src/platform/notifications/internal/default-notifications.service.spec.ts @@ -0,0 +1,321 @@ +import { mock, MockProxy } from "jest-mock-extended"; +import { BehaviorSubject, bufferCount, firstValueFrom, ObservedValueOf, Subject } from "rxjs"; + +import { LogoutReason } from "@bitwarden/auth/common"; + +import { awaitAsync } from "../../../../spec"; +import { Matrix } from "../../../../spec/matrix"; +import { AccountService } from "../../../auth/abstractions/account.service"; +import { AuthService } from "../../../auth/abstractions/auth.service"; +import { AuthenticationStatus } from "../../../auth/enums/authentication-status"; +import { NotificationType } from "../../../enums"; +import { NotificationResponse } from "../../../models/response/notification.response"; +import { UserId } from "../../../types/guid"; +import { AppIdService } from "../../abstractions/app-id.service"; +import { Environment, EnvironmentService } from "../../abstractions/environment.service"; +import { LogService } from "../../abstractions/log.service"; +import { MessageSender } from "../../messaging"; +import { SupportStatus } from "../../misc/support-status"; +import { SyncService } from "../../sync"; + +import { + DefaultNotificationsService, + DISABLED_NOTIFICATIONS_URL, +} from "./default-notifications.service"; +import { SignalRConnectionService, SignalRNotification } from "./signalr-connection.service"; +import { WebPushConnectionService, WebPushConnector } from "./webpush-connection.service"; +import { WorkerWebPushConnectionService } from "./worker-webpush-connection.service"; + +describe("NotificationsService", () => { + let syncService: MockProxy; + let appIdService: MockProxy; + let environmentService: MockProxy; + let logoutCallback: jest.Mock, [logoutReason: LogoutReason]>; + let messagingService: MockProxy; + let accountService: MockProxy; + let signalRNotificationConnectionService: MockProxy; + let authService: MockProxy; + let webPushNotificationConnectionService: MockProxy; + + let activeAccount: BehaviorSubject>; + + let environment: BehaviorSubject>; + + let authStatusGetter: (userId: UserId) => BehaviorSubject; + + let webPushSupportGetter: (userId: UserId) => BehaviorSubject>; + + let signalrNotificationGetter: ( + userId: UserId, + notificationsUrl: string, + ) => Subject; + + let sut: DefaultNotificationsService; + + beforeEach(() => { + syncService = mock(); + appIdService = mock(); + environmentService = mock(); + logoutCallback = jest.fn, [logoutReason: LogoutReason]>(); + messagingService = mock(); + accountService = mock(); + signalRNotificationConnectionService = mock(); + authService = mock(); + webPushNotificationConnectionService = mock(); + + activeAccount = new BehaviorSubject>(null); + accountService.activeAccount$ = activeAccount.asObservable(); + + environment = new BehaviorSubject>({ + getNotificationsUrl: () => "https://notifications.bitwarden.com", + } as Environment); + + environmentService.environment$ = environment; + + authStatusGetter = Matrix.autoMockMethod( + authService.authStatusFor$, + () => new BehaviorSubject(AuthenticationStatus.LoggedOut), + ); + + webPushSupportGetter = Matrix.autoMockMethod( + webPushNotificationConnectionService.supportStatus$, + () => + new BehaviorSubject>({ + type: "not-supported", + reason: "test", + }), + ); + + signalrNotificationGetter = Matrix.autoMockMethod( + signalRNotificationConnectionService.connect$, + () => new Subject(), + ); + + sut = new DefaultNotificationsService( + mock(), + syncService, + appIdService, + environmentService, + logoutCallback, + messagingService, + accountService, + signalRNotificationConnectionService, + authService, + webPushNotificationConnectionService, + ); + }); + + const mockUser1 = "user1" as UserId; + const mockUser2 = "user2" as UserId; + + function emitActiveUser(userId: UserId) { + if (userId == null) { + activeAccount.next(null); + } else { + activeAccount.next({ id: userId, email: "email", name: "Test Name", emailVerified: true }); + } + } + + function emitNotificationUrl(url: string) { + environment.next({ + getNotificationsUrl: () => url, + } as Environment); + } + + const expectNotification = ( + notification: readonly [NotificationResponse, UserId], + expectedUser: UserId, + expectedType: NotificationType, + ) => { + const [actualNotification, actualUser] = notification; + expect(actualUser).toBe(expectedUser); + expect(actualNotification.type).toBe(expectedType); + }; + + it("emits notifications through WebPush when supported", async () => { + const notificationsPromise = firstValueFrom(sut.notifications$.pipe(bufferCount(2))); + + emitActiveUser(mockUser1); + emitNotificationUrl("http://test.example.com"); + authStatusGetter(mockUser1).next(AuthenticationStatus.Unlocked); + + const webPush = mock(); + const webPushSubject = new Subject(); + webPush.notifications$ = webPushSubject; + + webPushSupportGetter(mockUser1).next({ type: "supported", service: webPush }); + webPushSubject.next(new NotificationResponse({ type: NotificationType.SyncFolderCreate })); + webPushSubject.next(new NotificationResponse({ type: NotificationType.SyncFolderDelete })); + + const notifications = await notificationsPromise; + expectNotification(notifications[0], mockUser1, NotificationType.SyncFolderCreate); + expectNotification(notifications[1], mockUser1, NotificationType.SyncFolderDelete); + }); + + it("switches to SignalR when web push is not supported.", async () => { + const notificationsPromise = firstValueFrom(sut.notifications$.pipe(bufferCount(2))); + + emitActiveUser(mockUser1); + emitNotificationUrl("http://test.example.com"); + authStatusGetter(mockUser1).next(AuthenticationStatus.Unlocked); + + const webPush = mock(); + const webPushSubject = new Subject(); + webPush.notifications$ = webPushSubject; + + webPushSupportGetter(mockUser1).next({ type: "supported", service: webPush }); + webPushSubject.next(new NotificationResponse({ type: NotificationType.SyncFolderCreate })); + + emitActiveUser(mockUser2); + authStatusGetter(mockUser2).next(AuthenticationStatus.Unlocked); + // Second user does not support web push + webPushSupportGetter(mockUser2).next({ type: "not-supported", reason: "test" }); + + signalrNotificationGetter(mockUser2, "http://test.example.com").next({ + type: "ReceiveMessage", + message: new NotificationResponse({ type: NotificationType.SyncCipherUpdate }), + }); + + const notifications = await notificationsPromise; + expectNotification(notifications[0], mockUser1, NotificationType.SyncFolderCreate); + expectNotification(notifications[1], mockUser2, NotificationType.SyncCipherUpdate); + }); + + it("switches to WebPush when it becomes supported.", async () => { + const notificationsPromise = firstValueFrom(sut.notifications$.pipe(bufferCount(2))); + + emitActiveUser(mockUser1); + emitNotificationUrl("http://test.example.com"); + authStatusGetter(mockUser1).next(AuthenticationStatus.Unlocked); + webPushSupportGetter(mockUser1).next({ type: "not-supported", reason: "test" }); + + signalrNotificationGetter(mockUser1, "http://test.example.com").next({ + type: "ReceiveMessage", + message: new NotificationResponse({ type: NotificationType.AuthRequest }), + }); + + const webPush = mock(); + const webPushSubject = new Subject(); + webPush.notifications$ = webPushSubject; + + webPushSupportGetter(mockUser1).next({ type: "supported", service: webPush }); + webPushSubject.next(new NotificationResponse({ type: NotificationType.SyncLoginDelete })); + + const notifications = await notificationsPromise; + expectNotification(notifications[0], mockUser1, NotificationType.AuthRequest); + expectNotification(notifications[1], mockUser1, NotificationType.SyncLoginDelete); + }); + + it("does not emit SignalR heartbeats", async () => { + const notificationsPromise = firstValueFrom(sut.notifications$.pipe(bufferCount(1))); + + emitActiveUser(mockUser1); + emitNotificationUrl("http://test.example.com"); + authStatusGetter(mockUser1).next(AuthenticationStatus.Unlocked); + webPushSupportGetter(mockUser1).next({ type: "not-supported", reason: "test" }); + + signalrNotificationGetter(mockUser1, "http://test.example.com").next({ type: "Heartbeat" }); + signalrNotificationGetter(mockUser1, "http://test.example.com").next({ + type: "ReceiveMessage", + message: new NotificationResponse({ type: NotificationType.AuthRequestResponse }), + }); + + const notifications = await notificationsPromise; + expectNotification(notifications[0], mockUser1, NotificationType.AuthRequestResponse); + }); + + it.each([ + // Temporarily rolling back notifications being connected while locked + // { initialStatus: AuthenticationStatus.Locked, updatedStatus: AuthenticationStatus.Unlocked }, + // { initialStatus: AuthenticationStatus.Unlocked, updatedStatus: AuthenticationStatus.Locked }, + // { initialStatus: AuthenticationStatus.Locked, updatedStatus: AuthenticationStatus.Locked }, + { initialStatus: AuthenticationStatus.Unlocked, updatedStatus: AuthenticationStatus.Unlocked }, + ])( + "does not re-connect when the user transitions from $initialStatus to $updatedStatus", + async ({ initialStatus, updatedStatus }) => { + emitActiveUser(mockUser1); + emitNotificationUrl("http://test.example.com"); + authStatusGetter(mockUser1).next(initialStatus); + webPushSupportGetter(mockUser1).next({ type: "not-supported", reason: "test" }); + + const notificationsSubscriptions = sut.notifications$.subscribe(); + await awaitAsync(1); + + authStatusGetter(mockUser1).next(updatedStatus); + await awaitAsync(1); + + expect(signalRNotificationConnectionService.connect$).toHaveBeenCalledTimes(1); + expect(signalRNotificationConnectionService.connect$).toHaveBeenCalledWith( + mockUser1, + "http://test.example.com", + ); + notificationsSubscriptions.unsubscribe(); + }, + ); + + it.each([ + // Temporarily disabling notifications connecting while in a locked state + // AuthenticationStatus.Locked, + AuthenticationStatus.Unlocked, + ])( + "connects when a user transitions from logged out to %s", + async (newStatus: AuthenticationStatus) => { + emitActiveUser(mockUser1); + emitNotificationUrl("http://test.example.com"); + authStatusGetter(mockUser1).next(AuthenticationStatus.LoggedOut); + webPushSupportGetter(mockUser1).next({ type: "not-supported", reason: "test" }); + + const notificationsSubscriptions = sut.notifications$.subscribe(); + await awaitAsync(1); + + authStatusGetter(mockUser1).next(newStatus); + await awaitAsync(1); + + expect(signalRNotificationConnectionService.connect$).toHaveBeenCalledTimes(1); + expect(signalRNotificationConnectionService.connect$).toHaveBeenCalledWith( + mockUser1, + "http://test.example.com", + ); + notificationsSubscriptions.unsubscribe(); + }, + ); + + it("does not connect to any notification stream when notifications are disabled through special url", () => { + const subscription = sut.notifications$.subscribe(); + emitActiveUser(mockUser1); + emitNotificationUrl(DISABLED_NOTIFICATIONS_URL); + + expect(signalRNotificationConnectionService.connect$).not.toHaveBeenCalled(); + expect(webPushNotificationConnectionService.supportStatus$).not.toHaveBeenCalled(); + + subscription.unsubscribe(); + }); + + it("does not connect to any notification stream when there is no active user", () => { + const subscription = sut.notifications$.subscribe(); + emitActiveUser(null); + + expect(signalRNotificationConnectionService.connect$).not.toHaveBeenCalled(); + expect(webPushNotificationConnectionService.supportStatus$).not.toHaveBeenCalled(); + + subscription.unsubscribe(); + }); + + it("does not reconnect if the same notification url is emitted", async () => { + const subscription = sut.notifications$.subscribe(); + + emitActiveUser(mockUser1); + emitNotificationUrl("http://test.example.com"); + authStatusGetter(mockUser1).next(AuthenticationStatus.Unlocked); + + await awaitAsync(1); + + expect(webPushNotificationConnectionService.supportStatus$).toHaveBeenCalledTimes(1); + emitNotificationUrl("http://test.example.com"); + + await awaitAsync(1); + + expect(webPushNotificationConnectionService.supportStatus$).toHaveBeenCalledTimes(1); + subscription.unsubscribe(); + }); +}); diff --git a/libs/common/src/platform/notifications/internal/default-notifications.service.ts b/libs/common/src/platform/notifications/internal/default-notifications.service.ts new file mode 100644 index 00000000000..fc505b018ce --- /dev/null +++ b/libs/common/src/platform/notifications/internal/default-notifications.service.ts @@ -0,0 +1,242 @@ +import { + BehaviorSubject, + catchError, + distinctUntilChanged, + EMPTY, + filter, + map, + mergeMap, + Observable, + switchMap, +} from "rxjs"; + +import { LogoutReason } from "@bitwarden/auth/common"; + +import { AccountService } from "../../../auth/abstractions/account.service"; +import { AuthService } from "../../../auth/abstractions/auth.service"; +import { AuthenticationStatus } from "../../../auth/enums/authentication-status"; +import { NotificationType } from "../../../enums"; +import { + NotificationResponse, + SyncCipherNotification, + SyncFolderNotification, + SyncSendNotification, +} from "../../../models/response/notification.response"; +import { UserId } from "../../../types/guid"; +import { SyncService } from "../../../vault/abstractions/sync/sync.service.abstraction"; +import { AppIdService } from "../../abstractions/app-id.service"; +import { EnvironmentService } from "../../abstractions/environment.service"; +import { LogService } from "../../abstractions/log.service"; +import { MessagingService } from "../../abstractions/messaging.service"; +import { supportSwitch } from "../../misc/support-status"; +import { NotificationsService as NotificationsServiceAbstraction } from "../notifications.service"; + +import { ReceiveMessage, SignalRConnectionService } from "./signalr-connection.service"; +import { WebPushConnectionService } from "./webpush-connection.service"; + +export const DISABLED_NOTIFICATIONS_URL = "http://-"; + +export class DefaultNotificationsService implements NotificationsServiceAbstraction { + notifications$: Observable; + + private activitySubject = new BehaviorSubject<"active" | "inactive">("active"); + + constructor( + private readonly logService: LogService, + private syncService: SyncService, + private appIdService: AppIdService, + private environmentService: EnvironmentService, + private logoutCallback: (logoutReason: LogoutReason, userId: UserId) => Promise, + private messagingService: MessagingService, + private readonly accountService: AccountService, + private readonly signalRConnectionService: SignalRConnectionService, + private readonly authService: AuthService, + private readonly webPushConnectionService: WebPushConnectionService, + ) { + this.notifications$ = this.accountService.activeAccount$.pipe( + map((account) => account?.id), + distinctUntilChanged(), + switchMap((activeAccountId) => { + if (activeAccountId == null) { + // We don't emit notifications for inactive accounts currently + return EMPTY; + } + + return this.userNotifications$(activeAccountId).pipe( + map((notification) => [notification, activeAccountId] as const), + ); + }), + ); + } + + /** + * Retrieves a stream of push notifications for the given user. + * @param userId The user id of the user to get the push notifications for. + */ + private userNotifications$(userId: UserId) { + return this.environmentService.environment$.pipe( + map((env) => env.getNotificationsUrl()), + distinctUntilChanged(), + switchMap((notificationsUrl) => { + if (notificationsUrl === DISABLED_NOTIFICATIONS_URL) { + return EMPTY; + } + + return this.userNotificationsHelper$(userId, notificationsUrl); + }), + ); + } + + private userNotificationsHelper$(userId: UserId, notificationsUrl: string) { + return this.hasAccessToken$(userId).pipe( + switchMap((hasAccessToken) => { + if (!hasAccessToken) { + return EMPTY; + } + + return this.activitySubject; + }), + switchMap((activityStatus) => { + if (activityStatus === "inactive") { + return EMPTY; + } + + return this.webPushConnectionService.supportStatus$(userId); + }), + supportSwitch({ + supported: (service) => + service.notifications$.pipe( + catchError((err: unknown) => { + this.logService.warning("Issue with web push, falling back to SignalR", err); + return this.connectSignalR$(userId, notificationsUrl); + }), + ), + notSupported: () => this.connectSignalR$(userId, notificationsUrl), + }), + ); + } + + private connectSignalR$(userId: UserId, notificationsUrl: string) { + return this.signalRConnectionService.connect$(userId, notificationsUrl).pipe( + filter((n) => n.type === "ReceiveMessage"), + map((n) => (n as ReceiveMessage).message), + ); + } + + // This method name is a lie currently as we also have an access token + // when locked, this is eventually where we want to be but it increases load + // on signalR so we are rolling back until we can move the load of browser to + // web push. + private hasAccessToken$(userId: UserId) { + return this.authService.authStatusFor$(userId).pipe( + map((authStatus) => authStatus === AuthenticationStatus.Unlocked), + distinctUntilChanged(), + ); + } + + private async processNotification(notification: NotificationResponse, userId: UserId) { + const appId = await this.appIdService.getAppId(); + if (notification == null || notification.contextId === appId) { + return; + } + + const payloadUserId = notification.payload?.userId || notification.payload?.UserId; + if (payloadUserId != null && payloadUserId !== userId) { + return; + } + + switch (notification.type) { + case NotificationType.SyncCipherCreate: + case NotificationType.SyncCipherUpdate: + await this.syncService.syncUpsertCipher( + notification.payload as SyncCipherNotification, + notification.type === NotificationType.SyncCipherUpdate, + payloadUserId, + ); + break; + case NotificationType.SyncCipherDelete: + case NotificationType.SyncLoginDelete: + await this.syncService.syncDeleteCipher( + notification.payload as SyncCipherNotification, + payloadUserId, + ); + break; + case NotificationType.SyncFolderCreate: + case NotificationType.SyncFolderUpdate: + await this.syncService.syncUpsertFolder( + notification.payload as SyncFolderNotification, + notification.type === NotificationType.SyncFolderUpdate, + userId, + ); + break; + case NotificationType.SyncFolderDelete: + await this.syncService.syncDeleteFolder( + notification.payload as SyncFolderNotification, + userId, + ); + break; + case NotificationType.SyncVault: + case NotificationType.SyncCiphers: + case NotificationType.SyncSettings: + await this.syncService.fullSync(false); + + break; + case NotificationType.SyncOrganizations: + // An organization update may not have bumped the user's account revision date, so force a sync + await this.syncService.fullSync(true); + break; + case NotificationType.SyncOrgKeys: + await this.syncService.fullSync(true); + this.activitySubject.next("inactive"); // Force a disconnect + this.activitySubject.next("active"); // Allow a reconnect + break; + case NotificationType.LogOut: + this.logService.info("[Notifications Service] Received logout notification"); + await this.logoutCallback("logoutNotification", userId); + break; + case NotificationType.SyncSendCreate: + case NotificationType.SyncSendUpdate: + await this.syncService.syncUpsertSend( + notification.payload as SyncSendNotification, + notification.type === NotificationType.SyncSendUpdate, + ); + break; + case NotificationType.SyncSendDelete: + await this.syncService.syncDeleteSend(notification.payload as SyncSendNotification); + break; + case NotificationType.AuthRequest: + { + this.messagingService.send("openLoginApproval", { + notificationId: notification.payload.id, + }); + } + break; + case NotificationType.SyncOrganizationStatusChanged: + await this.syncService.fullSync(true); + break; + case NotificationType.SyncOrganizationCollectionSettingChanged: + await this.syncService.fullSync(true); + break; + default: + break; + } + } + + startListening() { + return this.notifications$ + .pipe( + mergeMap(async ([notification, userId]) => this.processNotification(notification, userId)), + ) + .subscribe({ + error: (e: unknown) => this.logService.warning("Error in notifications$ observable", e), + }); + } + + reconnectFromActivity(): void { + this.activitySubject.next("active"); + } + + disconnectFromInactivity(): void { + this.activitySubject.next("inactive"); + } +} diff --git a/libs/common/src/platform/notifications/internal/index.ts b/libs/common/src/platform/notifications/internal/index.ts new file mode 100644 index 00000000000..067320ee56c --- /dev/null +++ b/libs/common/src/platform/notifications/internal/index.ts @@ -0,0 +1,8 @@ +export * from "./worker-webpush-connection.service"; +export * from "./signalr-connection.service"; +export * from "./default-notifications.service"; +export * from "./noop-notifications.service"; +export * from "./unsupported-webpush-connection.service"; +export * from "./webpush-connection.service"; +export * from "./websocket-webpush-connection.service"; +export * from "./web-push-notifications-api.service"; diff --git a/libs/common/src/platform/notifications/internal/noop-notifications.service.ts b/libs/common/src/platform/notifications/internal/noop-notifications.service.ts new file mode 100644 index 00000000000..f79cabfca8a --- /dev/null +++ b/libs/common/src/platform/notifications/internal/noop-notifications.service.ts @@ -0,0 +1,23 @@ +import { Subscription } from "rxjs"; + +import { LogService } from "../../abstractions/log.service"; +import { NotificationsService } from "../notifications.service"; + +export class NoopNotificationsService implements NotificationsService { + constructor(private logService: LogService) {} + + startListening(): Subscription { + this.logService.info( + "Initializing no-op notification service, no push notifications will be received", + ); + return Subscription.EMPTY; + } + + reconnectFromActivity(): void { + this.logService.info("Reconnecting notification service from activity"); + } + + disconnectFromInactivity(): void { + this.logService.info("Disconnecting notification service from inactivity"); + } +} diff --git a/libs/common/src/platform/notifications/internal/signalr-connection.service.ts b/libs/common/src/platform/notifications/internal/signalr-connection.service.ts new file mode 100644 index 00000000000..e5d210266c0 --- /dev/null +++ b/libs/common/src/platform/notifications/internal/signalr-connection.service.ts @@ -0,0 +1,125 @@ +import { + HttpTransportType, + HubConnectionBuilder, + HubConnectionState, + ILogger, + LogLevel, +} from "@microsoft/signalr"; +import { MessagePackHubProtocol } from "@microsoft/signalr-protocol-msgpack"; +import { Observable, Subscription } from "rxjs"; + +import { ApiService } from "../../../abstractions/api.service"; +import { NotificationResponse } from "../../../models/response/notification.response"; +import { UserId } from "../../../types/guid"; +import { LogService } from "../../abstractions/log.service"; + +// 2 Minutes +const MIN_RECONNECT_TIME = 2 * 60 * 1000; +// 5 Minutes +const MAX_RECONNECT_TIME = 5 * 60 * 1000; + +export type Heartbeat = { type: "Heartbeat" }; +export type ReceiveMessage = { type: "ReceiveMessage"; message: NotificationResponse }; + +export type SignalRNotification = Heartbeat | ReceiveMessage; + +class SignalRLogger implements ILogger { + constructor(private readonly logService: LogService) {} + + log(logLevel: LogLevel, message: string): void { + switch (logLevel) { + case LogLevel.Critical: + this.logService.error(message); + break; + case LogLevel.Error: + this.logService.error(message); + break; + case LogLevel.Warning: + this.logService.warning(message); + break; + case LogLevel.Information: + this.logService.info(message); + break; + case LogLevel.Debug: + this.logService.debug(message); + break; + } + } +} + +export class SignalRConnectionService { + constructor( + private readonly apiService: ApiService, + private readonly logService: LogService, + ) {} + + connect$(userId: UserId, notificationsUrl: string) { + return new Observable((subsciber) => { + const connection = new HubConnectionBuilder() + .withUrl(notificationsUrl + "/hub", { + accessTokenFactory: () => this.apiService.getActiveBearerToken(), + skipNegotiation: true, + transport: HttpTransportType.WebSockets, + }) + .withHubProtocol(new MessagePackHubProtocol()) + .configureLogging(new SignalRLogger(this.logService)) + .build(); + + connection.on("ReceiveMessage", (data: any) => { + subsciber.next({ type: "ReceiveMessage", message: new NotificationResponse(data) }); + }); + + connection.on("Heartbeat", () => { + subsciber.next({ type: "Heartbeat" }); + }); + + let reconnectSubscription: Subscription | null = null; + + // Create schedule reconnect function + const scheduleReconnect = (): Subscription => { + if ( + connection == null || + connection.state !== HubConnectionState.Disconnected || + (reconnectSubscription != null && !reconnectSubscription.closed) + ) { + return Subscription.EMPTY; + } + + const randomTime = this.random(); + const timeoutHandler = setTimeout(() => { + connection + .start() + .then(() => (reconnectSubscription = null)) + .catch(() => { + reconnectSubscription = scheduleReconnect(); + }); + }, randomTime); + + return new Subscription(() => clearTimeout(timeoutHandler)); + }; + + connection.onclose((error) => { + reconnectSubscription = scheduleReconnect(); + }); + + // Start connection + connection.start().catch(() => { + reconnectSubscription = scheduleReconnect(); + }); + + return () => { + connection?.stop().catch((error) => { + this.logService.error("Error while stopping SignalR connection", error); + // TODO: Does calling stop call `onclose`? + reconnectSubscription?.unsubscribe(); + }); + }; + }); + } + + private random() { + return ( + Math.floor(Math.random() * (MAX_RECONNECT_TIME - MIN_RECONNECT_TIME + 1)) + MIN_RECONNECT_TIME + ); + } +} diff --git a/libs/common/src/platform/notifications/internal/unsupported-webpush-connection.service.ts b/libs/common/src/platform/notifications/internal/unsupported-webpush-connection.service.ts new file mode 100644 index 00000000000..0016a882949 --- /dev/null +++ b/libs/common/src/platform/notifications/internal/unsupported-webpush-connection.service.ts @@ -0,0 +1,15 @@ +import { Observable, of } from "rxjs"; + +import { UserId } from "../../../types/guid"; +import { SupportStatus } from "../../misc/support-status"; + +import { WebPushConnectionService, WebPushConnector } from "./webpush-connection.service"; + +/** + * An implementation of {@see WebPushConnectionService} for clients that do not have support for WebPush + */ +export class UnsupportedWebPushConnectionService implements WebPushConnectionService { + supportStatus$(userId: UserId): Observable> { + return of({ type: "not-supported", reason: "client-not-supported" }); + } +} diff --git a/libs/common/src/platform/notifications/internal/web-push-notifications-api.service.ts b/libs/common/src/platform/notifications/internal/web-push-notifications-api.service.ts new file mode 100644 index 00000000000..b824b8c7d65 --- /dev/null +++ b/libs/common/src/platform/notifications/internal/web-push-notifications-api.service.ts @@ -0,0 +1,25 @@ +import { ApiService } from "../../../abstractions/api.service"; +import { AppIdService } from "../../abstractions/app-id.service"; + +import { WebPushRequest } from "./web-push.request"; + +export class WebPushNotificationsApiService { + constructor( + private readonly apiService: ApiService, + private readonly appIdService: AppIdService, + ) {} + + /** + * Posts a device-user association to the server and ensures it's installed for push notifications + */ + async putSubscription(pushSubscription: PushSubscriptionJSON): Promise { + const request = WebPushRequest.from(pushSubscription); + await this.apiService.send( + "POST", + `/devices/identifier/${await this.appIdService.getAppId()}/web-push-auth`, + request, + true, + false, + ); + } +} diff --git a/libs/common/src/platform/notifications/internal/web-push.request.ts b/libs/common/src/platform/notifications/internal/web-push.request.ts new file mode 100644 index 00000000000..c6375986324 --- /dev/null +++ b/libs/common/src/platform/notifications/internal/web-push.request.ts @@ -0,0 +1,13 @@ +export class WebPushRequest { + endpoint: string | undefined; + p256dh: string | undefined; + auth: string | undefined; + + static from(pushSubscription: PushSubscriptionJSON): WebPushRequest { + const result = new WebPushRequest(); + result.endpoint = pushSubscription.endpoint; + result.p256dh = pushSubscription.keys?.p256dh; + result.auth = pushSubscription.keys?.auth; + return result; + } +} diff --git a/libs/common/src/platform/notifications/internal/webpush-connection.service.ts b/libs/common/src/platform/notifications/internal/webpush-connection.service.ts new file mode 100644 index 00000000000..17ef87ea83e --- /dev/null +++ b/libs/common/src/platform/notifications/internal/webpush-connection.service.ts @@ -0,0 +1,13 @@ +import { Observable } from "rxjs"; + +import { NotificationResponse } from "../../../models/response/notification.response"; +import { UserId } from "../../../types/guid"; +import { SupportStatus } from "../../misc/support-status"; + +export interface WebPushConnector { + notifications$: Observable; +} + +export abstract class WebPushConnectionService { + abstract supportStatus$(userId: UserId): Observable>; +} diff --git a/libs/common/src/platform/notifications/internal/websocket-webpush-connection.service.ts b/libs/common/src/platform/notifications/internal/websocket-webpush-connection.service.ts new file mode 100644 index 00000000000..7a25fb4ce50 --- /dev/null +++ b/libs/common/src/platform/notifications/internal/websocket-webpush-connection.service.ts @@ -0,0 +1,12 @@ +import { Observable, of } from "rxjs"; + +import { UserId } from "../../../types/guid"; +import { SupportStatus } from "../../misc/support-status"; + +import { WebPushConnectionService, WebPushConnector } from "./webpush-connection.service"; + +export class WebSocketWebPushConnectionService implements WebPushConnectionService { + supportStatus$(userId: UserId): Observable> { + return of({ type: "not-supported", reason: "work-in-progress" }); + } +} diff --git a/libs/common/src/platform/notifications/internal/worker-webpush-connection.service.ts b/libs/common/src/platform/notifications/internal/worker-webpush-connection.service.ts new file mode 100644 index 00000000000..74981b6782f --- /dev/null +++ b/libs/common/src/platform/notifications/internal/worker-webpush-connection.service.ts @@ -0,0 +1,170 @@ +import { + concat, + concatMap, + defer, + distinctUntilChanged, + fromEvent, + map, + Observable, + Subject, + Subscription, + switchMap, +} from "rxjs"; + +import { PushTechnology } from "../../../enums/push-technology.enum"; +import { NotificationResponse } from "../../../models/response/notification.response"; +import { UserId } from "../../../types/guid"; +import { ConfigService } from "../../abstractions/config/config.service"; +import { SupportStatus } from "../../misc/support-status"; +import { Utils } from "../../misc/utils"; + +import { WebPushNotificationsApiService } from "./web-push-notifications-api.service"; +import { WebPushConnectionService, WebPushConnector } from "./webpush-connection.service"; + +// Ref: https://w3c.github.io/push-api/#the-pushsubscriptionchange-event +interface PushSubscriptionChangeEvent { + readonly newSubscription?: PushSubscription; + readonly oldSubscription?: PushSubscription; +} + +// Ref: https://developer.mozilla.org/en-US/docs/Web/API/PushMessageData +interface PushMessageData { + json(): any; +} + +// Ref: https://developer.mozilla.org/en-US/docs/Web/API/PushEvent +interface PushEvent { + data: PushMessageData; +} + +/** + * An implementation for connecting to web push based notifications running in a Worker. + */ +export class WorkerWebPushConnectionService implements WebPushConnectionService { + private pushEvent = new Subject(); + private pushChangeEvent = new Subject(); + + constructor( + private readonly configService: ConfigService, + private readonly webPushApiService: WebPushNotificationsApiService, + private readonly serviceWorkerRegistration: ServiceWorkerRegistration, + ) {} + + start(): Subscription { + const subscription = new Subscription(() => { + this.pushEvent.complete(); + this.pushChangeEvent.complete(); + this.pushEvent = new Subject(); + this.pushChangeEvent = new Subject(); + }); + + const pushEventSubscription = fromEvent(self, "push").subscribe(this.pushEvent); + + const pushChangeEventSubscription = fromEvent( + self, + "pushsubscriptionchange", + ).subscribe(this.pushChangeEvent); + + subscription.add(pushEventSubscription); + subscription.add(pushChangeEventSubscription); + + return subscription; + } + + supportStatus$(userId: UserId): Observable> { + // Check the server config to see if it supports sending WebPush notifications + // FIXME: get config of server for the specified userId, once ConfigService supports it + return this.configService.serverConfig$.pipe( + map((config) => + config?.push?.pushTechnology === PushTechnology.WebPush ? config.push.vapidPublicKey : null, + ), + // No need to re-emit when there is new server config if the vapidPublicKey is still there and the exact same + distinctUntilChanged(), + map((publicKey) => { + if (publicKey == null) { + return { + type: "not-supported", + reason: "server-not-configured", + } satisfies SupportStatus; + } + + return { + type: "supported", + service: new MyWebPushConnector( + publicKey, + userId, + this.webPushApiService, + this.serviceWorkerRegistration, + this.pushEvent, + this.pushChangeEvent, + ), + } satisfies SupportStatus; + }), + ); + } +} + +class MyWebPushConnector implements WebPushConnector { + notifications$: Observable; + + constructor( + private readonly vapidPublicKey: string, + private readonly userId: UserId, + private readonly webPushApiService: WebPushNotificationsApiService, + private readonly serviceWorkerRegistration: ServiceWorkerRegistration, + private readonly pushEvent$: Observable, + private readonly pushChangeEvent$: Observable, + ) { + this.notifications$ = this.getOrCreateSubscription$(this.vapidPublicKey).pipe( + concatMap((subscription) => { + return defer(() => { + if (subscription == null) { + throw new Error("Expected a non-null subscription."); + } + return this.webPushApiService.putSubscription(subscription.toJSON()); + }).pipe( + switchMap(() => this.pushEvent$), + map((e) => { + return new NotificationResponse(e.data.json().data); + }), + ); + }), + ); + } + + private async pushManagerSubscribe(key: string) { + return await this.serviceWorkerRegistration.pushManager.subscribe({ + userVisibleOnly: true, + applicationServerKey: key, + }); + } + + private getOrCreateSubscription$(key: string) { + return concat( + defer(async () => { + const existingSubscription = + await this.serviceWorkerRegistration.pushManager.getSubscription(); + + if (existingSubscription == null) { + return await this.pushManagerSubscribe(key); + } + + const subscriptionKey = Utils.fromBufferToUrlB64( + // REASON: `Utils.fromBufferToUrlB64` handles null by returning null back to it. + // its annotation should be updated and then this assertion can be removed. + // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain + existingSubscription.options?.applicationServerKey!, + ); + + if (subscriptionKey !== key) { + // There is a subscription, but it's not for the current server, unsubscribe and then make a new one + await existingSubscription.unsubscribe(); + return await this.pushManagerSubscribe(key); + } + + return existingSubscription; + }), + this.pushChangeEvent$.pipe(map((event) => event.newSubscription)), + ); + } +} diff --git a/libs/common/src/platform/notifications/notifications.service.ts b/libs/common/src/platform/notifications/notifications.service.ts new file mode 100644 index 00000000000..aa4ff2a57a6 --- /dev/null +++ b/libs/common/src/platform/notifications/notifications.service.ts @@ -0,0 +1,18 @@ +import { Subscription } from "rxjs"; + +/** + * A service offering abilities to interact with push notifications from the server. + */ +export abstract class NotificationsService { + /** + * Starts automatic listening and processing of notifications, should only be called once per application, + * or you will risk notifications being processed multiple times. + */ + abstract startListening(): Subscription; + // TODO: Delete this method in favor of an `ActivityService` that notifications can depend on. + // https://bitwarden.atlassian.net/browse/PM-14264 + abstract reconnectFromActivity(): void; + // TODO: Delete this method in favor of an `ActivityService` that notifications can depend on. + // https://bitwarden.atlassian.net/browse/PM-14264 + abstract disconnectFromInactivity(): void; +} diff --git a/libs/common/src/platform/scheduling/index.ts b/libs/common/src/platform/scheduling/index.ts index e5f10ca3baf..c9159244ff3 100644 --- a/libs/common/src/platform/scheduling/index.ts +++ b/libs/common/src/platform/scheduling/index.ts @@ -1,3 +1,3 @@ -export { TaskSchedulerService } from "./task-scheduler.service"; +export { TaskSchedulerService, toScheduler } from "./task-scheduler.service"; export { DefaultTaskSchedulerService } from "./default-task-scheduler.service"; export { ScheduledTaskNames, ScheduledTaskName } from "./scheduled-task-name.enum"; diff --git a/libs/common/src/platform/scheduling/scheduled-task-name.enum.ts b/libs/common/src/platform/scheduling/scheduled-task-name.enum.ts index 2c0ffc87eb7..990a4c77c94 100644 --- a/libs/common/src/platform/scheduling/scheduled-task-name.enum.ts +++ b/libs/common/src/platform/scheduling/scheduled-task-name.enum.ts @@ -7,6 +7,7 @@ export const ScheduledTaskNames = { scheduleNextSyncInterval: "scheduleNextSyncInterval", eventUploadsInterval: "eventUploadsInterval", vaultTimeoutCheckInterval: "vaultTimeoutCheckInterval", + clearPopupViewCache: "clearPopupViewCache", } as const; export type ScheduledTaskName = (typeof ScheduledTaskNames)[keyof typeof ScheduledTaskNames]; diff --git a/libs/common/src/platform/scheduling/task-scheduler.service.ts b/libs/common/src/platform/scheduling/task-scheduler.service.ts index a38a03e0076..35a577dda7d 100644 --- a/libs/common/src/platform/scheduling/task-scheduler.service.ts +++ b/libs/common/src/platform/scheduling/task-scheduler.service.ts @@ -1,9 +1,68 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Subscription } from "rxjs"; +import { asyncScheduler, SchedulerLike, Subscription } from "rxjs"; import { ScheduledTaskName } from "./scheduled-task-name.enum"; +/** + * Creates a RXJS scheduler based on a {@link TaskSchedulerService}. + * + * @description This API defers to `TaskSchedulerService` to schedule a task to be ran + * in the future but the task that is ran is NOT the remainder of your RXJS pipeline. The + * task you want ran must instead be registered in a location reachable on a service worker + * startup (on browser). An example of an acceptible location is the constructor of a service + * you know is created in `MainBackground`. Uses of this API is other clients _can_ have the + * `registerTaskHandler` call in more places, but in order to have it work across clients + * it is recommended to register it according to the rules of browser. + * + * @link https://rxjs.dev/guide/scheduler#using-schedulers + * + * @example + * ```ts + * class MyService { + * constructor(messageListener: MessageListener, taskScheduler: TaskSchedulerService) { + * // VERY IMPORTANT! + * this.taskSchedulerService.registerTaskHandler(SchedulerTaskNames.myTaskName, async () => { + * await this.runEvent(); + * }); + * + * messageListener.messages$(MY_MESSAGE).pipe( + * debounceTime( + * 10 * 1000, + * toScheduler(taskScheduler, ShedulerTaskNames.myTaskName), + * ), + * switchMap(() => this.runEvent()), + * ) + * } + * } + * ``` + * + * @param taskScheduler The task scheduler service to use to shedule RXJS work. + * @param taskName The name of the task that the handler should be registered and scheduled based on. + * @returns A SchedulerLike object that can be passed in to RXJS operators like `delay` and `timeout`. + */ +export function toScheduler( + taskScheduler: TaskSchedulerService, + taskName: ScheduledTaskName, +): SchedulerLike { + return new TaskSchedulerSheduler(taskScheduler, taskName); +} + +class TaskSchedulerSheduler implements SchedulerLike { + constructor( + private readonly taskSchedulerService: TaskSchedulerService, + private readonly taskName: ScheduledTaskName, + ) {} + + schedule(work: (state?: T) => void, delay?: number, state?: T): Subscription { + return this.taskSchedulerService.setTimeout(this.taskName, delay ?? 0); + } + + now(): number { + return asyncScheduler.now(); + } +} + export abstract class TaskSchedulerService { protected taskHandlers: Map void>; abstract setTimeout(taskName: ScheduledTaskName, delayInMs: number): Subscription; diff --git a/libs/common/src/platform/services/config/config.service.spec.ts b/libs/common/src/platform/services/config/config.service.spec.ts index 369338f945f..ea3b56a32f1 100644 --- a/libs/common/src/platform/services/config/config.service.spec.ts +++ b/libs/common/src/platform/services/config/config.service.spec.ts @@ -361,8 +361,6 @@ describe("ConfigService", () => { const configs = await firstValueFrom(sut.serverConfig$.pipe(bufferCount(2))); - await jest.runOnlyPendingTimersAsync(); - expect(configs[0].gitHash).toBe("existing-data"); expect(configs[1].gitHash).toBe("slow-response"); }); diff --git a/libs/common/src/platform/services/container.service.ts b/libs/common/src/platform/services/container.service.ts index 6022e097ab0..1428b2bbd7c 100644 --- a/libs/common/src/platform/services/container.service.ts +++ b/libs/common/src/platform/services/container.service.ts @@ -1,5 +1,6 @@ -import { KeyService } from "../../../../key-management/src/abstractions/key.service"; -import { EncryptService } from "../abstractions/encrypt.service"; +import { KeyService } from "@bitwarden/key-management"; + +import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service"; export class ContainerService { constructor( diff --git a/libs/common/src/platform/services/default-environment.service.spec.ts b/libs/common/src/platform/services/default-environment.service.spec.ts index 870f887c160..553f80f83b8 100644 --- a/libs/common/src/platform/services/default-environment.service.spec.ts +++ b/libs/common/src/platform/services/default-environment.service.spec.ts @@ -304,85 +304,21 @@ describe("EnvironmentService", () => { }); }); - describe("getEnvironment", () => { + describe("getEnvironment$", () => { it.each([ { region: Region.US, expectedHost: "bitwarden.com" }, { region: Region.EU, expectedHost: "bitwarden.eu" }, - ])("gets it from user data if there is an active user", async ({ region, expectedHost }) => { - setGlobalData(Region.US, new EnvironmentUrls()); - setUserData(region, new EnvironmentUrls()); + ])("gets it from the passed in userId: %s", async ({ region, expectedHost }) => { + setUserData(Region.US, new EnvironmentUrls()); + setUserData(region, new EnvironmentUrls(), alternateTestUser); await switchUser(testUser); - const env = await firstValueFrom(sut.getEnvironment$()); - expect(env.getHostname()).toBe(expectedHost); + const env = await firstValueFrom(sut.getEnvironment$(alternateTestUser)); + expect(env?.getHostname()).toBe(expectedHost); }); - it.each([ - { region: Region.US, expectedHost: "bitwarden.com" }, - { region: Region.EU, expectedHost: "bitwarden.eu" }, - ])("gets it from global data if there is no active user", async ({ region, expectedHost }) => { - setGlobalData(region, new EnvironmentUrls()); - setUserData(Region.US, new EnvironmentUrls()); - - const env = await firstValueFrom(sut.getEnvironment$()); - expect(env.getHostname()).toBe(expectedHost); - }); - - it.each([ - { region: Region.US, expectedHost: "bitwarden.com" }, - { region: Region.EU, expectedHost: "bitwarden.eu" }, - ])( - "gets it from global state if there is no active user even if a user id is passed in.", - async ({ region, expectedHost }) => { - setGlobalData(region, new EnvironmentUrls()); - setUserData(Region.US, new EnvironmentUrls()); - - const env = await firstValueFrom(sut.getEnvironment$(testUser)); - expect(env.getHostname()).toBe(expectedHost); - }, - ); - - it.each([ - { region: Region.US, expectedHost: "bitwarden.com" }, - { region: Region.EU, expectedHost: "bitwarden.eu" }, - ])( - "gets it from the passed in userId if there is any active user: %s", - async ({ region, expectedHost }) => { - setGlobalData(Region.US, new EnvironmentUrls()); - setUserData(Region.US, new EnvironmentUrls()); - setUserData(region, new EnvironmentUrls(), alternateTestUser); - - await switchUser(testUser); - - const env = await firstValueFrom(sut.getEnvironment$(alternateTestUser)); - expect(env.getHostname()).toBe(expectedHost); - }, - ); - - it("gets it from base url saved in self host config", async () => { - const globalSelfHostUrls = new EnvironmentUrls(); - globalSelfHostUrls.base = "https://base.example.com"; - setGlobalData(Region.SelfHosted, globalSelfHostUrls); - setUserData(Region.EU, new EnvironmentUrls()); - - const env = await firstValueFrom(sut.getEnvironment$()); - expect(env.getHostname()).toBe("base.example.com"); - }); - - it("gets it from webVault url saved in self host config", async () => { - const globalSelfHostUrls = new EnvironmentUrls(); - globalSelfHostUrls.webVault = "https://vault.example.com"; - globalSelfHostUrls.base = "https://base.example.com"; - setGlobalData(Region.SelfHosted, globalSelfHostUrls); - setUserData(Region.EU, new EnvironmentUrls()); - - const env = await firstValueFrom(sut.getEnvironment$()); - expect(env.getHostname()).toBe("vault.example.com"); - }); - - it("gets it from saved self host config from passed in user when there is an active user", async () => { - setGlobalData(Region.US, new EnvironmentUrls()); + it("gets env from saved self host config from passed in user when there is a different active user", async () => { setUserData(Region.EU, new EnvironmentUrls()); const selfHostUserUrls = new EnvironmentUrls(); @@ -392,7 +328,31 @@ describe("EnvironmentService", () => { await switchUser(testUser); const env = await firstValueFrom(sut.getEnvironment$(alternateTestUser)); - expect(env.getHostname()).toBe("base.example.com"); + expect(env?.getHostname()).toBe("base.example.com"); + }); + }); + + describe("getEnvironment (deprecated)", () => { + it("gets self hosted env from active user when no user passed in", async () => { + const selfHostUserUrls = new EnvironmentUrls(); + selfHostUserUrls.base = "https://base.example.com"; + setUserData(Region.SelfHosted, selfHostUserUrls); + + await switchUser(testUser); + + const env = await sut.getEnvironment(); + expect(env?.getHostname()).toBe("base.example.com"); + }); + + it("gets self hosted env from passed in user", async () => { + const selfHostUserUrls = new EnvironmentUrls(); + selfHostUserUrls.base = "https://base.example.com"; + setUserData(Region.SelfHosted, selfHostUserUrls); + + await switchUser(testUser); + + const env = await sut.getEnvironment(testUser); + expect(env?.getHostname()).toBe("base.example.com"); }); }); diff --git a/libs/common/src/platform/services/default-environment.service.ts b/libs/common/src/platform/services/default-environment.service.ts index ac3e39b2bb3..df55693ba0b 100644 --- a/libs/common/src/platform/services/default-environment.service.ts +++ b/libs/common/src/platform/services/default-environment.service.ts @@ -271,19 +271,8 @@ export class DefaultEnvironmentService implements EnvironmentService { } } - getEnvironment$(userId?: UserId): Observable { - if (userId == null) { - return this.environment$; - } - - return this.activeAccountId$.pipe( - switchMap((activeUserId) => { - // Previous rules dictated that we only get from user scoped state if there is an active user. - if (activeUserId == null) { - return this.globalState.state$; - } - return this.stateProvider.getUser(userId ?? activeUserId, USER_ENVIRONMENT_KEY).state$; - }), + getEnvironment$(userId: UserId): Observable { + return this.stateProvider.getUser(userId, USER_ENVIRONMENT_KEY).state$.pipe( map((state) => { return this.buildEnvironment(state?.region, state?.urls); }), @@ -294,7 +283,10 @@ export class DefaultEnvironmentService implements EnvironmentService { * @deprecated Use getEnvironment$ instead. */ async getEnvironment(userId?: UserId): Promise { - return firstValueFrom(this.getEnvironment$(userId)); + // Add backwards compatibility support for null userId + const definedUserId = userId ?? (await firstValueFrom(this.activeAccountId$)); + + return firstValueFrom(this.getEnvironment$(definedUserId)); } async seedUserEnvironment(userId: UserId) { diff --git a/libs/common/src/platform/services/encrypt.service.spec.ts b/libs/common/src/platform/services/encrypt.service.spec.ts deleted file mode 100644 index 609b5100a10..00000000000 --- a/libs/common/src/platform/services/encrypt.service.spec.ts +++ /dev/null @@ -1,263 +0,0 @@ -import { mockReset, mock } from "jest-mock-extended"; - -import { makeStaticByteArray } from "../../../spec"; -import { CsprngArray } from "../../types/csprng"; -import { CryptoFunctionService } from "../abstractions/crypto-function.service"; -import { LogService } from "../abstractions/log.service"; -import { EncryptionType } from "../enums"; -import { Utils } from "../misc/utils"; -import { EncArrayBuffer } from "../models/domain/enc-array-buffer"; -import { EncString } from "../models/domain/enc-string"; -import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; -import { EncryptServiceImplementation } from "../services/cryptography/encrypt.service.implementation"; - -describe("EncryptService", () => { - const cryptoFunctionService = mock(); - const logService = mock(); - - let encryptService: EncryptServiceImplementation; - - beforeEach(() => { - mockReset(cryptoFunctionService); - mockReset(logService); - - encryptService = new EncryptServiceImplementation(cryptoFunctionService, logService, true); - }); - - describe("encryptToBytes", () => { - const plainValue = makeStaticByteArray(16, 1); - const iv = makeStaticByteArray(16, 30); - const mac = makeStaticByteArray(32, 40); - const encryptedData = makeStaticByteArray(20, 50); - - it("throws if no key is provided", () => { - return expect(encryptService.encryptToBytes(plainValue, null)).rejects.toThrow( - "No encryption key", - ); - }); - - describe("encrypts data", () => { - beforeEach(() => { - cryptoFunctionService.randomBytes.calledWith(16).mockResolvedValueOnce(iv as CsprngArray); - cryptoFunctionService.aesEncrypt.mockResolvedValue(encryptedData); - }); - - it("using a key which supports mac", async () => { - const key = mock(); - const encType = EncryptionType.AesCbc128_HmacSha256_B64; - key.encType = encType; - - key.macKey = makeStaticByteArray(16, 20); - - cryptoFunctionService.hmac.mockResolvedValue(mac); - - const actual = await encryptService.encryptToBytes(plainValue, key); - - expect(actual.encryptionType).toEqual(encType); - expect(actual.ivBytes).toEqualBuffer(iv); - expect(actual.macBytes).toEqualBuffer(mac); - expect(actual.dataBytes).toEqualBuffer(encryptedData); - expect(actual.buffer.byteLength).toEqual( - 1 + iv.byteLength + mac.byteLength + encryptedData.byteLength, - ); - }); - - it("using a key which doesn't support mac", async () => { - const key = mock(); - const encType = EncryptionType.AesCbc256_B64; - key.encType = encType; - - key.macKey = null; - - const actual = await encryptService.encryptToBytes(plainValue, key); - - expect(cryptoFunctionService.hmac).not.toBeCalled(); - - expect(actual.encryptionType).toEqual(encType); - expect(actual.ivBytes).toEqualBuffer(iv); - expect(actual.macBytes).toBeNull(); - expect(actual.dataBytes).toEqualBuffer(encryptedData); - expect(actual.buffer.byteLength).toEqual(1 + iv.byteLength + encryptedData.byteLength); - }); - }); - }); - - describe("decryptToBytes", () => { - const encType = EncryptionType.AesCbc256_HmacSha256_B64; - const key = new SymmetricCryptoKey(makeStaticByteArray(64, 100), encType); - const computedMac = new Uint8Array(1); - const encBuffer = new EncArrayBuffer(makeStaticByteArray(60, encType)); - - beforeEach(() => { - cryptoFunctionService.hmac.mockResolvedValue(computedMac); - }); - - it("throws if no key is provided", () => { - return expect(encryptService.decryptToBytes(encBuffer, null)).rejects.toThrow( - "No encryption key", - ); - }); - - it("throws if no encrypted value is provided", () => { - return expect(encryptService.decryptToBytes(null, key)).rejects.toThrow( - "Nothing provided for decryption", - ); - }); - - it("decrypts data with provided key", async () => { - const decryptedBytes = makeStaticByteArray(10, 200); - - cryptoFunctionService.hmac.mockResolvedValue(makeStaticByteArray(1)); - cryptoFunctionService.compare.mockResolvedValue(true); - cryptoFunctionService.aesDecrypt.mockResolvedValueOnce(decryptedBytes); - - const actual = await encryptService.decryptToBytes(encBuffer, key); - - expect(cryptoFunctionService.aesDecrypt).toBeCalledWith( - expect.toEqualBuffer(encBuffer.dataBytes), - expect.toEqualBuffer(encBuffer.ivBytes), - expect.toEqualBuffer(key.encKey), - "cbc", - ); - - expect(actual).toEqualBuffer(decryptedBytes); - }); - - it("compares macs using CryptoFunctionService", async () => { - const expectedMacData = new Uint8Array( - encBuffer.ivBytes.byteLength + encBuffer.dataBytes.byteLength, - ); - expectedMacData.set(new Uint8Array(encBuffer.ivBytes)); - expectedMacData.set(new Uint8Array(encBuffer.dataBytes), encBuffer.ivBytes.byteLength); - - await encryptService.decryptToBytes(encBuffer, key); - - expect(cryptoFunctionService.hmac).toBeCalledWith( - expect.toEqualBuffer(expectedMacData), - key.macKey, - "sha256", - ); - - expect(cryptoFunctionService.compare).toBeCalledWith( - expect.toEqualBuffer(encBuffer.macBytes), - expect.toEqualBuffer(computedMac), - ); - }); - - it("returns null if macs don't match", async () => { - cryptoFunctionService.compare.mockResolvedValue(false); - - const actual = await encryptService.decryptToBytes(encBuffer, key); - expect(cryptoFunctionService.compare).toHaveBeenCalled(); - expect(cryptoFunctionService.aesDecrypt).not.toHaveBeenCalled(); - expect(actual).toBeNull(); - }); - - it("returns null if encTypes don't match", async () => { - key.encType = EncryptionType.AesCbc256_B64; - cryptoFunctionService.compare.mockResolvedValue(true); - - const actual = await encryptService.decryptToBytes(encBuffer, key); - - expect(actual).toBeNull(); - expect(cryptoFunctionService.aesDecrypt).not.toHaveBeenCalled(); - }); - }); - - describe("rsa", () => { - const data = makeStaticByteArray(10, 100); - const encryptedData = makeStaticByteArray(10, 150); - const publicKey = makeStaticByteArray(10, 200); - const privateKey = makeStaticByteArray(10, 250); - const encString = makeEncString(encryptedData); - - function makeEncString(data: Uint8Array): EncString { - return new EncString(EncryptionType.Rsa2048_OaepSha1_B64, Utils.fromBufferToB64(data)); - } - - describe("rsaEncrypt", () => { - it("throws if no data is provided", () => { - return expect(encryptService.rsaEncrypt(null, publicKey)).rejects.toThrow("No data"); - }); - - it("throws if no public key is provided", () => { - return expect(encryptService.rsaEncrypt(data, null)).rejects.toThrow("No public key"); - }); - - it("encrypts data with provided key", async () => { - cryptoFunctionService.rsaEncrypt.mockResolvedValue(encryptedData); - - const actual = await encryptService.rsaEncrypt(data, publicKey); - - expect(cryptoFunctionService.rsaEncrypt).toBeCalledWith( - expect.toEqualBuffer(data), - expect.toEqualBuffer(publicKey), - "sha1", - ); - - expect(actual).toEqual(encString); - expect(actual.dataBytes).toEqualBuffer(encryptedData); - }); - }); - - describe("rsaDecrypt", () => { - it("throws if no data is provided", () => { - return expect(encryptService.rsaDecrypt(null, privateKey)).rejects.toThrow("No data"); - }); - - it("throws if no private key is provided", () => { - return expect(encryptService.rsaDecrypt(encString, null)).rejects.toThrow("No private key"); - }); - - it.each([ - EncryptionType.AesCbc256_B64, - EncryptionType.AesCbc128_HmacSha256_B64, - EncryptionType.AesCbc256_HmacSha256_B64, - ])("throws if encryption type is %s", async (encType) => { - encString.encryptionType = encType; - - await expect(encryptService.rsaDecrypt(encString, privateKey)).rejects.toThrow( - "Invalid encryption type", - ); - }); - - it("decrypts data with provided key", async () => { - cryptoFunctionService.rsaDecrypt.mockResolvedValue(data); - - const actual = await encryptService.rsaDecrypt(makeEncString(data), privateKey); - - expect(cryptoFunctionService.rsaDecrypt).toBeCalledWith( - expect.toEqualBuffer(data), - expect.toEqualBuffer(privateKey), - "sha1", - ); - - expect(actual).toEqualBuffer(data); - }); - }); - }); - - describe("resolveLegacyKey", () => { - it("creates a legacy key if required", async () => { - const key = new SymmetricCryptoKey(makeStaticByteArray(32), EncryptionType.AesCbc256_B64); - const encString = mock(); - encString.encryptionType = EncryptionType.AesCbc128_HmacSha256_B64; - - const actual = encryptService.resolveLegacyKey(key, encString); - - const expected = new SymmetricCryptoKey(key.key, EncryptionType.AesCbc128_HmacSha256_B64); - expect(actual).toEqual(expected); - }); - - it("does not create a legacy key if not required", async () => { - const encType = EncryptionType.AesCbc256_HmacSha256_B64; - const key = new SymmetricCryptoKey(makeStaticByteArray(64), encType); - const encString = mock(); - encString.encryptionType = encType; - - const actual = encryptService.resolveLegacyKey(key, encString); - - expect(actual).toEqual(key); - }); - }); -}); diff --git a/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts b/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts index 226f4c2cfe9..3ea86a1f504 100644 --- a/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts +++ b/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts @@ -3,7 +3,8 @@ import { TextEncoder } from "util"; import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject, of } from "rxjs"; -import { Account, AccountService } from "../../../auth/abstractions/account.service"; +import { mockAccountServiceWith } from "../../../../spec"; +import { Account } from "../../../auth/abstractions/account.service"; import { UserId } from "../../../types/guid"; import { CipherService } from "../../../vault/abstractions/cipher.service"; import { SyncService } from "../../../vault/abstractions/sync/sync.service.abstraction"; @@ -46,7 +47,6 @@ describe("FidoAuthenticatorService", () => { let userInterface!: MockProxy>; let userInterfaceSession!: MockProxy; let syncService!: MockProxy; - let accountService!: MockProxy; let authenticator!: Fido2AuthenticatorService; let windowReference!: ParentWindowReference; @@ -58,7 +58,7 @@ describe("FidoAuthenticatorService", () => { syncService = mock({ activeUserLastSync$: () => of(new Date()), }); - accountService = mock(); + const accountService = mockAccountServiceWith("testId" as UserId); authenticator = new Fido2AuthenticatorService( cipherService, userInterface, diff --git a/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts b/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts index 376f4dcdced..76bd19b2876 100644 --- a/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts +++ b/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts @@ -1,8 +1,9 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { AccountService } from "../../../auth/abstractions/account.service"; +import { getUserId } from "../../../auth/services/account.service"; import { CipherService } from "../../../vault/abstractions/cipher.service"; import { SyncService } from "../../../vault/abstractions/sync/sync.service.abstraction"; import { CipherRepromptType } from "../../../vault/enums/cipher-reprompt-type"; @@ -145,10 +146,10 @@ export class Fido2AuthenticatorService try { keyPair = await createKeyPair(); pubKeyDer = await crypto.subtle.exportKey("spki", keyPair.publicKey); - const encrypted = await this.cipherService.get(cipherId); const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), + this.accountService.activeAccount$.pipe(getUserId), ); + const encrypted = await this.cipherService.get(cipherId, activeUserId); cipher = await encrypted.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(encrypted, activeUserId), @@ -309,7 +310,7 @@ export class Fido2AuthenticatorService if (selectedFido2Credential.counter > 0) { const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), + this.accountService.activeAccount$.pipe(getUserId), ); const encrypted = await this.cipherService.encrypt(selectedCipher, activeUserId); await this.cipherService.updateWithServer(encrypted); @@ -400,7 +401,8 @@ export class Fido2AuthenticatorService return []; } - const ciphers = await this.cipherService.getAllDecrypted(); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + const ciphers = await this.cipherService.getAllDecrypted(activeUserId); return ciphers .filter( (cipher) => @@ -421,7 +423,8 @@ export class Fido2AuthenticatorService return []; } - const ciphers = await this.cipherService.getAllDecrypted(); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + const ciphers = await this.cipherService.getAllDecrypted(activeUserId); return ciphers.filter( (cipher) => !cipher.isDeleted && @@ -438,7 +441,8 @@ export class Fido2AuthenticatorService } private async findCredentialsByRp(rpId: string): Promise { - const ciphers = await this.cipherService.getAllDecrypted(); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + const ciphers = await this.cipherService.getAllDecrypted(activeUserId); return ciphers.filter( (cipher) => !cipher.isDeleted && diff --git a/libs/common/src/platform/services/fido2/fido2-client.service.ts b/libs/common/src/platform/services/fido2/fido2-client.service.ts index 4bf30ef6537..2445cd366de 100644 --- a/libs/common/src/platform/services/fido2/fido2-client.service.ts +++ b/libs/common/src/platform/services/fido2/fido2-client.service.ts @@ -251,7 +251,8 @@ export class Fido2ClientService clientDataJSON: Fido2Utils.bufferToString(clientDataJSONBytes), publicKey: Fido2Utils.bufferToString(makeCredentialResult.publicKey), publicKeyAlgorithm: makeCredentialResult.publicKeyAlgorithm, - transports: params.rp.id === "google.com" ? ["internal", "usb"] : ["internal"], + transports: + params.rp.id === "google.com" ? ["internal", "usb", "hybrid"] : ["internal", "hybrid"], extensions: { credProps }, }; } diff --git a/libs/common/src/platform/services/noop-notifications.service.ts b/libs/common/src/platform/services/noop-notifications.service.ts deleted file mode 100644 index edfeccd322d..00000000000 --- a/libs/common/src/platform/services/noop-notifications.service.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { NotificationsService as NotificationsServiceAbstraction } from "../../abstractions/notifications.service"; -import { LogService } from "../abstractions/log.service"; - -export class NoopNotificationsService implements NotificationsServiceAbstraction { - constructor(private logService: LogService) {} - - init(): Promise { - this.logService.info( - "Initializing no-op notification service, no push notifications will be received", - ); - return Promise.resolve(); - } - - updateConnection(sync?: boolean): Promise { - this.logService.info("Updating notification service connection"); - return Promise.resolve(); - } - - reconnectFromActivity(): Promise { - this.logService.info("Reconnecting notification service from activity"); - return Promise.resolve(); - } - - disconnectFromInactivity(): Promise { - this.logService.info("Disconnecting notification service from inactivity"); - return Promise.resolve(); - } -} diff --git a/libs/common/src/platform/services/sdk/default-sdk-client-factory.ts b/libs/common/src/platform/services/sdk/default-sdk-client-factory.ts index 8e99af2efed..fc55cc83ac8 100644 --- a/libs/common/src/platform/services/sdk/default-sdk-client-factory.ts +++ b/libs/common/src/platform/services/sdk/default-sdk-client-factory.ts @@ -1,19 +1,19 @@ import * as sdk from "@bitwarden/sdk-internal"; -import * as module from "@bitwarden/sdk-internal/bitwarden_wasm_internal_bg.wasm"; import { SdkClientFactory } from "../../abstractions/sdk/sdk-client-factory"; /** - * Directly imports the Bitwarden SDK and initializes it. - * - * **Warning**: This requires WASM support and will fail if the environment does not support it. + * Default SDK client factory. */ export class DefaultSdkClientFactory implements SdkClientFactory { + /** + * Initializes a Bitwarden client. Assumes the SDK is already loaded. + * @param args Bitwarden client constructor parameters + * @returns A BitwardenClient + */ async createSdkClient( ...args: ConstructorParameters ): Promise { - (sdk as any).init(module); - return Promise.resolve(new sdk.BitwardenClient(...args)); } } diff --git a/libs/common/src/platform/services/sdk/default-sdk-load.service.ts b/libs/common/src/platform/services/sdk/default-sdk-load.service.ts new file mode 100644 index 00000000000..0c4114b8796 --- /dev/null +++ b/libs/common/src/platform/services/sdk/default-sdk-load.service.ts @@ -0,0 +1,15 @@ +import * as sdk from "@bitwarden/sdk-internal"; +import * as bitwardenModule from "@bitwarden/sdk-internal/bitwarden_wasm_internal_bg.wasm"; + +import { SdkLoadService } from "../../abstractions/sdk/sdk-load.service"; + +/** + * Directly imports the Bitwarden SDK and initializes it. + * + * **Warning**: This requires WASM support and will fail if the environment does not support it. + */ +export class DefaultSdkLoadService extends SdkLoadService { + async load(): Promise { + (sdk as any).init(bitwardenModule); + } +} diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts b/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts index c5917e0230f..a66b2a9cb6f 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts @@ -4,17 +4,28 @@ import { BehaviorSubject, firstValueFrom, of } from "rxjs"; import { KdfConfigService, KeyService, PBKDF2KdfConfig } from "@bitwarden/key-management"; import { BitwardenClient } from "@bitwarden/sdk-internal"; +import { ObservableTracker } from "../../../../spec"; import { AccountInfo, AccountService } from "../../../auth/abstractions/account.service"; import { UserId } from "../../../types/guid"; import { UserKey } from "../../../types/key"; import { Environment, EnvironmentService } from "../../abstractions/environment.service"; import { PlatformUtilsService } from "../../abstractions/platform-utils.service"; import { SdkClientFactory } from "../../abstractions/sdk/sdk-client-factory"; +import { SdkLoadService } from "../../abstractions/sdk/sdk-load.service"; +import { UserNotLoggedInError } from "../../abstractions/sdk/sdk.service"; +import { Rc } from "../../misc/reference-counting/rc"; import { EncryptedString } from "../../models/domain/enc-string"; import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key"; import { DefaultSdkService } from "./default-sdk.service"; +class TestSdkLoadService extends SdkLoadService { + protected override load(): Promise { + // Simulate successfull WASM load + return Promise.resolve(); + } +} + describe("DefaultSdkService", () => { describe("userClient$", () => { let sdkClientFactory!: MockProxy; @@ -25,9 +36,9 @@ describe("DefaultSdkService", () => { let keyService!: MockProxy; let service!: DefaultSdkService; - let mockClient!: MockProxy; + beforeEach(async () => { + await new TestSdkLoadService().loadAndInit(); - beforeEach(() => { sdkClientFactory = mock(); environmentService = mock(); platformUtilsService = mock(); @@ -46,15 +57,10 @@ describe("DefaultSdkService", () => { kdfConfigService, keyService, ); - - mockClient = mock(); - mockClient.crypto.mockReturnValue(mock()); - sdkClientFactory.createSdkClient.mockResolvedValue(mockClient); }); describe("given the user is logged in", () => { const userId = "user-id" as UserId; - beforeEach(() => { environmentService.getEnvironment$ .calledWith(userId) @@ -74,56 +80,144 @@ describe("DefaultSdkService", () => { keyService.encryptedOrgKeys$.calledWith(userId).mockReturnValue(of({})); }); - it("creates an SDK client when called the first time", async () => { - const result = await firstValueFrom(service.userClient$(userId)); + describe("given no client override has been set for the user", () => { + let mockClient!: MockProxy; - expect(result).toBe(mockClient); - expect(sdkClientFactory.createSdkClient).toHaveBeenCalled(); + beforeEach(() => { + mockClient = createMockClient(); + sdkClientFactory.createSdkClient.mockResolvedValue(mockClient); + }); + + it("creates an internal SDK client when called the first time", async () => { + await firstValueFrom(service.userClient$(userId)); + + expect(sdkClientFactory.createSdkClient).toHaveBeenCalled(); + }); + + it("does not create an SDK client when called the second time with same userId", async () => { + const subject_1 = new BehaviorSubject | undefined>(undefined); + const subject_2 = new BehaviorSubject | undefined>(undefined); + + // Use subjects to ensure the subscription is kept alive + service.userClient$(userId).subscribe(subject_1); + service.userClient$(userId).subscribe(subject_2); + + // Wait for the next tick to ensure all async operations are done + await new Promise(process.nextTick); + + expect(subject_1.value.take().value).toBe(mockClient); + expect(subject_2.value.take().value).toBe(mockClient); + expect(sdkClientFactory.createSdkClient).toHaveBeenCalledTimes(1); + }); + + it("destroys the internal SDK client when all subscriptions are closed", async () => { + const subject_1 = new BehaviorSubject | undefined>(undefined); + const subject_2 = new BehaviorSubject | undefined>(undefined); + const subscription_1 = service.userClient$(userId).subscribe(subject_1); + const subscription_2 = service.userClient$(userId).subscribe(subject_2); + await new Promise(process.nextTick); + + subscription_1.unsubscribe(); + subscription_2.unsubscribe(); + + await new Promise(process.nextTick); + expect(mockClient.free).toHaveBeenCalledTimes(1); + }); + + it("destroys the internal SDK client when the userKey is unset (i.e. lock or logout)", async () => { + const userKey$ = new BehaviorSubject( + new SymmetricCryptoKey(new Uint8Array(64)) as UserKey, + ); + keyService.userKey$.calledWith(userId).mockReturnValue(userKey$); + + const subject = new BehaviorSubject | undefined>(undefined); + service.userClient$(userId).subscribe(subject); + await new Promise(process.nextTick); + + userKey$.next(undefined); + await new Promise(process.nextTick); + + expect(mockClient.free).toHaveBeenCalledTimes(1); + expect(subject.value).toBe(undefined); + }); }); - it("does not create an SDK client when called the second time with same userId", async () => { - const subject_1 = new BehaviorSubject(undefined); - const subject_2 = new BehaviorSubject(undefined); + describe("given overrides are used", () => { + it("does not create a new client and emits the override client when a client override has already been set ", async () => { + const mockClient = mock(); + service.setClient(userId, mockClient); + const userClientTracker = new ObservableTracker(service.userClient$(userId), false); + await userClientTracker.pauseUntilReceived(1); - // Use subjects to ensure the subscription is kept alive - service.userClient$(userId).subscribe(subject_1); - service.userClient$(userId).subscribe(subject_2); + expect(sdkClientFactory.createSdkClient).not.toHaveBeenCalled(); + expect(userClientTracker.emissions[0].take().value).toBe(mockClient); + }); - // Wait for the next tick to ensure all async operations are done - await new Promise(process.nextTick); + it("emits the internal client then switches to override when an override is set", async () => { + const mockInternalClient = createMockClient(); + const mockOverrideClient = createMockClient(); + sdkClientFactory.createSdkClient.mockResolvedValue(mockInternalClient); + const userClientTracker = new ObservableTracker(service.userClient$(userId), false); - expect(subject_1.value).toBe(mockClient); - expect(subject_2.value).toBe(mockClient); - expect(sdkClientFactory.createSdkClient).toHaveBeenCalledTimes(1); - }); + await userClientTracker.pauseUntilReceived(1); + expect(userClientTracker.emissions[0].take().value).toBe(mockInternalClient); - it("destroys the SDK client when all subscriptions are closed", async () => { - const subject_1 = new BehaviorSubject(undefined); - const subject_2 = new BehaviorSubject(undefined); - const subscription_1 = service.userClient$(userId).subscribe(subject_1); - const subscription_2 = service.userClient$(userId).subscribe(subject_2); - await new Promise(process.nextTick); + service.setClient(userId, mockOverrideClient); - subscription_1.unsubscribe(); - subscription_2.unsubscribe(); + await userClientTracker.pauseUntilReceived(2); + expect(userClientTracker.emissions[1].take().value).toBe(mockOverrideClient); + }); - expect(mockClient.free).toHaveBeenCalledTimes(1); - }); + it("throws error when the client has explicitly been set as undefined", async () => { + service.setClient(userId, undefined); - it("destroys the SDK client when the userKey is unset (i.e. lock or logout)", async () => { - const userKey$ = new BehaviorSubject(new SymmetricCryptoKey(new Uint8Array(64)) as UserKey); - keyService.userKey$.calledWith(userId).mockReturnValue(userKey$); + const result = () => firstValueFrom(service.userClient$(userId)); - const subject = new BehaviorSubject(undefined); - service.userClient$(userId).subscribe(subject); - await new Promise(process.nextTick); + await expect(result).rejects.toThrow(UserNotLoggedInError); + }); - userKey$.next(undefined); - await new Promise(process.nextTick); + it("completes the subscription when the override is set to undefined after having been defined", async () => { + const mockOverrideClient = createMockClient(); + service.setClient(userId, mockOverrideClient); + const userClientTracker = new ObservableTracker(service.userClient$(userId), false); + await userClientTracker.pauseUntilReceived(1); - expect(mockClient.free).toHaveBeenCalledTimes(1); - expect(subject.value).toBe(undefined); + service.setClient(userId, undefined); + + await userClientTracker.expectCompletion(); + }); + + it("destroys the internal client when an override is set", async () => { + const mockInternalClient = createMockClient(); + const mockOverrideClient = createMockClient(); + sdkClientFactory.createSdkClient.mockResolvedValue(mockInternalClient); + const userClientTracker = new ObservableTracker(service.userClient$(userId), false); + + await userClientTracker.pauseUntilReceived(1); + service.setClient(userId, mockOverrideClient); + await userClientTracker.pauseUntilReceived(2); + + expect(mockInternalClient.free).toHaveBeenCalled(); + }); + + it("destroys the override client when explicitly setting the client to undefined", async () => { + const mockOverrideClient = createMockClient(); + service.setClient(userId, mockOverrideClient); + const userClientTracker = new ObservableTracker(service.userClient$(userId), false); + await userClientTracker.pauseUntilReceived(1); + + service.setClient(userId, undefined); + await userClientTracker.expectCompletion(); + + expect(mockOverrideClient.free).toHaveBeenCalled(); + }); }); }); }); }); + +function createMockClient(): MockProxy { + const client = mock(); + client.crypto.mockReturnValue(mock()); + return client; +} diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.ts b/libs/common/src/platform/services/sdk/default-sdk.service.ts index e9cecbb15dc..5c381c7dd1b 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { combineLatest, concatMap, @@ -10,13 +8,16 @@ import { tap, switchMap, catchError, + BehaviorSubject, + of, + takeWhile, + throwIfEmpty, } from "rxjs"; import { KeyService, KdfConfigService, KdfConfig, KdfType } from "@bitwarden/key-management"; import { BitwardenClient, ClientSettings, - LogLevel, DeviceType as SdkDeviceType, } from "@bitwarden/sdk-internal"; @@ -28,17 +29,27 @@ import { UserKey } from "../../../types/key"; import { Environment, EnvironmentService } from "../../abstractions/environment.service"; import { PlatformUtilsService } from "../../abstractions/platform-utils.service"; import { SdkClientFactory } from "../../abstractions/sdk/sdk-client-factory"; -import { SdkService } from "../../abstractions/sdk/sdk.service"; +import { SdkLoadService } from "../../abstractions/sdk/sdk-load.service"; +import { SdkService, UserNotLoggedInError } from "../../abstractions/sdk/sdk.service"; import { compareValues } from "../../misc/compare-values"; +import { Rc } from "../../misc/reference-counting/rc"; import { EncryptedString } from "../../models/domain/enc-string"; +// A symbol that represents an overriden client that is explicitly set to undefined, +// blocking the creation of an internal client for that user. +const UnsetClient = Symbol("UnsetClient"); + export class DefaultSdkService implements SdkService { - private sdkClientCache = new Map>(); + private sdkClientOverrides = new BehaviorSubject<{ + [userId: UserId]: Rc | typeof UnsetClient; + }>({}); + private sdkClientCache = new Map>>(); client$ = this.environmentService.environment$.pipe( concatMap(async (env) => { + await SdkLoadService.Ready; const settings = this.toSettings(env); - return await this.sdkClientFactory.createSdkClient(settings, LogLevel.Info); + return await this.sdkClientFactory.createSdkClient(settings); }), shareReplay({ refCount: true, bufferSize: 1 }), ); @@ -55,13 +66,54 @@ export class DefaultSdkService implements SdkService { private accountService: AccountService, private kdfConfigService: KdfConfigService, private keyService: KeyService, - private userAgent: string = null, + private userAgent: string | null = null, ) {} - userClient$(userId: UserId): Observable { - // TODO: Figure out what happens when the user logs out - if (this.sdkClientCache.has(userId)) { - return this.sdkClientCache.get(userId); + userClient$(userId: UserId): Observable | undefined> { + return this.sdkClientOverrides.pipe( + takeWhile((clients) => clients[userId] !== UnsetClient, false), + map((clients) => { + if (clients[userId] === UnsetClient) { + throw new Error("Encountered UnsetClient even though it should have been filtered out"); + } + return clients[userId] as Rc; + }), + distinctUntilChanged(), + switchMap((clientOverride) => { + if (clientOverride) { + return of(clientOverride); + } + + return this.internalClient$(userId); + }), + throwIfEmpty(() => new UserNotLoggedInError(userId)), + ); + } + + setClient(userId: UserId, client: BitwardenClient | undefined) { + const previousValue = this.sdkClientOverrides.value[userId]; + + this.sdkClientOverrides.next({ + ...this.sdkClientOverrides.value, + [userId]: client ? new Rc(client) : UnsetClient, + }); + + if (previousValue !== UnsetClient && previousValue !== undefined) { + previousValue.markForDisposal(); + } + } + + /** + * This method is used to create a client for a specific user by using the existing state of the application. + * This methods is a fallback for when no client has been provided by Auth. As Auth starts implementing the + * client creation, this method will be deprecated. + * @param userId The user id for which to create the client + * @returns An observable that emits the client for the user + */ + private internalClient$(userId: UserId): Observable | undefined> { + const cached = this.sdkClientCache.get(userId); + if (cached !== undefined) { + return cached; } const account$ = this.accountService.accounts$.pipe( @@ -84,36 +136,37 @@ export class DefaultSdkService implements SdkService { privateKey$, userKey$, orgKeys$, + SdkLoadService.Ready, // Makes sure we wait (once) for the SDK to be loaded ]).pipe( // switchMap is required to allow the clean-up logic to be executed when `combineLatest` emits a new value. switchMap(([env, account, kdfParams, privateKey, userKey, orgKeys]) => { // Create our own observable to be able to implement clean-up logic - return new Observable((subscriber) => { - let client: BitwardenClient; - + return new Observable>((subscriber) => { const createAndInitializeClient = async () => { - if (privateKey == null || userKey == null) { + if (env == null || kdfParams == null || privateKey == null || userKey == null) { return undefined; } const settings = this.toSettings(env); - client = await this.sdkClientFactory.createSdkClient(settings, LogLevel.Info); + const client = await this.sdkClientFactory.createSdkClient(settings); await this.initializeClient(client, account, kdfParams, privateKey, userKey, orgKeys); return client; }; + let client: Rc | undefined; createAndInitializeClient() .then((c) => { - client = c; - subscriber.next(c); + client = c === undefined ? undefined : new Rc(c); + + subscriber.next(client); }) .catch((e) => { subscriber.error(e); }); - return () => client?.free(); + return () => client?.markForDisposal(); }); }), tap({ @@ -132,7 +185,7 @@ export class DefaultSdkService implements SdkService { kdfParams: KdfConfig, privateKey: EncryptedString, userKey: UserKey, - orgKeys?: Record, + orgKeys: Record | null, ) { await client.crypto().initialize_user_crypto({ email: account.email, diff --git a/libs/common/src/platform/services/sdk/noop-sdk-load.service.ts b/libs/common/src/platform/services/sdk/noop-sdk-load.service.ts new file mode 100644 index 00000000000..9fd04fdf833 --- /dev/null +++ b/libs/common/src/platform/services/sdk/noop-sdk-load.service.ts @@ -0,0 +1,7 @@ +import { SdkLoadService } from "../../abstractions/sdk/sdk-load.service"; + +export class NoopSdkLoadService extends SdkLoadService { + async load() { + throw new Error("SDK not available in this environment"); + } +} diff --git a/libs/common/src/platform/services/state.service.ts b/libs/common/src/platform/services/state.service.ts index 0ad2473d058..a78a9b37a8c 100644 --- a/libs/common/src/platform/services/state.service.ts +++ b/libs/common/src/platform/services/state.service.ts @@ -134,7 +134,6 @@ export class StateService< } async addAccount(account: TAccount) { - await this.environmentService.seedUserEnvironment(account.profile.userId as UserId); await this.updateState(async (state) => { state.accounts[account.profile.userId] = account; return state; @@ -170,7 +169,7 @@ export class StateService< /** * user key when using the "never" option of vault timeout */ - async setUserKeyAutoUnlock(value: string, options?: StorageOptions): Promise { + async setUserKeyAutoUnlock(value: string | null, options?: StorageOptions): Promise { options = this.reconcileOptions( this.reconcileOptions(options, { keySuffix: "auto" }), await this.defaultSecureStorageOptions(), @@ -226,7 +225,7 @@ export class StateService< /** * @deprecated Use UserKeyAuto instead */ - async setCryptoMasterKeyAuto(value: string, options?: StorageOptions): Promise { + async setCryptoMasterKeyAuto(value: string | null, options?: StorageOptions): Promise { options = this.reconcileOptions( this.reconcileOptions(options, { keySuffix: "auto" }), await this.defaultSecureStorageOptions(), @@ -663,7 +662,7 @@ export class StateService< protected async saveSecureStorageKey( key: string, - value: T, + value: T | null, options?: StorageOptions, ) { return value == null diff --git a/libs/common/src/platform/services/user-auto-unlock-key.service.spec.ts b/libs/common/src/platform/services/user-auto-unlock-key.service.spec.ts index 23a8ba3138b..84511d1e71a 100644 --- a/libs/common/src/platform/services/user-auto-unlock-key.service.spec.ts +++ b/libs/common/src/platform/services/user-auto-unlock-key.service.spec.ts @@ -1,6 +1,7 @@ import { mock } from "jest-mock-extended"; -import { DefaultKeyService } from "../../../../key-management/src/key.service"; +import { DefaultKeyService } from "@bitwarden/key-management"; + import { CsprngArray } from "../../types/csprng"; import { UserId } from "../../types/guid"; import { UserKey } from "../../types/key"; diff --git a/libs/common/src/platform/services/user-auto-unlock-key.service.ts b/libs/common/src/platform/services/user-auto-unlock-key.service.ts index abb8993c39c..bf64c13b060 100644 --- a/libs/common/src/platform/services/user-auto-unlock-key.service.ts +++ b/libs/common/src/platform/services/user-auto-unlock-key.service.ts @@ -1,4 +1,5 @@ -import { KeyService } from "../../../../key-management/src/abstractions/key.service"; +import { KeyService } from "@bitwarden/key-management"; + import { UserId } from "../../types/guid"; import { KeySuffixOptions } from "../enums"; diff --git a/libs/common/src/platform/state/global-state.ts b/libs/common/src/platform/state/global-state.ts index b0f19c53faa..b2ac634df24 100644 --- a/libs/common/src/platform/state/global-state.ts +++ b/libs/common/src/platform/state/global-state.ts @@ -9,7 +9,7 @@ import { StateUpdateOptions } from "./state-update-options"; export interface GlobalState { /** * Method for allowing you to manipulate state in an additive way. - * @param configureState callback for how you want manipulate this section of state + * @param configureState callback for how you want to manipulate this section of state * @param options Defaults given by @see {module:state-update-options#DEFAULT_OPTIONS} * @param options.shouldUpdate A callback for determining if you want to update state. Defaults to () => true * @param options.combineLatestWith An observable that you want to combine with the current state for callbacks. Defaults to null @@ -18,13 +18,13 @@ export interface GlobalState { * Resolves to the new state. If `shouldUpdate` returns false, the promise will resolve to the current state. */ update: ( - configureState: (state: T, dependency: TCombine) => T, + configureState: (state: T | null, dependency: TCombine) => T | null, options?: StateUpdateOptions, - ) => Promise; + ) => Promise; /** * An observable stream of this state, the first emission of this will be the current state on disk * and subsequent updates will be from an update to that state. */ - state$: Observable; + state$: Observable; } diff --git a/libs/common/src/platform/state/implementations/default-derived-state.provider.ts b/libs/common/src/platform/state/implementations/default-derived-state.provider.ts index 3c8c39e21e8..61f36fa0b75 100644 --- a/libs/common/src/platform/state/implementations/default-derived-state.provider.ts +++ b/libs/common/src/platform/state/implementations/default-derived-state.provider.ts @@ -8,7 +8,14 @@ import { DerivedStateProvider } from "../derived-state.provider"; import { DefaultDerivedState } from "./default-derived-state"; export class DefaultDerivedStateProvider implements DerivedStateProvider { - private cache: Record> = {}; + /** + * The cache uses a WeakMap to maintain separate derived states per user. + * Each user's state Observable acts as a unique key, without needing to + * pass around `userId`. Also, when a user's state Observable is cleaned up + * (like during an account swap) their cache is automatically garbage + * collected. + */ + private cache = new WeakMap, Record>>(); constructor() {} @@ -17,8 +24,14 @@ export class DefaultDerivedStateProvider implements DerivedStateProvider { deriveDefinition: DeriveDefinition, dependencies: TDeps, ): DerivedState { + let stateCache = this.cache.get(parentState$); + if (!stateCache) { + stateCache = {}; + this.cache.set(parentState$, stateCache); + } + const cacheKey = deriveDefinition.buildCacheKey(); - const existingDerivedState = this.cache[cacheKey]; + const existingDerivedState = stateCache[cacheKey]; if (existingDerivedState != null) { // I have to cast out of the unknown generic but this should be safe if rules // around domain token are made @@ -26,7 +39,7 @@ export class DefaultDerivedStateProvider implements DerivedStateProvider { } const newDerivedState = this.buildDerivedState(parentState$, deriveDefinition, dependencies); - this.cache[cacheKey] = newDerivedState; + stateCache[cacheKey] = newDerivedState; return newDerivedState; } diff --git a/libs/common/src/platform/state/implementations/default-derived-state.spec.ts b/libs/common/src/platform/state/implementations/default-derived-state.spec.ts index 7e8d76bd203..6fcc1c408cb 100644 --- a/libs/common/src/platform/state/implementations/default-derived-state.spec.ts +++ b/libs/common/src/platform/state/implementations/default-derived-state.spec.ts @@ -9,6 +9,7 @@ import { DeriveDefinition } from "../derive-definition"; import { StateDefinition } from "../state-definition"; import { DefaultDerivedState } from "./default-derived-state"; +import { DefaultDerivedStateProvider } from "./default-derived-state.provider"; let callCount = 0; const cleanupDelayMs = 10; @@ -182,4 +183,29 @@ describe("DefaultDerivedState", () => { expect(await firstValueFrom(observable)).toEqual(new Date(newDate)); }); }); + + describe("account switching", () => { + let provider: DefaultDerivedStateProvider; + + beforeEach(() => { + provider = new DefaultDerivedStateProvider(); + }); + + it("should provide a dedicated cache for each account", async () => { + const user1State$ = new Subject(); + const user1Derived = provider.get(user1State$, deriveDefinition, deps); + const user1Emissions = trackEmissions(user1Derived.state$); + + const user2State$ = new Subject(); + const user2Derived = provider.get(user2State$, deriveDefinition, deps); + const user2Emissions = trackEmissions(user2Derived.state$); + + user1State$.next("2015-12-30"); + user2State$.next("2020-12-29"); + await awaitAsync(); + + expect(user1Emissions).toEqual([new Date("2015-12-30")]); + expect(user2Emissions).toEqual([new Date("2020-12-29")]); + }); + }); }); diff --git a/libs/common/src/platform/state/implementations/default-single-user-state.ts b/libs/common/src/platform/state/implementations/default-single-user-state.ts index 4cfba1ffa95..1dafd3aecad 100644 --- a/libs/common/src/platform/state/implementations/default-single-user-state.ts +++ b/libs/common/src/platform/state/implementations/default-single-user-state.ts @@ -16,7 +16,7 @@ export class DefaultSingleUserState extends StateBase> implements SingleUserState { - readonly combinedState$: Observable>; + readonly combinedState$: Observable>; constructor( readonly userId: UserId, diff --git a/libs/common/src/platform/state/implementations/default-state.provider.ts b/libs/common/src/platform/state/implementations/default-state.provider.ts index f86ba11b268..31795767979 100644 --- a/libs/common/src/platform/state/implementations/default-state.provider.ts +++ b/libs/common/src/platform/state/implementations/default-state.provider.ts @@ -54,9 +54,9 @@ export class DefaultStateProvider implements StateProvider { async setUserState( userKeyDefinition: UserKeyDefinition, - value: T, + value: T | null, userId?: UserId, - ): Promise<[UserId, T]> { + ): Promise<[UserId, T | null]> { if (userId) { return [userId, await this.getUser(userId, userKeyDefinition).update(() => value)]; } else { diff --git a/libs/common/src/platform/state/implementations/state-base.ts b/libs/common/src/platform/state/implementations/state-base.ts index 567de957e53..578720a2281 100644 --- a/libs/common/src/platform/state/implementations/state-base.ts +++ b/libs/common/src/platform/state/implementations/state-base.ts @@ -1,12 +1,12 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { - Observable, - ReplaySubject, defer, filter, firstValueFrom, merge, + Observable, + ReplaySubject, share, switchMap, tap, @@ -22,13 +22,13 @@ import { ObservableStorageService, } from "../../abstractions/storage.service"; import { DebugOptions } from "../key-definition"; -import { StateUpdateOptions, populateOptionsWithDefault } from "../state-update-options"; +import { populateOptionsWithDefault, StateUpdateOptions } from "../state-update-options"; import { getStoredValue } from "./util"; // The parts of a KeyDefinition this class cares about to make it work type KeyDefinitionRequirements = { - deserializer: (jsonState: Jsonify) => T; + deserializer: (jsonState: Jsonify) => T | null; cleanupDelayMs: number; debug: Required; }; @@ -36,7 +36,7 @@ type KeyDefinitionRequirements = { export abstract class StateBase> { private updatePromise: Promise; - readonly state$: Observable; + readonly state$: Observable; constructor( protected readonly key: StorageKey, @@ -86,9 +86,9 @@ export abstract class StateBase> } async update( - configureState: (state: T, dependency: TCombine) => T, + configureState: (state: T | null, dependency: TCombine) => T | null, options: StateUpdateOptions = {}, - ): Promise { + ): Promise { options = populateOptionsWithDefault(options); if (this.updatePromise != null) { await this.updatePromise; @@ -96,17 +96,16 @@ export abstract class StateBase> try { this.updatePromise = this.internalUpdate(configureState, options); - const newState = await this.updatePromise; - return newState; + return await this.updatePromise; } finally { this.updatePromise = null; } } private async internalUpdate( - configureState: (state: T, dependency: TCombine) => T, + configureState: (state: T | null, dependency: TCombine) => T | null, options: StateUpdateOptions, - ): Promise { + ): Promise { const currentState = await this.getStateForUpdate(); const combinedDependencies = options.combineLatestWith != null @@ -122,7 +121,7 @@ export abstract class StateBase> return newState; } - protected async doStorageSave(newState: T, oldState: T) { + protected async doStorageSave(newState: T | null, oldState: T) { if (this.keyDefinition.debug.enableUpdateLogging) { this.logService.info( `Updating '${this.key}' from ${oldState == null ? "null" : "non-null"} to ${newState == null ? "null" : "non-null"}`, diff --git a/libs/common/src/platform/state/implementations/util.ts b/libs/common/src/platform/state/implementations/util.ts index f3d57fbafc4..0a9d76f6da5 100644 --- a/libs/common/src/platform/state/implementations/util.ts +++ b/libs/common/src/platform/state/implementations/util.ts @@ -5,12 +5,11 @@ import { AbstractStorageService } from "../../abstractions/storage.service"; export async function getStoredValue( key: string, storage: AbstractStorageService, - deserializer: (jsonValue: Jsonify) => T, + deserializer: (jsonValue: Jsonify) => T | null, ) { if (storage.valuesRequireDeserialization) { const jsonValue = await storage.get>(key); - const value = deserializer(jsonValue); - return value; + return deserializer(jsonValue); } else { const value = await storage.get(key); return value ?? null; diff --git a/libs/common/src/platform/state/key-definition.ts b/libs/common/src/platform/state/key-definition.ts index a270fc3e1a2..519e98ef52d 100644 --- a/libs/common/src/platform/state/key-definition.ts +++ b/libs/common/src/platform/state/key-definition.ts @@ -42,7 +42,7 @@ export type KeyDefinitionOptions = { * @param jsonValue The JSON object representation of your state. * @returns The fully typed version of your state. */ - readonly deserializer: (jsonValue: Jsonify) => T; + readonly deserializer: (jsonValue: Jsonify) => T | null; /** * The number of milliseconds to wait before cleaning up the state after the last subscriber has unsubscribed. * Defaults to 1000ms. diff --git a/libs/common/src/platform/state/state-definitions.ts b/libs/common/src/platform/state/state-definitions.ts index 1ed5227cb13..b37aeb442ed 100644 --- a/libs/common/src/platform/state/state-definitions.ts +++ b/libs/common/src/platform/state/state-definitions.ts @@ -29,9 +29,20 @@ export const ORGANIZATION_MANAGEMENT_PREFERENCES_DISK = new StateDefinition( web: "disk-local", }, ); -export const AC_BANNERS_DISMISSED_DISK = new StateDefinition("acBannersDismissed", "disk", { - web: "disk-local", -}); +export const ACCOUNT_DEPROVISIONING_BANNER_DISK = new StateDefinition( + "showAccountDeprovisioningBanner", + "disk", + { + web: "disk-local", + }, +); +export const DELETE_MANAGED_USER_WARNING = new StateDefinition( + "showDeleteManagedUserWarning", + "disk", + { + web: "disk-local", + }, +); // Billing export const BILLING_DISK = new StateDefinition("billing", "disk"); @@ -120,6 +131,10 @@ export const THEMING_DISK = new StateDefinition("theming", "disk", { web: "disk- export const TRANSLATION_DISK = new StateDefinition("translation", "disk", { web: "disk-local" }); export const ANIMATION_DISK = new StateDefinition("animation", "disk"); export const TASK_SCHEDULER_DISK = new StateDefinition("taskScheduler", "disk"); +export const EXTENSION_INITIAL_INSTALL_DISK = new StateDefinition( + "extensionInitialInstall", + "disk", +); // Design System @@ -133,6 +148,7 @@ export const SM_ONBOARDING_DISK = new StateDefinition("smOnboarding", "disk", { // Tools +export const EXTENSION_DISK = new StateDefinition("extension", "disk"); export const GENERATOR_DISK = new StateDefinition("generator", "disk"); export const GENERATOR_MEMORY = new StateDefinition("generator", "memory"); export const BROWSER_SEND_MEMORY = new StateDefinition("sendBrowser", "memory"); @@ -179,7 +195,6 @@ export const PREMIUM_BANNER_DISK_LOCAL = new StateDefinition("premiumBannerRepro web: "disk-local", }); export const BANNERS_DISMISSED_DISK = new StateDefinition("bannersDismissed", "disk"); -export const VAULT_BROWSER_UI_ONBOARDING = new StateDefinition("vaultBrowserUiOnboarding", "disk"); export const NEW_DEVICE_VERIFICATION_NOTICE = new StateDefinition( "newDeviceVerificationNotice", "disk", @@ -188,3 +203,6 @@ export const NEW_DEVICE_VERIFICATION_NOTICE = new StateDefinition( }, ); export const VAULT_APPEARANCE = new StateDefinition("vaultAppearance", "disk"); +export const SECURITY_TASKS_DISK = new StateDefinition("securityTasks", "disk"); +export const AT_RISK_PASSWORDS_PAGE_DISK = new StateDefinition("atRiskPasswordsPage", "disk"); +export const NOTIFICATION_DISK = new StateDefinition("notifications", "disk"); diff --git a/libs/common/src/platform/state/state.provider.ts b/libs/common/src/platform/state/state.provider.ts index 44736500afc..dc8cb3e9359 100644 --- a/libs/common/src/platform/state/state.provider.ts +++ b/libs/common/src/platform/state/state.provider.ts @@ -60,9 +60,9 @@ export abstract class StateProvider { */ abstract setUserState( keyDefinition: UserKeyDefinition, - value: T, + value: T | null, userId?: UserId, - ): Promise<[UserId, T]>; + ): Promise<[UserId, T | null]>; /** @see{@link ActiveUserStateProvider.get} */ abstract getActive(userKeyDefinition: UserKeyDefinition): ActiveUserState; diff --git a/libs/common/src/platform/state/user-state.ts b/libs/common/src/platform/state/user-state.ts index 44bc8732544..26fa6f83fa3 100644 --- a/libs/common/src/platform/state/user-state.ts +++ b/libs/common/src/platform/state/user-state.ts @@ -12,10 +12,11 @@ export interface UserState { readonly state$: Observable; /** Emits a stream of tuples, with the first element being a user id and the second element being the data for that user. */ - readonly combinedState$: Observable>; + readonly combinedState$: Observable>; } export const activeMarker: unique symbol = Symbol("active"); + export interface ActiveUserState extends UserState { readonly [activeMarker]: true; @@ -32,15 +33,16 @@ export interface ActiveUserState extends UserState { * @param options.shouldUpdate A callback for determining if you want to update state. Defaults to () => true * @param options.combineLatestWith An observable that you want to combine with the current state for callbacks. Defaults to null * @param options.msTimeout A timeout for how long you are willing to wait for a `combineLatestWith` option to complete. Defaults to 1000ms. Only applies if `combineLatestWith` is set. - + * * @returns A promise that must be awaited before your next action to ensure the update has been written to state. * Resolves to the new state. If `shouldUpdate` returns false, the promise will resolve to the current state. */ readonly update: ( - configureState: (state: T, dependencies: TCombine) => T, + configureState: (state: T | null, dependencies: TCombine) => T | null, options?: StateUpdateOptions, - ) => Promise<[UserId, T]>; + ) => Promise<[UserId, T | null]>; } + export interface SingleUserState extends UserState { readonly userId: UserId; @@ -51,12 +53,12 @@ export interface SingleUserState extends UserState { * @param options.shouldUpdate A callback for determining if you want to update state. Defaults to () => true * @param options.combineLatestWith An observable that you want to combine with the current state for callbacks. Defaults to null * @param options.msTimeout A timeout for how long you are willing to wait for a `combineLatestWith` option to complete. Defaults to 1000ms. Only applies if `combineLatestWith` is set. - + * * @returns A promise that must be awaited before your next action to ensure the update has been written to state. * Resolves to the new state. If `shouldUpdate` returns false, the promise will resolve to the current state. */ readonly update: ( - configureState: (state: T, dependencies: TCombine) => T, + configureState: (state: T | null, dependencies: TCombine) => T | null, options?: StateUpdateOptions, - ) => Promise; + ) => Promise; } diff --git a/libs/common/src/platform/sync/core-sync.service.ts b/libs/common/src/platform/sync/core-sync.service.ts index cfa9030c9de..92a10baf6d2 100644 --- a/libs/common/src/platform/sync/core-sync.service.ts +++ b/libs/common/src/platform/sync/core-sync.service.ts @@ -129,12 +129,18 @@ export abstract class CoreSyncService implements SyncService { return this.syncCompleted(false); } - async syncUpsertCipher(notification: SyncCipherNotification, isEdit: boolean): Promise { + async syncUpsertCipher( + notification: SyncCipherNotification, + isEdit: boolean, + userId: UserId, + ): Promise { this.syncStarted(); - if (await this.stateService.getIsAuthenticated()) { + + const authStatus = await firstValueFrom(this.authService.authStatusFor$(userId)); + if (authStatus >= AuthenticationStatus.Locked) { try { let shouldUpdate = true; - const localCipher = await this.cipherService.get(notification.id); + const localCipher = await this.cipherService.get(notification.id, userId); if (localCipher != null && localCipher.revisionDate >= notification.revisionDate) { shouldUpdate = false; } @@ -182,7 +188,7 @@ export abstract class CoreSyncService implements SyncService { } } catch (e) { if (e != null && e.statusCode === 404 && isEdit) { - await this.cipherService.delete(notification.id); + await this.cipherService.delete(notification.id, userId); this.messageSender.send("syncedDeletedCipher", { cipherId: notification.id }); return this.syncCompleted(true); } @@ -191,10 +197,12 @@ export abstract class CoreSyncService implements SyncService { return this.syncCompleted(false); } - async syncDeleteCipher(notification: SyncCipherNotification): Promise { + async syncDeleteCipher(notification: SyncCipherNotification, userId: UserId): Promise { this.syncStarted(); - if (await this.stateService.getIsAuthenticated()) { - await this.cipherService.delete(notification.id); + + const authStatus = await firstValueFrom(this.authService.authStatusFor$(userId)); + if (authStatus >= AuthenticationStatus.Locked) { + await this.cipherService.delete(notification.id, userId); this.messageSender.send("syncedDeletedCipher", { cipherId: notification.id }); return this.syncCompleted(true); } diff --git a/libs/common/src/platform/sync/default-sync.service.ts b/libs/common/src/platform/sync/default-sync.service.ts index 9e36aa69417..656267d864c 100644 --- a/libs/common/src/platform/sync/default-sync.service.ts +++ b/libs/common/src/platform/sync/default-sync.service.ts @@ -7,10 +7,14 @@ import { CollectionData, CollectionDetailsResponse, } from "@bitwarden/admin-console/common"; +import { KeyService } from "@bitwarden/key-management"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { UserDecryptionOptionsServiceAbstraction } from "../../../../auth/src/common/abstractions"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { LogoutReason } from "../../../../auth/src/common/types"; -import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { ApiService } from "../../abstractions/api.service"; import { InternalOrganizationServiceAbstraction } from "../../admin-console/abstractions/organization/organization.service.abstraction"; import { InternalPolicyService } from "../../admin-console/abstractions/policy/policy.service.abstraction"; @@ -23,13 +27,13 @@ import { PolicyResponse } from "../../admin-console/models/response/policy.respo import { AccountService } from "../../auth/abstractions/account.service"; import { AuthService } from "../../auth/abstractions/auth.service"; import { AvatarService } from "../../auth/abstractions/avatar.service"; -import { KeyConnectorService } from "../../auth/abstractions/key-connector.service"; -import { InternalMasterPasswordServiceAbstraction } from "../../auth/abstractions/master-password.service.abstraction"; import { TokenService } from "../../auth/abstractions/token.service"; import { AuthenticationStatus } from "../../auth/enums/authentication-status"; import { ForceSetPasswordReason } from "../../auth/models/domain/force-set-password-reason"; import { DomainSettingsService } from "../../autofill/services/domain-settings.service"; import { BillingAccountProfileStateService } from "../../billing/abstractions"; +import { KeyConnectorService } from "../../key-management/key-connector/abstractions/key-connector.service"; +import { InternalMasterPasswordServiceAbstraction } from "../../key-management/master-password/abstractions/master-password.service.abstraction"; import { DomainsResponse } from "../../models/response/domains.response"; import { ProfileResponse } from "../../models/response/profile.response"; import { SendData } from "../../tools/send/models/data/send.data"; @@ -191,6 +195,7 @@ export class DefaultSyncService extends CoreSyncService { await this.avatarService.setSyncAvatarColor(response.id, response.avatarColor); await this.tokenService.setSecurityStamp(response.securityStamp, response.id); await this.accountService.setAccountEmailVerified(response.id, response.emailVerified); + await this.accountService.setAccountVerifyNewDeviceLogin(response.id, response.verifyDevices); await this.billingAccountProfileStateService.setHasPremium( response.premiumPersonally, diff --git a/libs/common/src/platform/sync/sync.service.ts b/libs/common/src/platform/sync/sync.service.ts index 6763e01cab7..967e4db27a5 100644 --- a/libs/common/src/platform/sync/sync.service.ts +++ b/libs/common/src/platform/sync/sync.service.ts @@ -62,8 +62,9 @@ export abstract class SyncService { abstract syncUpsertCipher( notification: SyncCipherNotification, isEdit: boolean, + userId: UserId, ): Promise; - abstract syncDeleteCipher(notification: SyncFolderNotification): Promise; + abstract syncDeleteCipher(notification: SyncFolderNotification, userId: UserId): Promise; abstract syncUpsertSend(notification: SyncSendNotification, isEdit: boolean): Promise; abstract syncDeleteSend(notification: SyncSendNotification): Promise; } diff --git a/libs/common/src/platform/theming/theme-state.service.ts b/libs/common/src/platform/theming/theme-state.service.ts index bb146be4927..a02400b5b3a 100644 --- a/libs/common/src/platform/theming/theme-state.service.ts +++ b/libs/common/src/platform/theming/theme-state.service.ts @@ -1,39 +1,33 @@ -import { Observable, combineLatest, map } from "rxjs"; +import { Observable, map } from "rxjs"; -import { FeatureFlag } from "../../enums/feature-flag.enum"; -import { ConfigService } from "../abstractions/config/config.service"; -import { ThemeType } from "../enums"; +import { Theme, ThemeTypes } from "../enums"; import { GlobalStateProvider, KeyDefinition, THEMING_DISK } from "../state"; export abstract class ThemeStateService { /** * The users selected theme. */ - abstract selectedTheme$: Observable; + abstract selectedTheme$: Observable; /** * A method for updating the current users configured theme. * @param theme The chosen user theme. */ - abstract setSelectedTheme(theme: ThemeType): Promise; + abstract setSelectedTheme(theme: Theme): Promise; } -export const THEME_SELECTION = new KeyDefinition(THEMING_DISK, "selection", { +export const THEME_SELECTION = new KeyDefinition(THEMING_DISK, "selection", { deserializer: (s) => s, }); export class DefaultThemeStateService implements ThemeStateService { private readonly selectedThemeState = this.globalStateProvider.get(THEME_SELECTION); - selectedTheme$ = combineLatest([ - this.selectedThemeState.state$, - this.configService.getFeatureFlag$(FeatureFlag.ExtensionRefresh), - ]).pipe( - map(([theme, isExtensionRefresh]) => { - // The extension refresh should not allow for Nord or SolarizedDark - // Default the user to their system theme - if (isExtensionRefresh && [ThemeType.Nord, ThemeType.SolarizedDark].includes(theme)) { - return ThemeType.System; + selectedTheme$ = this.selectedThemeState.state$.pipe( + map((theme) => { + // We used to support additional themes. Since these are no longer supported we return null to default to the system theme. + if (theme != null && !Object.values(ThemeTypes).includes(theme)) { + return null; } return theme; @@ -43,11 +37,10 @@ export class DefaultThemeStateService implements ThemeStateService { constructor( private globalStateProvider: GlobalStateProvider, - private configService: ConfigService, - private defaultTheme: ThemeType = ThemeType.System, + private defaultTheme: Theme = ThemeTypes.System, ) {} - async setSelectedTheme(theme: ThemeType): Promise { + async setSelectedTheme(theme: Theme): Promise { await this.selectedThemeState.update(() => theme, { shouldUpdate: (currentTheme) => currentTheme !== theme, }); diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index dc0a8d61f64..2ff2fb01c87 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -3,16 +3,16 @@ import { firstValueFrom } from "rxjs"; import { - CollectionRequest, CollectionAccessDetailsResponse, CollectionDetailsResponse, + CollectionRequest, CollectionResponse, } from "@bitwarden/admin-console/common"; import { LogoutReason } from "@bitwarden/auth/common"; import { ApiService as ApiServiceAbstraction } from "../abstractions/api.service"; -import { VaultTimeoutSettingsService } from "../abstractions/vault-timeout/vault-timeout-settings.service"; import { OrganizationConnectionType } from "../admin-console/enums"; +import { CollectionBulkDeleteRequest } from "../admin-console/models/request/collection-bulk-delete.request"; import { OrganizationSponsorshipCreateRequest } from "../admin-console/models/request/organization/organization-sponsorship-create.request"; import { OrganizationSponsorshipRedeemRequest } from "../admin-console/models/request/organization/organization-sponsorship-redeem.request"; import { OrganizationConnectionRequest } from "../admin-console/models/request/organization-connection.request"; @@ -44,7 +44,6 @@ import { } from "../admin-console/models/response/provider/provider-user.response"; import { SelectionReadOnlyResponse } from "../admin-console/models/response/selection-read-only.response"; import { TokenService } from "../auth/abstractions/token.service"; -import { AuthRequest } from "../auth/models/request/auth.request"; import { DeviceVerificationRequest } from "../auth/models/request/device-verification.request"; import { DisableTwoFactorAuthenticatorRequest } from "../auth/models/request/disable-two-factor-authenticator.request"; import { EmailTokenRequest } from "../auth/models/request/email-token.request"; @@ -55,19 +54,12 @@ import { SsoTokenRequest } from "../auth/models/request/identity-token/sso-token import { TokenTwoFactorRequest } from "../auth/models/request/identity-token/token-two-factor.request"; import { UserApiTokenRequest } from "../auth/models/request/identity-token/user-api-token.request"; import { WebAuthnLoginTokenRequest } from "../auth/models/request/identity-token/webauthn-login-token.request"; -import { KeyConnectorUserKeyRequest } from "../auth/models/request/key-connector-user-key.request"; import { PasswordHintRequest } from "../auth/models/request/password-hint.request"; -import { PasswordRequest } from "../auth/models/request/password.request"; import { PasswordlessAuthRequest } from "../auth/models/request/passwordless-auth.request"; import { SecretVerificationRequest } from "../auth/models/request/secret-verification.request"; -import { SetKeyConnectorKeyRequest } from "../auth/models/request/set-key-connector-key.request"; -import { SetPasswordRequest } from "../auth/models/request/set-password.request"; import { TwoFactorEmailRequest } from "../auth/models/request/two-factor-email.request"; import { TwoFactorProviderRequest } from "../auth/models/request/two-factor-provider.request"; -import { TwoFactorRecoveryRequest } from "../auth/models/request/two-factor-recovery.request"; import { UpdateProfileRequest } from "../auth/models/request/update-profile.request"; -import { UpdateTdeOffboardingPasswordRequest } from "../auth/models/request/update-tde-offboarding-password.request"; -import { UpdateTempPasswordRequest } from "../auth/models/request/update-temp-password.request"; import { UpdateTwoFactorAuthenticatorRequest } from "../auth/models/request/update-two-factor-authenticator.request"; import { UpdateTwoFactorDuoRequest } from "../auth/models/request/update-two-factor-duo.request"; import { UpdateTwoFactorEmailRequest } from "../auth/models/request/update-two-factor-email.request"; @@ -78,6 +70,7 @@ import { ApiKeyResponse } from "../auth/models/response/api-key.response"; import { AuthRequestResponse } from "../auth/models/response/auth-request.response"; import { DeviceVerificationResponse } from "../auth/models/response/device-verification.response"; import { IdentityCaptchaResponse } from "../auth/models/response/identity-captcha.response"; +import { IdentityDeviceVerificationResponse } from "../auth/models/response/identity-device-verification.response"; import { IdentityTokenResponse } from "../auth/models/response/identity-token.response"; import { IdentityTwoFactorResponse } from "../auth/models/response/identity-two-factor.response"; import { KeyConnectorUserKeyResponse } from "../auth/models/response/key-connector-user-key.response"; @@ -103,10 +96,11 @@ import { PaymentResponse } from "../billing/models/response/payment.response"; import { PlanResponse } from "../billing/models/response/plan.response"; import { SubscriptionResponse } from "../billing/models/response/subscription.response"; import { TaxInfoResponse } from "../billing/models/response/tax-info.response"; -import { TaxRateResponse } from "../billing/models/response/tax-rate.response"; import { DeviceType } from "../enums"; -import { VaultTimeoutAction } from "../enums/vault-timeout-action.enum"; -import { CollectionBulkDeleteRequest } from "../models/request/collection-bulk-delete.request"; +import { KeyConnectorUserKeyRequest } from "../key-management/key-connector/models/key-connector-user-key.request"; +import { SetKeyConnectorKeyRequest } from "../key-management/key-connector/models/set-key-connector-key.request"; +import { VaultTimeoutSettingsService } from "../key-management/vault-timeout"; +import { VaultTimeoutAction } from "../key-management/vault-timeout/enums/vault-timeout-action.enum"; import { DeleteRecoverRequest } from "../models/request/delete-recover.request"; import { EventRequest } from "../models/request/event.request"; import { KdfRequest } from "../models/request/kdf.request"; @@ -158,6 +152,13 @@ export class ApiService implements ApiServiceAbstraction { private deviceType: string; private isWebClient = false; private isDesktopClient = false; + private refreshTokenPromise: Promise | undefined; + + /** + * The message (responseJson.ErrorModel.Message) that comes back from the server when a new device verification is required. + */ + private static readonly NEW_DEVICE_VERIFICATION_REQUIRED_MESSAGE = + "new device verification required"; constructor( private tokenService: TokenService, @@ -198,7 +199,12 @@ export class ApiService implements ApiServiceAbstraction { | PasswordTokenRequest | SsoTokenRequest | WebAuthnLoginTokenRequest, - ): Promise { + ): Promise< + | IdentityTokenResponse + | IdentityTwoFactorResponse + | IdentityCaptchaResponse + | IdentityDeviceVerificationResponse + > { const headers = new Headers({ "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", Accept: "application/json", @@ -246,6 +252,11 @@ export class ApiService implements ApiServiceAbstraction { Object.keys(responseJson.HCaptcha_SiteKey).length ) { return new IdentityCaptchaResponse(responseJson); + } else if ( + response.status === 400 && + responseJson?.ErrorModel?.Message === ApiService.NEW_DEVICE_VERIFICATION_REQUIRED_MESSAGE + ) { + return new IdentityDeviceVerificationResponse(responseJson); } } @@ -262,22 +273,6 @@ export class ApiService implements ApiServiceAbstraction { } // TODO: PM-3519: Create and move to AuthRequest Api service - // TODO: PM-9724: Remove legacy auth request methods when we remove legacy LoginViaAuthRequestV1Components - async postAuthRequest(request: AuthRequest): Promise { - const r = await this.send("POST", "/auth-requests/", request, false, true); - return new AuthRequestResponse(r); - } - async postAdminAuthRequest(request: AuthRequest): Promise { - const r = await this.send("POST", "/auth-requests/admin-request", request, true, true); - return new AuthRequestResponse(r); - } - - async getAuthResponse(id: string, accessCode: string): Promise { - const path = `/auth-requests/${id}/response?code=${accessCode}`; - const r = await this.send("GET", path, null, false, true); - return new AuthRequestResponse(r); - } - async getAuthRequest(id: string): Promise { const path = `/auth-requests/${id}`; const r = await this.send("GET", path, null, true, true); @@ -357,14 +352,6 @@ export class ApiService implements ApiServiceAbstraction { return this.send("POST", "/accounts/email", request, true, false); } - postPassword(request: PasswordRequest): Promise { - return this.send("POST", "/accounts/password", request, true, false); - } - - setPassword(request: SetPasswordRequest): Promise { - return this.send("POST", "/accounts/set-password", request, true, false); - } - postSetKeyConnectorKey(request: SetKeyConnectorKeyRequest): Promise { return this.send("POST", "/accounts/set-key-connector-key", request, true, false); } @@ -462,14 +449,6 @@ export class ApiService implements ApiServiceAbstraction { return new ApiKeyResponse(r); } - putUpdateTempPassword(request: UpdateTempPasswordRequest): Promise { - return this.send("PUT", "/accounts/update-temp-password", request, true, false); - } - - putUpdateTdeOffboardingPassword(request: UpdateTdeOffboardingPasswordRequest): Promise { - return this.send("PUT", "/accounts/update-tde-offboarding-password", request, true, false); - } - postConvertToKeyConnector(): Promise { return this.send("POST", "/accounts/convert-to-key-connector", null, true, false); } @@ -685,7 +664,7 @@ export class ApiService implements ApiServiceAbstraction { } deleteCipherAttachment(id: string, attachmentId: string): Promise { - return this.send("DELETE", "/ciphers/" + id + "/attachment/" + attachmentId, null, true, false); + return this.send("DELETE", "/ciphers/" + id + "/attachment/" + attachmentId, null, true, true); } deleteCipherAttachmentAdmin(id: string, attachmentId: string): Promise { @@ -897,11 +876,6 @@ export class ApiService implements ApiServiceAbstraction { return new ListResponse(r, PlanResponse); } - async getTaxRates(): Promise> { - const r = await this.send("GET", "/plans/sales-tax-rates/", null, true, true); - return new ListResponse(r, TaxRateResponse); - } - // Settings APIs async getSettingsDomains(): Promise { @@ -1089,10 +1063,6 @@ export class ApiService implements ApiServiceAbstraction { return new TwoFactorProviderResponse(r); } - postTwoFactorRecover(request: TwoFactorRecoveryRequest): Promise { - return this.send("POST", "/two-factor/recover", request, false, false); - } - postTwoFactorEmailSetup(request: TwoFactorEmailRequest): Promise { return this.send("POST", "/two-factor/send-email", request, true, false); } @@ -1722,7 +1692,18 @@ export class ApiService implements ApiServiceAbstraction { ); } - protected async refreshToken(): Promise { + // Keep the running refreshTokenPromise to prevent parallel calls. + protected refreshToken(): Promise { + if (this.refreshTokenPromise === undefined) { + this.refreshTokenPromise = this.internalRefreshToken(); + void this.refreshTokenPromise.finally(() => { + this.refreshTokenPromise = undefined; + }); + } + return this.refreshTokenPromise; + } + + private async internalRefreshToken(): Promise { const refreshToken = await this.tokenService.getRefreshToken(); if (refreshToken != null && refreshToken !== "") { return this.refreshAccessToken(); @@ -1835,12 +1816,12 @@ export class ApiService implements ApiServiceAbstraction { } async send( - method: "GET" | "POST" | "PUT" | "DELETE", + method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", path: string, body: any, authed: boolean, hasResponse: boolean, - apiUrl?: string, + apiUrl?: string | null, alterHeaders?: (headers: Headers) => void, ): Promise { const env = await firstValueFrom(this.environmentService.environment$); @@ -1875,7 +1856,7 @@ export class ApiService implements ApiServiceAbstraction { return responseJson; } else if (hasResponse && response.status === 200 && responseIsCsv) { return await response.text(); - } else if (response.status !== 200) { + } else if (response.status !== 200 && response.status !== 204) { const error = await this.handleError(response, false, authed); return Promise.reject(error); } diff --git a/libs/common/src/services/event/event-collection.service.ts b/libs/common/src/services/event/event-collection.service.ts index b06985e0ba7..b37ec0de271 100644 --- a/libs/common/src/services/event/event-collection.service.ts +++ b/libs/common/src/services/event/event-collection.service.ts @@ -1,11 +1,15 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { firstValueFrom, map, from, zip, Observable } from "rxjs"; +import { firstValueFrom, map, from, zip } from "rxjs"; + +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { UserId } from "@bitwarden/common/types/guid"; import { EventCollectionService as EventCollectionServiceAbstraction } from "../../abstractions/event/event-collection.service"; import { EventUploadService } from "../../abstractions/event/event-upload.service"; -import { OrganizationService } from "../../admin-console/abstractions/organization/organization.service.abstraction"; -import { AccountService } from "../../auth/abstractions/account.service"; import { AuthService } from "../../auth/abstractions/auth.service"; import { AuthenticationStatus } from "../../auth/enums/authentication-status"; import { EventType } from "../../enums"; @@ -17,8 +21,6 @@ import { CipherView } from "../../vault/models/view/cipher.view"; import { EVENT_COLLECTION } from "./key-definitions"; export class EventCollectionService implements EventCollectionServiceAbstraction { - private orgIds$: Observable; - constructor( private cipherService: CipherService, private stateProvider: StateProvider, @@ -26,11 +28,11 @@ export class EventCollectionService implements EventCollectionServiceAbstraction private eventUploadService: EventUploadService, private authService: AuthService, private accountService: AccountService, - ) { - this.orgIds$ = this.organizationService.organizations$.pipe( - map((orgs) => orgs?.filter((o) => o.useEvents)?.map((x) => x.id) ?? []), - ); - } + ) {} + + private getOrgIds = (orgs: Organization[]): string[] => { + return orgs?.filter((o) => o.useEvents)?.map((x) => x.id) ?? []; + }; /** Adds an event to the active user's event collection * @param eventType the event type to be added @@ -42,14 +44,15 @@ export class EventCollectionService implements EventCollectionServiceAbstraction ciphers: CipherView[], uploadImmediately = false, ): Promise { - const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id))); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const eventStore = this.stateProvider.getUser(userId, EVENT_COLLECTION); - if (!(await this.shouldUpdate(null, eventType, ciphers))) { + if (!(await this.shouldUpdate(userId, null, eventType, ciphers))) { return; } - const events$ = this.orgIds$.pipe( + const events$ = this.organizationService.organizations$(userId).pipe( + map((orgs) => this.getOrgIds(orgs)), map((orgs) => ciphers .filter((c) => orgs.includes(c.organizationId)) @@ -86,10 +89,10 @@ export class EventCollectionService implements EventCollectionServiceAbstraction uploadImmediately = false, organizationId: string = null, ): Promise { - const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id))); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const eventStore = this.stateProvider.getUser(userId, EVENT_COLLECTION); - if (!(await this.shouldUpdate(organizationId, eventType, undefined, cipherId))) { + if (!(await this.shouldUpdate(userId, organizationId, eventType, undefined, cipherId))) { return; } @@ -111,19 +114,25 @@ export class EventCollectionService implements EventCollectionServiceAbstraction } /** Verifies if the event collection should be updated for the provided information + * @param userId the active user's id * @param cipherId the cipher for the event * @param organizationId the organization for the event */ private async shouldUpdate( + userId: UserId, organizationId: string = null, eventType: EventType = null, ciphers: CipherView[] = [], cipherId?: string, ): Promise { - const cipher$ = from(this.cipherService.get(cipherId)); + const cipher$ = from(this.cipherService.get(cipherId, userId)); + + const orgIds$ = this.organizationService + .organizations$(userId) + .pipe(map((orgs) => this.getOrgIds(orgs))); const [authStatus, orgIds, cipher] = await firstValueFrom( - zip(this.authService.activeAccountStatus$, this.orgIds$, cipher$), + zip(this.authService.activeAccountStatus$, orgIds$, cipher$), ); // The user must be authorized diff --git a/libs/common/src/services/notifications.service.ts b/libs/common/src/services/notifications.service.ts deleted file mode 100644 index f88c904bee1..00000000000 --- a/libs/common/src/services/notifications.service.ts +++ /dev/null @@ -1,280 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import * as signalR from "@microsoft/signalr"; -import * as signalRMsgPack from "@microsoft/signalr-protocol-msgpack"; -import { firstValueFrom, Subscription } from "rxjs"; - -import { LogoutReason } from "@bitwarden/auth/common"; - -import { ApiService } from "../abstractions/api.service"; -import { NotificationsService as NotificationsServiceAbstraction } from "../abstractions/notifications.service"; -import { AuthService } from "../auth/abstractions/auth.service"; -import { AuthenticationStatus } from "../auth/enums/authentication-status"; -import { NotificationType } from "../enums"; -import { - NotificationResponse, - SyncCipherNotification, - SyncFolderNotification, - SyncSendNotification, -} from "../models/response/notification.response"; -import { AppIdService } from "../platform/abstractions/app-id.service"; -import { EnvironmentService } from "../platform/abstractions/environment.service"; -import { LogService } from "../platform/abstractions/log.service"; -import { MessagingService } from "../platform/abstractions/messaging.service"; -import { StateService } from "../platform/abstractions/state.service"; -import { ScheduledTaskNames } from "../platform/scheduling/scheduled-task-name.enum"; -import { TaskSchedulerService } from "../platform/scheduling/task-scheduler.service"; -import { SyncService } from "../vault/abstractions/sync/sync.service.abstraction"; - -export class NotificationsService implements NotificationsServiceAbstraction { - private signalrConnection: signalR.HubConnection; - private url: string; - private connected = false; - private inited = false; - private inactive = false; - private reconnectTimerSubscription: Subscription; - private isSyncingOnReconnect = true; - - constructor( - private logService: LogService, - private syncService: SyncService, - private appIdService: AppIdService, - private apiService: ApiService, - private environmentService: EnvironmentService, - private logoutCallback: (logoutReason: LogoutReason) => Promise, - private stateService: StateService, - private authService: AuthService, - private messagingService: MessagingService, - private taskSchedulerService: TaskSchedulerService, - ) { - this.taskSchedulerService.registerTaskHandler( - ScheduledTaskNames.notificationsReconnectTimeout, - () => this.reconnect(this.isSyncingOnReconnect), - ); - this.environmentService.environment$.subscribe(() => { - if (!this.inited) { - return; - } - - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.init(); - }); - } - - async init(): Promise { - this.inited = false; - this.url = (await firstValueFrom(this.environmentService.environment$)).getNotificationsUrl(); - - // Set notifications server URL to `https://-` to effectively disable communication - // with the notifications server from the client app - if (this.url === "https://-") { - return; - } - - if (this.signalrConnection != null) { - this.signalrConnection.off("ReceiveMessage"); - this.signalrConnection.off("Heartbeat"); - await this.signalrConnection.stop(); - this.connected = false; - this.signalrConnection = null; - } - - this.signalrConnection = new signalR.HubConnectionBuilder() - .withUrl(this.url + "/hub", { - accessTokenFactory: () => this.apiService.getActiveBearerToken(), - skipNegotiation: true, - transport: signalR.HttpTransportType.WebSockets, - }) - .withHubProtocol(new signalRMsgPack.MessagePackHubProtocol() as signalR.IHubProtocol) - // .configureLogging(signalR.LogLevel.Trace) - .build(); - - this.signalrConnection.on("ReceiveMessage", (data: any) => - this.processNotification(new NotificationResponse(data)), - ); - // eslint-disable-next-line - this.signalrConnection.on("Heartbeat", (data: any) => { - /*console.log('Heartbeat!');*/ - }); - this.signalrConnection.onclose(() => { - this.connected = false; - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.reconnect(true); - }); - this.inited = true; - if (await this.isAuthedAndUnlocked()) { - await this.reconnect(false); - } - } - - async updateConnection(sync = false): Promise { - if (!this.inited) { - return; - } - try { - if (await this.isAuthedAndUnlocked()) { - await this.reconnect(sync); - } else { - await this.signalrConnection.stop(); - } - } catch (e) { - this.logService.error(e.toString()); - } - } - - async reconnectFromActivity(): Promise { - this.inactive = false; - if (this.inited && !this.connected) { - await this.reconnect(true); - } - } - - async disconnectFromInactivity(): Promise { - this.inactive = true; - if (this.inited && this.connected) { - await this.signalrConnection.stop(); - } - } - - private async processNotification(notification: NotificationResponse) { - const appId = await this.appIdService.getAppId(); - if (notification == null || notification.contextId === appId) { - return; - } - - const isAuthenticated = await this.stateService.getIsAuthenticated(); - const payloadUserId = notification.payload.userId || notification.payload.UserId; - const myUserId = await this.stateService.getUserId(); - if (isAuthenticated && payloadUserId != null && payloadUserId !== myUserId) { - return; - } - - switch (notification.type) { - case NotificationType.SyncCipherCreate: - case NotificationType.SyncCipherUpdate: - await this.syncService.syncUpsertCipher( - notification.payload as SyncCipherNotification, - notification.type === NotificationType.SyncCipherUpdate, - ); - break; - case NotificationType.SyncCipherDelete: - case NotificationType.SyncLoginDelete: - await this.syncService.syncDeleteCipher(notification.payload as SyncCipherNotification); - break; - case NotificationType.SyncFolderCreate: - case NotificationType.SyncFolderUpdate: - await this.syncService.syncUpsertFolder( - notification.payload as SyncFolderNotification, - notification.type === NotificationType.SyncFolderUpdate, - payloadUserId, - ); - break; - case NotificationType.SyncFolderDelete: - await this.syncService.syncDeleteFolder( - notification.payload as SyncFolderNotification, - payloadUserId, - ); - break; - case NotificationType.SyncVault: - case NotificationType.SyncCiphers: - case NotificationType.SyncSettings: - if (isAuthenticated) { - await this.syncService.fullSync(false); - } - break; - case NotificationType.SyncOrganizations: - if (isAuthenticated) { - // An organization update may not have bumped the user's account revision date, so force a sync - await this.syncService.fullSync(true); - } - break; - case NotificationType.SyncOrgKeys: - if (isAuthenticated) { - await this.syncService.fullSync(true); - // Stop so a reconnect can be made - await this.signalrConnection.stop(); - } - break; - case NotificationType.LogOut: - if (isAuthenticated) { - this.logService.info("[Notifications Service] Received logout notification"); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.logoutCallback("logoutNotification"); - } - break; - case NotificationType.SyncSendCreate: - case NotificationType.SyncSendUpdate: - await this.syncService.syncUpsertSend( - notification.payload as SyncSendNotification, - notification.type === NotificationType.SyncSendUpdate, - ); - break; - case NotificationType.SyncSendDelete: - await this.syncService.syncDeleteSend(notification.payload as SyncSendNotification); - break; - case NotificationType.AuthRequest: - { - this.messagingService.send("openLoginApproval", { - notificationId: notification.payload.id, - }); - } - break; - case NotificationType.SyncOrganizationStatusChanged: - if (isAuthenticated) { - await this.syncService.fullSync(true); - } - break; - case NotificationType.SyncOrganizationCollectionSettingChanged: - if (isAuthenticated) { - await this.syncService.fullSync(true); - } - break; - default: - break; - } - } - - private async reconnect(sync: boolean) { - this.reconnectTimerSubscription?.unsubscribe(); - - if (this.connected || !this.inited || this.inactive) { - return; - } - const authedAndUnlocked = await this.isAuthedAndUnlocked(); - if (!authedAndUnlocked) { - return; - } - - try { - await this.signalrConnection.start(); - this.connected = true; - if (sync) { - await this.syncService.fullSync(false); - } - } catch (e) { - this.logService.error(e); - } - - if (!this.connected) { - this.isSyncingOnReconnect = sync; - this.reconnectTimerSubscription = this.taskSchedulerService.setTimeout( - ScheduledTaskNames.notificationsReconnectTimeout, - this.random(120000, 300000), - ); - } - } - - private async isAuthedAndUnlocked() { - const authStatus = await this.authService.getAuthStatus(); - return authStatus >= AuthenticationStatus.Unlocked; - } - - private random(min: number, max: number) { - min = Math.ceil(min); - max = Math.floor(max); - return Math.floor(Math.random() * (max - min + 1)) + min; - } -} diff --git a/libs/common/src/services/search.service.ts b/libs/common/src/services/search.service.ts index 3ee46818432..28e2902f102 100644 --- a/libs/common/src/services/search.service.ts +++ b/libs/common/src/services/search.service.ts @@ -9,13 +9,13 @@ import { UriMatchStrategy } from "../models/domain/domain-service"; import { I18nService } from "../platform/abstractions/i18n.service"; import { LogService } from "../platform/abstractions/log.service"; import { - ActiveUserState, + SingleUserState, StateProvider, UserKeyDefinition, VAULT_SEARCH_MEMORY, } from "../platform/state"; import { SendView } from "../tools/send/models/view/send.view"; -import { IndexedEntityId } from "../types/guid"; +import { IndexedEntityId, UserId } from "../types/guid"; import { FieldType } from "../vault/enums"; import { CipherType } from "../vault/enums/cipher-type"; import { CipherView } from "../vault/models/view/cipher.view"; @@ -70,24 +70,6 @@ export const LUNR_SEARCH_INDEXING = new UserKeyDefinition( export class SearchService implements SearchServiceAbstraction { private static registeredPipeline = false; - private searchIndexState: ActiveUserState = - this.stateProvider.getActive(LUNR_SEARCH_INDEX); - private readonly index$: Observable = this.searchIndexState.state$.pipe( - map((searchIndex) => (searchIndex ? lunr.Index.load(searchIndex) : null)), - ); - - private searchIndexEntityIdState: ActiveUserState = this.stateProvider.getActive( - LUNR_SEARCH_INDEXED_ENTITY_ID, - ); - readonly indexedEntityId$: Observable = - this.searchIndexEntityIdState.state$.pipe(map((id) => id)); - - private searchIsIndexingState: ActiveUserState = - this.stateProvider.getActive(LUNR_SEARCH_INDEXING); - private readonly searchIsIndexing$: Observable = this.searchIsIndexingState.state$.pipe( - map((indexing) => indexing ?? false), - ); - private readonly immediateSearchLocales: string[] = ["zh-CN", "zh-TW", "ja", "ko", "vi"]; private readonly defaultSearchableMinLength: number = 2; private searchableMinLength: number = this.defaultSearchableMinLength; @@ -114,15 +96,41 @@ export class SearchService implements SearchServiceAbstraction { } } - async clearIndex(): Promise { - await this.searchIndexEntityIdState.update(() => null); - await this.searchIndexState.update(() => null); - await this.searchIsIndexingState.update(() => null); + private searchIndexState(userId: UserId): SingleUserState { + return this.stateProvider.getUser(userId, LUNR_SEARCH_INDEX); } - async isSearchable(query: string): Promise { + private index$(userId: UserId): Observable { + return this.searchIndexState(userId).state$.pipe( + map((searchIndex) => (searchIndex ? lunr.Index.load(searchIndex) : null)), + ); + } + + private searchIndexEntityIdState(userId: UserId): SingleUserState { + return this.stateProvider.getUser(userId, LUNR_SEARCH_INDEXED_ENTITY_ID); + } + + indexedEntityId$(userId: UserId): Observable { + return this.searchIndexEntityIdState(userId).state$.pipe(map((id) => id)); + } + + private searchIsIndexingState(userId: UserId): SingleUserState { + return this.stateProvider.getUser(userId, LUNR_SEARCH_INDEXING); + } + + private searchIsIndexing$(userId: UserId): Observable { + return this.searchIsIndexingState(userId).state$.pipe(map((indexing) => indexing ?? false)); + } + + async clearIndex(userId: UserId): Promise { + await this.searchIndexEntityIdState(userId).update(() => null); + await this.searchIndexState(userId).update(() => null); + await this.searchIsIndexingState(userId).update(() => null); + } + + async isSearchable(userId: UserId, query: string): Promise { query = SearchService.normalizeSearchQuery(query); - const index = await this.getIndexForSearch(); + const index = await this.getIndexForSearch(userId); const notSearchable = query == null || (index == null && query.length < this.searchableMinLength) || @@ -130,13 +138,17 @@ export class SearchService implements SearchServiceAbstraction { return !notSearchable; } - async indexCiphers(ciphers: CipherView[], indexedEntityId?: string): Promise { - if (await this.getIsIndexing()) { + async indexCiphers( + userId: UserId, + ciphers: CipherView[], + indexedEntityId?: string, + ): Promise { + if (await this.getIsIndexing(userId)) { return; } - await this.setIsIndexing(true); - await this.setIndexedEntityIdForSearch(indexedEntityId as IndexedEntityId); + await this.setIsIndexing(userId, true); + await this.setIndexedEntityIdForSearch(userId, indexedEntityId as IndexedEntityId); const builder = new lunr.Builder(); builder.pipeline.add(this.normalizeAccentsPipelineFunction); builder.ref("id"); @@ -172,14 +184,15 @@ export class SearchService implements SearchServiceAbstraction { ciphers.forEach((c) => builder.add(c)); const index = builder.build(); - await this.setIndexForSearch(index.toJSON() as SerializedLunrIndex); + await this.setIndexForSearch(userId, index.toJSON() as SerializedLunrIndex); - await this.setIsIndexing(false); + await this.setIsIndexing(userId, false); this.logService.info("Finished search indexing"); } async searchCiphers( + userId: UserId, query: string, filter: ((cipher: CipherView) => boolean) | ((cipher: CipherView) => boolean)[] = null, ciphers: CipherView[], @@ -202,18 +215,18 @@ export class SearchService implements SearchServiceAbstraction { ciphers = ciphers.filter(filter as (cipher: CipherView) => boolean); } - if (!(await this.isSearchable(query))) { + if (!(await this.isSearchable(userId, query))) { return ciphers; } - if (await this.getIsIndexing()) { + if (await this.getIsIndexing(userId)) { await new Promise((r) => setTimeout(r, 250)); - if (await this.getIsIndexing()) { + if (await this.getIsIndexing(userId)) { await new Promise((r) => setTimeout(r, 500)); } } - const index = await this.getIndexForSearch(); + const index = await this.getIndexForSearch(userId); if (index == null) { // Fall back to basic search if index is not available return this.searchCiphersBasic(ciphers, query); @@ -307,24 +320,27 @@ export class SearchService implements SearchServiceAbstraction { return sendsMatched.concat(lowPriorityMatched); } - async getIndexForSearch(): Promise { - return await firstValueFrom(this.index$); + async getIndexForSearch(userId: UserId): Promise { + return await firstValueFrom(this.index$(userId)); } - private async setIndexForSearch(index: SerializedLunrIndex): Promise { - await this.searchIndexState.update(() => index); + private async setIndexForSearch(userId: UserId, index: SerializedLunrIndex): Promise { + await this.searchIndexState(userId).update(() => index); } - private async setIndexedEntityIdForSearch(indexedEntityId: IndexedEntityId): Promise { - await this.searchIndexEntityIdState.update(() => indexedEntityId); + private async setIndexedEntityIdForSearch( + userId: UserId, + indexedEntityId: IndexedEntityId, + ): Promise { + await this.searchIndexEntityIdState(userId).update(() => indexedEntityId); } - private async setIsIndexing(indexing: boolean): Promise { - await this.searchIsIndexingState.update(() => indexing); + private async setIsIndexing(userId: UserId, indexing: boolean): Promise { + await this.searchIsIndexingState(userId).update(() => indexing); } - private async getIsIndexing(): Promise { - return await firstValueFrom(this.searchIsIndexing$); + private async getIsIndexing(userId: UserId): Promise { + return await firstValueFrom(this.searchIsIndexing$(userId)); } private fieldExtractor(c: CipherView, joined: boolean) { diff --git a/libs/common/src/state-migrations/.eslintrc.json b/libs/common/src/state-migrations/.eslintrc.json deleted file mode 100644 index 4b66f0a32fa..00000000000 --- a/libs/common/src/state-migrations/.eslintrc.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "overrides": [ - { - "files": ["*"], - "rules": { - "import/no-restricted-paths": [ - "error", - { - "basePath": "libs/common/src/state-migrations", - "zones": [ - { - "target": "./", - "from": "../", - // Relative to from, not basePath - "except": ["state-migrations"], - "message": "State migrations should rarely import from the greater codebase. If you need to import from another location, take into account the likelihood of change in that code and consider copying to the migration instead." - } - ] - } - ] - } - } - ] -} diff --git a/libs/common/src/state-migrations/migrate.ts b/libs/common/src/state-migrations/migrate.ts index 81b1016a53d..b409f52d936 100644 --- a/libs/common/src/state-migrations/migrate.ts +++ b/libs/common/src/state-migrations/migrate.ts @@ -66,13 +66,15 @@ import { ForwarderOptionsMigrator } from "./migrations/65-migrate-forwarder-sett import { MoveFinalDesktopSettingsMigrator } from "./migrations/66-move-final-desktop-settings"; import { RemoveUnassignedItemsBannerDismissed } from "./migrations/67-remove-unassigned-items-banner-dismissed"; import { MoveLastSyncDate } from "./migrations/68-move-last-sync-date"; +import { MigrateIncorrectFolderKey } from "./migrations/69-migrate-incorrect-folder-key"; import { MoveBiometricAutoPromptToAccount } from "./migrations/7-move-biometric-auto-prompt-to-account"; +import { RemoveAcBannersDismissed } from "./migrations/70-remove-ac-banner-dismissed"; import { MoveStateVersionMigrator } from "./migrations/8-move-state-version"; import { MoveBrowserSettingsToGlobal } from "./migrations/9-move-browser-settings-to-global"; import { MinVersionMigrator } from "./migrations/min-version"; export const MIN_VERSION = 3; -export const CURRENT_VERSION = 68; +export const CURRENT_VERSION = 70; export type MinVersion = typeof MIN_VERSION; export function createMigrationBuilder() { @@ -142,7 +144,9 @@ export function createMigrationBuilder() { .with(ForwarderOptionsMigrator, 64, 65) .with(MoveFinalDesktopSettingsMigrator, 65, 66) .with(RemoveUnassignedItemsBannerDismissed, 66, 67) - .with(MoveLastSyncDate, 67, CURRENT_VERSION); + .with(MoveLastSyncDate, 67, 68) + .with(MigrateIncorrectFolderKey, 68, 69) + .with(RemoveAcBannersDismissed, 69, CURRENT_VERSION); } export async function currentVersion( diff --git a/libs/common/src/state-migrations/migration-helper.spec.ts b/libs/common/src/state-migrations/migration-helper.spec.ts index 49e6e7fe9cf..11126c6723a 100644 --- a/libs/common/src/state-migrations/migration-helper.spec.ts +++ b/libs/common/src/state-migrations/migration-helper.spec.ts @@ -1,6 +1,5 @@ import { MockProxy, mock } from "jest-mock-extended"; -// eslint-disable-next-line import/no-restricted-paths -- Needed to print log messages import { FakeStorageService } from "../../spec/fake-storage.service"; // eslint-disable-next-line import/no-restricted-paths -- Needed client type enum import { ClientType } from "../enums"; diff --git a/libs/common/src/state-migrations/migrations/18-move-autofill-settings-to-state-providers.ts b/libs/common/src/state-migrations/migrations/18-move-autofill-settings-to-state-providers.ts index 53ae94c30b2..84bcaeec608 100644 --- a/libs/common/src/state-migrations/migrations/18-move-autofill-settings-to-state-providers.ts +++ b/libs/common/src/state-migrations/migrations/18-move-autofill-settings-to-state-providers.ts @@ -3,6 +3,8 @@ import { StateDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-unused-vars const AutofillOverlayVisibility = { Off: 0, OnButtonClick: 1, diff --git a/libs/common/src/state-migrations/migrations/25-move-clear-clipboard-to-autofill-settings-state-provider.ts b/libs/common/src/state-migrations/migrations/25-move-clear-clipboard-to-autofill-settings-state-provider.ts index 04b4a50e0ed..9397191f54b 100644 --- a/libs/common/src/state-migrations/migrations/25-move-clear-clipboard-to-autofill-settings-state-provider.ts +++ b/libs/common/src/state-migrations/migrations/25-move-clear-clipboard-to-autofill-settings-state-provider.ts @@ -3,6 +3,8 @@ import { StateDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-unused-vars const ClearClipboardDelay = { Never: null as null, TenSeconds: 10, diff --git a/libs/common/src/state-migrations/migrations/34-move-domain-settings-to-state-providers.ts b/libs/common/src/state-migrations/migrations/34-move-domain-settings-to-state-providers.ts index b465a69131a..71e5c531d50 100644 --- a/libs/common/src/state-migrations/migrations/34-move-domain-settings-to-state-providers.ts +++ b/libs/common/src/state-migrations/migrations/34-move-domain-settings-to-state-providers.ts @@ -3,6 +3,8 @@ import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-unused-vars const UriMatchStrategy = { Domain: 0, Host: 1, diff --git a/libs/common/src/state-migrations/migrations/69-migrate-incorrect-folder-key.spec.ts b/libs/common/src/state-migrations/migrations/69-migrate-incorrect-folder-key.spec.ts new file mode 100644 index 00000000000..e5dec943f78 --- /dev/null +++ b/libs/common/src/state-migrations/migrations/69-migrate-incorrect-folder-key.spec.ts @@ -0,0 +1,98 @@ +import { runMigrator } from "../migration-helper.spec"; + +import { MigrateIncorrectFolderKey } from "./69-migrate-incorrect-folder-key"; + +function exampleJSON() { + return { + global_account_accounts: { + user1: null as any, + user2: null as any, + }, + user_user1_folder_folder: { + // Incorrect "folder" key + folderId1: { + id: "folderId1", + name: "folder-name-1", + revisionDate: "folder-revision-date-1", + }, + folderId2: { + id: "folderId2", + name: "folder-name-2", + revisionDate: "folder-revision-date-2", + }, + }, + user_user2_folder_folder: null as any, + }; +} + +describe("MigrateIncorrectFolderKey", () => { + const sut = new MigrateIncorrectFolderKey(68, 69); + it("migrates data", async () => { + const output = await runMigrator(sut, exampleJSON()); + + expect(output).toEqual({ + global_account_accounts: { + user1: null, + user2: null, + }, + user_user1_folder_folders: { + // Correct "folders" key + folderId1: { + id: "folderId1", + name: "folder-name-1", + revisionDate: "folder-revision-date-1", + }, + folderId2: { + id: "folderId2", + name: "folder-name-2", + revisionDate: "folder-revision-date-2", + }, + }, + }); + }); + + it("rolls back data", async () => { + const output = await runMigrator( + sut, + { + global_account_accounts: { + user1: null, + user2: null, + }, + user_user1_folder_folders: { + folderId1: { + id: "folderId1", + name: "folder-name-1", + revisionDate: "folder-revision-date-1", + }, + folderId2: { + id: "folderId2", + name: "folder-name-2", + revisionDate: "folder-revision-date-2", + }, + }, + }, + "rollback", + ); + + expect(output).toEqual({ + global_account_accounts: { + user1: null, + user2: null, + }, + user_user1_folder_folder: { + // Incorrect "folder" key + folderId1: { + id: "folderId1", + name: "folder-name-1", + revisionDate: "folder-revision-date-1", + }, + folderId2: { + id: "folderId2", + name: "folder-name-2", + revisionDate: "folder-revision-date-2", + }, + }, + }); + }); +}); diff --git a/libs/common/src/state-migrations/migrations/69-migrate-incorrect-folder-key.ts b/libs/common/src/state-migrations/migrations/69-migrate-incorrect-folder-key.ts new file mode 100644 index 00000000000..046c0cf0dfa --- /dev/null +++ b/libs/common/src/state-migrations/migrations/69-migrate-incorrect-folder-key.ts @@ -0,0 +1,45 @@ +import { + KeyDefinitionLike, + MigrationHelper, +} from "@bitwarden/common/state-migrations/migration-helper"; +import { Migrator } from "@bitwarden/common/state-migrations/migrator"; + +const BAD_FOLDER_KEY: KeyDefinitionLike = { + key: "folder", // We inadvertently changed the key from "folders" to "folder" + stateDefinition: { + name: "folder", + }, +}; + +const GOOD_FOLDER_KEY: KeyDefinitionLike = { + key: "folders", // We should keep the key as "folders" + stateDefinition: { + name: "folder", + }, +}; + +export class MigrateIncorrectFolderKey extends Migrator<68, 69> { + async migrate(helper: MigrationHelper): Promise { + async function migrateUser(userId: string) { + const value = await helper.getFromUser(userId, BAD_FOLDER_KEY); + if (value != null) { + await helper.setToUser(userId, GOOD_FOLDER_KEY, value); + } + await helper.removeFromUser(userId, BAD_FOLDER_KEY); + } + const users = await helper.getKnownUserIds(); + await Promise.all(users.map((userId) => migrateUser(userId))); + } + + async rollback(helper: MigrationHelper): Promise { + async function rollbackUser(userId: string) { + const value = await helper.getFromUser(userId, GOOD_FOLDER_KEY); + if (value != null) { + await helper.setToUser(userId, BAD_FOLDER_KEY, value); + } + await helper.removeFromUser(userId, GOOD_FOLDER_KEY); + } + const users = await helper.getKnownUserIds(); + await Promise.all(users.map((userId) => rollbackUser(userId))); + } +} diff --git a/libs/common/src/state-migrations/migrations/70-remove-ac-banner-dismissed.spec.ts b/libs/common/src/state-migrations/migrations/70-remove-ac-banner-dismissed.spec.ts new file mode 100644 index 00000000000..59f39d195e9 --- /dev/null +++ b/libs/common/src/state-migrations/migrations/70-remove-ac-banner-dismissed.spec.ts @@ -0,0 +1,50 @@ +import { runMigrator } from "../migration-helper.spec"; +import { IRREVERSIBLE } from "../migrator"; + +import { RemoveAcBannersDismissed } from "./70-remove-ac-banner-dismissed"; + +describe("RemoveAcBannersDismissed", () => { + const sut = new RemoveAcBannersDismissed(69, 70); + + describe("migrate", () => { + it("deletes ac banner from all users", async () => { + const output = await runMigrator(sut, { + global_account_accounts: { + user1: { + email: "user1@email.com", + name: "User 1", + emailVerified: true, + }, + user2: { + email: "user2@email.com", + name: "User 2", + emailVerified: true, + }, + }, + user_user1_showProviderClientVaultPrivacyBanner_acBannersDismissed: true, + user_user2_showProviderClientVaultPrivacyBanner_acBannersDismissed: true, + }); + + expect(output).toEqual({ + global_account_accounts: { + user1: { + email: "user1@email.com", + name: "User 1", + emailVerified: true, + }, + user2: { + email: "user2@email.com", + name: "User 2", + emailVerified: true, + }, + }, + }); + }); + }); + + describe("rollback", () => { + it("is irreversible", async () => { + await expect(runMigrator(sut, {}, "rollback")).rejects.toThrow(IRREVERSIBLE); + }); + }); +}); diff --git a/libs/common/src/state-migrations/migrations/70-remove-ac-banner-dismissed.ts b/libs/common/src/state-migrations/migrations/70-remove-ac-banner-dismissed.ts new file mode 100644 index 00000000000..087994b508f --- /dev/null +++ b/libs/common/src/state-migrations/migrations/70-remove-ac-banner-dismissed.ts @@ -0,0 +1,23 @@ +import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; +import { IRREVERSIBLE, Migrator } from "../migrator"; + +export const SHOW_BANNER_KEY: KeyDefinitionLike = { + key: "acBannersDismissed", + stateDefinition: { name: "showProviderClientVaultPrivacyBanner" }, +}; + +export class RemoveAcBannersDismissed extends Migrator<69, 70> { + async migrate(helper: MigrationHelper): Promise { + await Promise.all( + (await helper.getAccounts()).map(async ({ userId }) => { + if (helper.getFromUser(userId, SHOW_BANNER_KEY) != null) { + await helper.removeFromUser(userId, SHOW_BANNER_KEY); + } + }), + ); + } + + async rollback(helper: MigrationHelper): Promise { + throw IRREVERSIBLE; + } +} diff --git a/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.spec.ts b/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.spec.ts index 12257905d1c..66edc5a4838 100644 --- a/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.spec.ts +++ b/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.spec.ts @@ -1,13 +1,13 @@ import { mock } from "jest-mock-extended"; import { BehaviorSubject, Subject } from "rxjs"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import { CsprngArray } from "@bitwarden/common/types/csprng"; -import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; -import { OrgKey, UserKey } from "@bitwarden/common/types/key"; import { KeyService } from "@bitwarden/key-management"; +import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service"; +import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key"; +import { CsprngArray } from "../../types/csprng"; +import { OrganizationId, UserId } from "../../types/guid"; +import { OrgKey, UserKey } from "../../types/key"; import { OrganizationBound, UserBound } from "../dependencies"; import { KeyServiceLegacyEncryptorProvider } from "./key-service-legacy-encryptor-provider"; @@ -184,7 +184,7 @@ describe("KeyServiceLegacyEncryptorProvider", () => { singleUserId$.complete(); - expect(completed).toBeTrue(); + expect(completed).toBe(true); }); it("completes when `userKey$` emits a falsy value after emitting a truthy value", () => { @@ -199,7 +199,7 @@ describe("KeyServiceLegacyEncryptorProvider", () => { userKey$.next(null); - expect(completed).toBeTrue(); + expect(completed).toBe(true); }); it("completes once `dependencies.singleUserId$` emits and `userKey$` completes", () => { @@ -214,7 +214,7 @@ describe("KeyServiceLegacyEncryptorProvider", () => { userKey$.complete(); - expect(completed).toBeTrue(); + expect(completed).toBe(true); }); }); @@ -445,7 +445,7 @@ describe("KeyServiceLegacyEncryptorProvider", () => { singleOrganizationId$.complete(); - expect(completed).toBeTrue(); + expect(completed).toBe(true); }); it("completes when `orgKeys$` emits a falsy value after emitting a truthy value", () => { @@ -466,7 +466,7 @@ describe("KeyServiceLegacyEncryptorProvider", () => { orgKey$.next(OrgRecords); orgKey$.next(null); - expect(completed).toBeTrue(); + expect(completed).toBe(true); }); it("completes once `dependencies.singleOrganizationId$` emits and `userKey$` completes", () => { @@ -486,7 +486,7 @@ describe("KeyServiceLegacyEncryptorProvider", () => { orgKey$.complete(); - expect(completed).toBeTrue(); + expect(completed).toBe(true); }); }); }); diff --git a/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.ts b/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.ts index 3eee08eb6bd..c91181a004a 100644 --- a/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.ts +++ b/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.ts @@ -12,10 +12,10 @@ import { takeWhile, } from "rxjs"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; -import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { KeyService } from "@bitwarden/key-management"; +import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service"; +import { OrganizationId, UserId } from "../../types/guid"; import { OrganizationBound, SingleOrganizationDependency, diff --git a/libs/common/src/tools/cryptography/organization-encryptor.abstraction.ts b/libs/common/src/tools/cryptography/organization-encryptor.abstraction.ts index dda8bfe957a..9eec207e92d 100644 --- a/libs/common/src/tools/cryptography/organization-encryptor.abstraction.ts +++ b/libs/common/src/tools/cryptography/organization-encryptor.abstraction.ts @@ -2,9 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; -import { OrganizationId } from "@bitwarden/common/types/guid"; - import { EncString } from "../../platform/models/domain/enc-string"; +import { OrganizationId } from "../../types/guid"; /** An encryption strategy that protects a type's secrets with * organization-specific keys. This strategy is bound to a specific organization. diff --git a/libs/common/src/tools/cryptography/organization-key-encryptor.spec.ts b/libs/common/src/tools/cryptography/organization-key-encryptor.spec.ts index 62c8ea24ae6..3d93db81389 100644 --- a/libs/common/src/tools/cryptography/organization-key-encryptor.spec.ts +++ b/libs/common/src/tools/cryptography/organization-key-encryptor.spec.ts @@ -1,6 +1,6 @@ import { mock } from "jest-mock-extended"; -import { EncryptService } from "../../platform/abstractions/encrypt.service"; +import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service"; import { EncString } from "../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key"; import { CsprngArray } from "../../types/csprng"; diff --git a/libs/common/src/tools/cryptography/organization-key-encryptor.ts b/libs/common/src/tools/cryptography/organization-key-encryptor.ts index 3fdc0a1da32..31f3db91232 100644 --- a/libs/common/src/tools/cryptography/organization-key-encryptor.ts +++ b/libs/common/src/tools/cryptography/organization-key-encryptor.ts @@ -2,10 +2,9 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; -import { OrganizationId } from "@bitwarden/common/types/guid"; - -import { EncryptService } from "../../platform/abstractions/encrypt.service"; +import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service"; import { EncString } from "../../platform/models/domain/enc-string"; +import { OrganizationId } from "../../types/guid"; import { OrgKey } from "../../types/key"; import { DataPacker } from "../state/data-packer.abstraction"; diff --git a/libs/common/src/tools/cryptography/user-encryptor.abstraction.ts b/libs/common/src/tools/cryptography/user-encryptor.abstraction.ts index d63a5e908ef..6bb0e252af3 100644 --- a/libs/common/src/tools/cryptography/user-encryptor.abstraction.ts +++ b/libs/common/src/tools/cryptography/user-encryptor.abstraction.ts @@ -2,9 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; -import { UserId } from "@bitwarden/common/types/guid"; - import { EncString } from "../../platform/models/domain/enc-string"; +import { UserId } from "../../types/guid"; /** An encryption strategy that protects a type's secrets with * user-specific keys. This strategy is bound to a specific user. diff --git a/libs/common/src/tools/cryptography/user-key-encryptor.spec.ts b/libs/common/src/tools/cryptography/user-key-encryptor.spec.ts index 5b0ee5103cb..e52190500b0 100644 --- a/libs/common/src/tools/cryptography/user-key-encryptor.spec.ts +++ b/libs/common/src/tools/cryptography/user-key-encryptor.spec.ts @@ -1,6 +1,6 @@ import { mock } from "jest-mock-extended"; -import { EncryptService } from "../../platform/abstractions/encrypt.service"; +import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service"; import { EncString } from "../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key"; import { CsprngArray } from "../../types/csprng"; diff --git a/libs/common/src/tools/cryptography/user-key-encryptor.ts b/libs/common/src/tools/cryptography/user-key-encryptor.ts index 15a3a1b6d5c..4b7cd1516a0 100644 --- a/libs/common/src/tools/cryptography/user-key-encryptor.ts +++ b/libs/common/src/tools/cryptography/user-key-encryptor.ts @@ -2,10 +2,9 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; -import { UserId } from "@bitwarden/common/types/guid"; - -import { EncryptService } from "../../platform/abstractions/encrypt.service"; +import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service"; import { EncString } from "../../platform/models/domain/enc-string"; +import { UserId } from "../../types/guid"; import { UserKey } from "../../types/key"; import { DataPacker } from "../state/data-packer.abstraction"; diff --git a/libs/common/src/tools/dependencies.ts b/libs/common/src/tools/dependencies.ts index cdae45bc94a..5aeb79023c9 100644 --- a/libs/common/src/tools/dependencies.ts +++ b/libs/common/src/tools/dependencies.ts @@ -1,10 +1,6 @@ import { Observable } from "rxjs"; -import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; -import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; - -import { OrganizationEncryptor } from "./cryptography/organization-encryptor.abstraction"; -import { UserEncryptor } from "./cryptography/user-encryptor.abstraction"; +import { OrganizationId, UserId } from "../types/guid"; /** error emitted when the `SingleUserDependency` changes Ids */ export type UserChangedError = { @@ -22,22 +18,21 @@ export type OrganizationChangedError = { actualOrganizationId: OrganizationId; }; -/** A pattern for types that depend upon a dynamic policy stream and return - * an observable. +/** A pattern for types that depend upon the lifetime of a fixed dependency. + * The dependency's lifetime is tracked through the observable. The observable + * emits the dependency once it becomes available and completes when the + * dependency becomes unavailable. * - * Consumers of this dependency should emit when `policy$` - * emits, provided that the latest message materially - * changes the output of the consumer. If `policy$` emits - * an unrecoverable error, the consumer should continue using - * the last-emitted policy. If `policy$` completes, the consumer - * should continue using the last-emitted policy. + * Consumers of this dependency should emit a `SequenceError` if the dependency emits + * multiple times. When the dependency completes, the consumer should also + * complete. When the dependency errors, the consumer should also error. */ -export type PolicyDependency = { - /** A stream that emits policies when subscribed and - * when the policy changes. The stream should not - * emit null or undefined. +export type BoundDependency = { + /** A stream that emits a dependency once it becomes available + * and completes when the dependency becomes unavailable. The stream emits + * only once per subscription and never emits null or undefined. */ - policy$: Observable; + [K in `${Name}$`]: Observable; }; /** A pattern for types that depend upon a dynamic userid and return @@ -72,26 +67,6 @@ export type OrganizationBound = { [P in K]: T } & { organizationId: OrganizationId; }; -/** A pattern for types that depend upon a fixed-key encryptor and return - * an observable. - * - * Consumers of this dependency should emit a `OrganizationChangedError` if - * the bound OrganizationId changes or if the encryptor changes. If - * `singleOrganizationEncryptor$` completes, the consumer should complete - * once all events received prior to the completion event are - * finished processing. The consumer should, where possible, - * prioritize these events in order to complete as soon as possible. - * If `singleOrganizationEncryptor$` emits an unrecoverable error, the consumer - * should also emit the error. - */ -export type SingleOrganizationEncryptorDependency = { - /** A stream that emits an encryptor when subscribed and the org key - * is available, and completes when the org key is no longer available. - * The stream should not emit null or undefined. - */ - singleOrgEncryptor$: Observable>; -}; - /** A pattern for types that depend upon a fixed-value organizationId and return * an observable. * @@ -112,26 +87,6 @@ export type SingleOrganizationDependency = { singleOrganizationId$: Observable>; }; -/** A pattern for types that depend upon a fixed-key encryptor and return - * an observable. - * - * Consumers of this dependency should emit a `UserChangedError` if - * the bound UserId changes or if the encryptor changes. If - * `singleUserEncryptor$` completes, the consumer should complete - * once all events received prior to the completion event are - * finished processing. The consumer should, where possible, - * prioritize these events in order to complete as soon as possible. - * If `singleUserEncryptor$` emits an unrecoverable error, the consumer - * should also emit the error. - */ -export type SingleUserEncryptorDependency = { - /** A stream that emits an encryptor when subscribed and the user key - * is available, and completes when the user key is no longer available. - * The stream should not emit null or undefined. - */ - singleUserEncryptor$: Observable>; -}; - /** A pattern for types that depend upon a fixed-value userid and return * an observable. * @@ -152,7 +107,8 @@ export type SingleUserDependency = { }; /** A pattern for types that emit values exclusively when the dependency - * emits a message. + * emits a message. Set a type parameter when your method requires contextual + * information when the request is issued. * * Consumers of this dependency should emit when `on$` emits. If `on$` * completes, the consumer should also complete. If `on$` @@ -161,10 +117,10 @@ export type SingleUserDependency = { * @remarks This dependency is useful when you have a nondeterministic * or stateful algorithm that you would like to run when an event occurs. */ -export type OnDependency = { +export type OnDependency = { /** The stream that controls emissions */ - on$: Observable; + on$: Observable; }; /** A pattern for types that emit when a dependency is `true`. @@ -181,22 +137,6 @@ export type WhenDependency = { when$: Observable; }; -/** A pattern for types that allow their managed settings to - * be overridden. - * - * Consumers of this dependency should emit when `settings$` - * change. If `settings$` completes, the consumer should also - * complete. If `settings$` errors, the consumer should also - * emit the error. - */ -export type SettingsDependency = { - /** A stream that emits settings when settings become available - * and when they change. If the settings are not available, the - * stream should wait to emit until they become available. - */ - settings$: Observable; -}; - /** A pattern for types that accept an arbitrary dependency and * inject it into behavior-customizing functions. * diff --git a/libs/common/src/tools/extension/data.ts b/libs/common/src/tools/extension/data.ts new file mode 100644 index 00000000000..cab6272a068 --- /dev/null +++ b/libs/common/src/tools/extension/data.ts @@ -0,0 +1,26 @@ +/** well-known name for a feature extensible through an extension. */ +export const Site = Object.freeze({ + forwarder: "forwarder", +} as const); + +/** well-known name for a field surfaced from an extension site to a vendor. */ +export const Field = Object.freeze({ + token: "token", + baseUrl: "baseUrl", + domain: "domain", + prefix: "prefix", +} as const); + +/** Permission levels for metadata. */ +export const Permission = Object.freeze({ + /** unless a rule denies access, allow it. If a permission is `null` + * or `undefined` it should be treated as `Permission.default`. + */ + default: "default", + /** unless a rule allows access, deny it. */ + none: "none", + /** access is explicitly granted to use an extension. */ + allow: "allow", + /** access is explicitly prohibited for this extension. This rule overrides allow rules. */ + deny: "deny", +} as const); diff --git a/libs/common/src/tools/extension/extension-registry.abstraction.ts b/libs/common/src/tools/extension/extension-registry.abstraction.ts new file mode 100644 index 00000000000..7734c01ea50 --- /dev/null +++ b/libs/common/src/tools/extension/extension-registry.abstraction.ts @@ -0,0 +1,104 @@ +import { ExtensionSite } from "./extension-site"; +import { + ExtensionMetadata, + ExtensionSet, + ExtensionPermission, + SiteId, + SiteMetadata, + VendorId, + VendorMetadata, +} from "./type"; + +/** Tracks extension sites and the vendors that extend them. */ +export abstract class ExtensionRegistry { + /** Registers a site supporting extensibility. + * Each site may only be registered once. Calls after the first for + * the same SiteId have no effect. + * @param site identifies the site being extended + * @param meta configures the extension site + * @return self for method chaining. + * @remarks The registry initializes with a set of allowed sites and fields. + * `registerSite` drops a registration and trims its allowed fields to only + * those indicated in the allow list. + */ + abstract registerSite: (meta: SiteMetadata) => this; + + /** List all registered extension sites with their extension permission, if any. + * @returns a list of all extension sites. `permission` is defined when the site + * is associated with an extension permission. + */ + abstract sites: () => { site: SiteMetadata; permission?: ExtensionPermission }[]; + + /** Get a site's metadata + * @param site identifies a site registration + * @return the site's metadata or `undefined` if the site isn't registered. + */ + abstract site: (site: SiteId) => SiteMetadata | undefined; + + /** Registers a vendor providing an extension. + * Each vendor may only be registered once. Calls after the first for + * the same VendorId have no effect. + * @param site - identifies the site being extended + * @param meta - configures the extension site + * @return self for method chaining. + */ + abstract registerVendor: (meta: VendorMetadata) => this; + + /** List all registered vendors with their permissions, if any. + * @returns a list of all extension sites. `permission` is defined when the site + * is associated with an extension permission. + */ + abstract vendors: () => { vendor: VendorMetadata; permission?: ExtensionPermission }[]; + + /** Get a vendor's metadata + * @param site identifies a vendor registration + * @return the vendor's metadata or `undefined` if the vendor isn't registered. + */ + abstract vendor: (vendor: VendorId) => VendorMetadata | undefined; + + /** Registers an extension provided by a vendor to an extension site. + * The vendor and site MUST be registered before the extension. + * Each extension may only be registered once. Calls after the first for + * the same SiteId and VendorId have no effect. + * @param site - identifies the site being extended + * @param meta - configures the extension site + * @return self for method chaining. + */ + abstract registerExtension: (meta: ExtensionMetadata) => this; + + /** Get an extensions metadata + * @param site identifies the extension's site + * @param vendor identifies the extension's vendor + * @return the extension's metadata or `undefined` if the extension isn't registered. + */ + abstract extension: (site: SiteId, vendor: VendorId) => ExtensionMetadata | undefined; + + /** List all registered extensions and their permissions */ + abstract extensions: () => ReadonlyArray<{ + extension: ExtensionMetadata; + permissions: ExtensionPermission[]; + }>; + + /** Registers a permission. Only 1 permission can be registered for each extension set. + * Calls after the first *replace* the registered permission. + * @param set the collection of extensions affected by the permission + * @param permission the permission for the collection + * @return self for method chaining. + */ + abstract setPermission: (set: ExtensionSet, permission: ExtensionPermission) => this; + + /** Retrieves the current permission for the given extension set or `undefined` if + * a permission doesn't exist. + */ + abstract permission: (set: ExtensionSet) => ExtensionPermission | undefined; + + /** Returns all registered extension rules. */ + abstract permissions: () => { set: ExtensionSet; permission: ExtensionPermission }[]; + + /** Creates a point-in-time snapshot of the registry's contents with extension + * permissions applied for the provided SiteId. + * @param id identifies the extension site to create. + * @returns the extension site, or `undefined` if the site is not registered. + */ + abstract build: (id: SiteId) => ExtensionSite | undefined; +} diff --git a/libs/common/src/tools/extension/extension-site.ts b/libs/common/src/tools/extension/extension-site.ts new file mode 100644 index 00000000000..e8aba008493 --- /dev/null +++ b/libs/common/src/tools/extension/extension-site.ts @@ -0,0 +1,20 @@ +import { deepFreeze } from "../util"; + +import { ExtensionMetadata, SiteMetadata, VendorId } from "./type"; + +/** Describes the capabilities of an extension site. + * This type is immutable. + */ +export class ExtensionSite { + /** instantiate the extension site + * @param site describes the extension site + * @param vendors describes the available vendors + * @param extensions describes the available extensions + */ + constructor( + readonly site: Readonly, + readonly extensions: ReadonlyMap>, + ) { + deepFreeze(this); + } +} diff --git a/libs/common/src/tools/extension/extension.service.spec.ts b/libs/common/src/tools/extension/extension.service.spec.ts new file mode 100644 index 00000000000..dad5684b523 --- /dev/null +++ b/libs/common/src/tools/extension/extension.service.spec.ts @@ -0,0 +1,136 @@ +import { mock } from "jest-mock-extended"; +import { BehaviorSubject, firstValueFrom } from "rxjs"; + +import { FakeAccountService, FakeStateProvider, awaitAsync } from "../../../spec"; +import { Account } from "../../auth/abstractions/account.service"; +import { EXTENSION_DISK, UserKeyDefinition } from "../../platform/state"; +import { UserId } from "../../types/guid"; +import { LegacyEncryptorProvider } from "../cryptography/legacy-encryptor-provider"; +import { UserEncryptor } from "../cryptography/user-encryptor.abstraction"; +import { disabledSemanticLoggerProvider } from "../log"; +import { UserStateSubjectDependencyProvider } from "../state/user-state-subject-dependency-provider"; + +import { Site } from "./data"; +import { ExtensionRegistry } from "./extension-registry.abstraction"; +import { ExtensionSite } from "./extension-site"; +import { ExtensionService } from "./extension.service"; +import { ExtensionMetadata, ExtensionProfileMetadata, ExtensionStorageKey } from "./type"; +import { Vendor } from "./vendor/data"; +import { SimpleLogin } from "./vendor/simplelogin"; + +const SomeUser = "some user" as UserId; +const SomeAccount = { + id: SomeUser, + email: "someone@example.com", + emailVerified: true, + name: "Someone", +}; +const SomeAccount$ = new BehaviorSubject(SomeAccount); + +type TestType = { foo: string }; + +const SomeEncryptor: UserEncryptor = { + userId: SomeUser, + + encrypt(secret) { + const tmp: any = secret; + return Promise.resolve({ foo: `encrypt(${tmp.foo})` } as any); + }, + + decrypt(secret) { + const tmp: any = JSON.parse(secret.encryptedString!); + return Promise.resolve({ foo: `decrypt(${tmp.foo})` } as any); + }, +}; + +const SomeAccountService = new FakeAccountService({ + [SomeUser]: SomeAccount, +}); + +const SomeStateProvider = new FakeStateProvider(SomeAccountService); + +const SomeProvider = { + encryptor: { + userEncryptor$: () => { + return new BehaviorSubject({ encryptor: SomeEncryptor, userId: SomeUser }).asObservable(); + }, + organizationEncryptor$() { + throw new Error("`organizationEncryptor$` should never be invoked."); + }, + } as LegacyEncryptorProvider, + state: SomeStateProvider, + log: disabledSemanticLoggerProvider, +} as UserStateSubjectDependencyProvider; + +const SomeExtension: ExtensionMetadata = { + site: { id: "forwarder", availableFields: [] }, + product: { vendor: SimpleLogin }, + host: { + selfHost: "maybe", + baseUrl: "https://www.example.com/", + authentication: true, + }, + requestedFields: [], +}; + +const SomeRegistry = mock(); + +const SomeProfileMetadata = { + type: "extension", + site: Site.forwarder, + storage: { + key: "someProfile", + options: { + deserializer: (value) => value as TestType, + clearOn: [], + }, + } as ExtensionStorageKey, +} satisfies ExtensionProfileMetadata; + +describe("ExtensionService", () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + describe("settings", () => { + it("writes to the user's state", async () => { + const extension = new ExtensionService(SomeRegistry, SomeProvider); + SomeRegistry.extension.mockReturnValue(SomeExtension); + const subject = extension.settings(SomeProfileMetadata, Vendor.simplelogin, { + account$: SomeAccount$, + }); + + subject.next({ foo: "next value" }); + await awaitAsync(); + + // if the write succeeded, then the storage location should contain an object; + // the precise value isn't tested to avoid coupling the test to the storage format + const expectedKey = new UserKeyDefinition( + EXTENSION_DISK, + "forwarder.simplelogin.someProfile", + SomeProfileMetadata.storage.options, + ); + const result = await firstValueFrom(SomeStateProvider.getUserState$(expectedKey, SomeUser)); + expect(result).toBeTruthy(); + }); + + it("panics when the extension metadata isn't available", async () => { + const extension = new ExtensionService(SomeRegistry, SomeProvider); + expect(() => + extension.settings(SomeProfileMetadata, Vendor.bitwarden, { account$: SomeAccount$ }), + ).toThrow("extension not defined"); + }); + }); + + describe("site", () => { + it("returns an extension site", () => { + const expected = new ExtensionSite(SomeExtension.site, new Map()); + SomeRegistry.build.mockReturnValueOnce(expected); + const extension = new ExtensionService(SomeRegistry, SomeProvider); + + const site = extension.site(Site.forwarder); + + expect(site).toEqual(expected); + }); + }); +}); diff --git a/libs/common/src/tools/extension/extension.service.ts b/libs/common/src/tools/extension/extension.service.ts new file mode 100644 index 00000000000..c4e6887b821 --- /dev/null +++ b/libs/common/src/tools/extension/extension.service.ts @@ -0,0 +1,63 @@ +import { shareReplay } from "rxjs"; + +import { Account } from "../../auth/abstractions/account.service"; +import { BoundDependency } from "../dependencies"; +import { SemanticLogger } from "../log"; +import { UserStateSubject } from "../state/user-state-subject"; +import { UserStateSubjectDependencyProvider } from "../state/user-state-subject-dependency-provider"; + +import { ExtensionRegistry } from "./extension-registry.abstraction"; +import { ExtensionProfileMetadata, SiteId, VendorId } from "./type"; +import { toObjectKey } from "./util"; + +/** Provides configuration and storage support for Bitwarden client extensions. + * These extensions integrate 3rd party services into Bitwarden. + */ +export class ExtensionService { + /** Instantiate the extension service. + * @param registry provides runtime status for extension sites + * @param providers provide persistent data + */ + constructor( + private registry: ExtensionRegistry, + private readonly providers: UserStateSubjectDependencyProvider, + ) { + this.log = providers.log({ + type: "ExtensionService", + }); + } + + private log: SemanticLogger; + + /** Get a subject bound to a user's extension settings + * @param profile the site's extension profile + * @param vendor the vendor integrated at the extension site + * @param dependencies.account$ the account to which the settings are bound + * @returns a subject bound to the requested user's generator settings + */ + settings( + profile: ExtensionProfileMetadata, + vendor: VendorId, + dependencies: BoundDependency<"account", Account>, + ): UserStateSubject { + const metadata = this.registry.extension(profile.site, vendor); + if (!metadata) { + this.log.panic({ site: profile.site as string, vendor }, "extension not defined"); + } + + const key = toObjectKey(profile, metadata); + const account$ = dependencies.account$.pipe(shareReplay({ bufferSize: 1, refCount: true })); + // FIXME: load and apply constraints + const subject = new UserStateSubject(key, this.providers, { account$ }); + + return subject; + } + + /** Look up extension metadata for a site + * @param site defines the site to retrieve. + * @returns the extensions available at the site. + */ + site(site: SiteId) { + return this.registry.build(site); + } +} diff --git a/libs/common/src/tools/extension/factory.ts b/libs/common/src/tools/extension/factory.ts new file mode 100644 index 00000000000..10ebc77804a --- /dev/null +++ b/libs/common/src/tools/extension/factory.ts @@ -0,0 +1,24 @@ +import { DefaultFields, DefaultSites, Extension } from "./metadata"; +import { RuntimeExtensionRegistry } from "./runtime-extension-registry"; +import { VendorExtensions, Vendors } from "./vendor"; + +// FIXME: find a better way to build the registry than a hard-coded factory function + +/** Constructs the extension registry */ +export function buildExtensionRegistry() { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + + for (const site of Reflect.ownKeys(Extension) as string[]) { + registry.registerSite(Extension[site]); + } + + for (const vendor of Vendors) { + registry.registerVendor(vendor); + } + + for (const extension of VendorExtensions) { + registry.registerExtension(extension); + } + + return registry; +} diff --git a/libs/common/src/tools/extension/index.ts b/libs/common/src/tools/extension/index.ts new file mode 100644 index 00000000000..e786dde4f59 --- /dev/null +++ b/libs/common/src/tools/extension/index.ts @@ -0,0 +1,12 @@ +export { Site, Field, Permission } from "./data"; +export { + SiteId, + FieldId, + VendorId, + ExtensionId, + ExtensionPermission, + SiteMetadata, + ExtensionMetadata, + VendorMetadata, +} from "./type"; +export { ExtensionSite } from "./extension-site"; diff --git a/libs/common/src/tools/extension/metadata.ts b/libs/common/src/tools/extension/metadata.ts new file mode 100644 index 00000000000..895b1d1b31f --- /dev/null +++ b/libs/common/src/tools/extension/metadata.ts @@ -0,0 +1,17 @@ +import { Field, Site, Permission } from "./data"; +import { FieldId, SiteId, SiteMetadata } from "./type"; + +export const DefaultSites: SiteId[] = Object.freeze(Object.keys(Site) as any); + +export const DefaultFields: FieldId[] = Object.freeze(Object.keys(Field) as any); + +export const Extension: Record = { + [Site.forwarder]: { + id: Site.forwarder, + availableFields: [Field.baseUrl, Field.domain, Field.prefix, Field.token], + }, +}; + +export const AllowedPermissions: ReadonlyArray = Object.freeze( + Object.values(Permission), +); diff --git a/libs/common/src/tools/extension/runtime-extension-registry.spec.ts b/libs/common/src/tools/extension/runtime-extension-registry.spec.ts new file mode 100644 index 00000000000..6aa7382db57 --- /dev/null +++ b/libs/common/src/tools/extension/runtime-extension-registry.spec.ts @@ -0,0 +1,923 @@ +import { deepFreeze } from "../util"; + +import { Field, Site, Permission } from "./data"; +import { ExtensionSite } from "./extension-site"; +import { DefaultFields, DefaultSites } from "./metadata"; +import { RuntimeExtensionRegistry } from "./runtime-extension-registry"; +import { ExtensionMetadata, SiteId, SiteMetadata, VendorMetadata } from "./type"; +import { Bitwarden } from "./vendor/bitwarden"; + +// arbitrary test entities +const SomeSiteId: SiteId = Site.forwarder; + +const SomeSite: SiteMetadata = Object.freeze({ + id: SomeSiteId, + availableFields: [], +}); + +const SomeVendor = Bitwarden; +const SomeVendorId = SomeVendor.id; +const SomeExtension: ExtensionMetadata = deepFreeze({ + site: SomeSite, + product: { vendor: SomeVendor, name: "Some Product" }, + host: { authorization: "bearer", selfHost: "maybe", baseUrl: "https://vault.bitwarden.com" }, + requestedFields: [], +}); + +const JustTrustUs: VendorMetadata = Object.freeze({ + id: "justrustus" as any, + name: "JustTrust.Us", +}); +const JustTrustUsExtension: ExtensionMetadata = deepFreeze({ + site: SomeSite, + product: { vendor: JustTrustUs }, + host: { authorization: "bearer", selfHost: "maybe", baseUrl: "https://justrust.us" }, + requestedFields: [], +}); + +// In the following tests, not-null assertions (`!`) indicate that +// the returned object should never be null or undefined given +// the conditions defined within the test case +describe("RuntimeExtensionRegistry", () => { + describe("registerSite", () => { + it("registers an extension site", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + + const result = registry.registerSite(SomeSite).site(SomeSiteId); + + expect(result).toEqual(SomeSite); + }); + + it("interns the site", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + + const result = registry.registerSite(SomeSite).site(SomeSiteId); + + expect(result).not.toBe(SomeSite); + }); + + it("registers an extension site with fields", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + const site: SiteMetadata = { + ...SomeSite, + availableFields: [Field.baseUrl], + }; + + const result = registry.registerSite(site).site(SomeSiteId); + + expect(result).toEqual(site); + }); + + it("ignores unavailable sites", () => { + const registry = new RuntimeExtensionRegistry([], []); + const ignored: SiteMetadata = { + id: "an-unavailable-site" as any, + availableFields: [], + }; + + const result = registry.registerSite(ignored).sites(); + + expect(result).toEqual([]); + }); + + it("ignores duplicate registrations", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + const ignored: SiteMetadata = { + ...SomeSite, + availableFields: [Field.token], + }; + + const result = registry.registerSite(SomeSite).registerSite(ignored).site(SomeSiteId); + + expect(result).toEqual(SomeSite); + }); + + it("ignores unknown available fields", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + const ignored: SiteMetadata = { + ...SomeSite, + availableFields: [SomeSite.availableFields, "ignored" as any], + }; + + const { availableFields } = registry.registerSite(ignored).site(SomeSiteId)!; + + expect(availableFields).toEqual(SomeSite.availableFields); + }); + + it("freezes the site definition", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + const site = registry.registerSite(SomeSite).site(SomeSiteId)!; + + // reassigning `availableFields` throws b/c the object is frozen + expect(() => (site.availableFields = [Field.domain])).toThrow(); + }); + }); + + describe("site", () => { + it("returns `undefined` for an unknown site", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + + const result = registry.site(SomeSiteId); + + expect(result).toBeUndefined(); + }); + + it("returns the same result when called repeatedly", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + registry.registerSite(SomeSite); + + const first = registry.site(SomeSiteId); + const second = registry.site(SomeSiteId); + + expect(first).toBe(second); + }); + }); + + describe("sites", () => { + it("lists registered sites", () => { + const registry = new RuntimeExtensionRegistry([SomeSiteId, "bar"] as any[], DefaultFields); + const barSite: SiteMetadata = { + id: "bar" as any, + availableFields: [], + }; + + const result = registry.registerSite(SomeSite).registerSite(barSite).sites(); + + expect(result.some(({ site }) => site.id === SomeSiteId)).toBe(true); + expect(result.some(({ site }) => site.id === barSite.id)).toBe(true); + }); + + it("includes permissions for a site", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + + const result = registry + .registerSite(SomeSite) + .setPermission({ site: SomeSite.id }, Permission.allow) + .sites(); + + expect(result).toEqual([{ site: SomeSite, permission: Permission.allow }]); + }); + + it("ignores duplicate registrations", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + const ignored: SiteMetadata = { + ...SomeSite, + availableFields: [Field.token], + }; + + const result = registry.registerSite(SomeSite).registerSite(ignored).sites(); + + expect(result).toEqual([{ site: SomeSite }]); + }); + + it("ignores permissions for other sites", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + + const result = registry + .registerSite(SomeSite) + .setPermission({ site: SomeSite.id }, Permission.allow) + .setPermission({ site: "bar" as any }, Permission.deny) + .sites(); + + expect(result).toEqual([{ site: SomeSite, permission: Permission.allow }]); + }); + }); + + describe("registerVendor", () => { + it("registers a vendor", () => { + const registry = new RuntimeExtensionRegistry([], []); + const result = registry.registerVendor(SomeVendor).vendors(); + + expect(result).toEqual([{ vendor: SomeVendor }]); + }); + + it("freezes the vendor definition", () => { + const registry = new RuntimeExtensionRegistry([], []); + // copy `SomeVendor` because it is already frozen + const original: VendorMetadata = { ...SomeVendor }; + + const [{ vendor }] = registry.registerVendor(original).vendors(); + + // reassigning `name` throws b/c the object is frozen + expect(() => (vendor.name = "Bytewarden")).toThrow(); + }); + }); + + describe("vendor", () => { + it("returns `undefined` for an unknown site", () => { + const registry = new RuntimeExtensionRegistry([], []); + + const result = registry.vendor(SomeVendorId); + + expect(result).toBeUndefined(); + }); + + it("returns the same result when called repeatedly", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + registry.registerVendor(SomeVendor); + + const first = registry.vendor(SomeVendorId); + const second = registry.vendor(SomeVendorId); + + expect(first).toBe(second); + }); + }); + + describe("vendors", () => { + it("lists registered vendors", () => { + const registry = new RuntimeExtensionRegistry([], []); + registry.registerVendor(SomeVendor).registerVendor(JustTrustUs); + + const result = registry.vendors(); + + expect(result.some(({ vendor }) => vendor.id === SomeVendorId)).toBe(true); + expect(result.some(({ vendor }) => vendor.id === JustTrustUs.id)).toBe(true); + }); + + it("includes permissions for a vendor", () => { + const registry = new RuntimeExtensionRegistry([], []); + + const result = registry + .registerVendor(SomeVendor) + .setPermission({ vendor: SomeVendorId }, Permission.allow) + .vendors(); + + expect(result).toEqual([{ vendor: SomeVendor, permission: Permission.allow }]); + }); + + it("ignores duplicate registrations", () => { + const registry = new RuntimeExtensionRegistry([], []); + const vendor: VendorMetadata = SomeVendor; + const ignored: VendorMetadata = { + ...SomeVendor, + name: "Duplicate", + }; + + const result = registry.registerVendor(vendor).registerVendor(ignored).vendors(); + + expect(result).toEqual([{ vendor }]); + }); + + it("ignores permissions for other sites", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + registry.registerVendor(SomeVendor).setPermission({ vendor: SomeVendorId }, Permission.allow); + + const result = registry.setPermission({ vendor: JustTrustUs.id }, Permission.deny).vendors(); + + expect(result).toEqual([{ vendor: SomeVendor, permission: Permission.allow }]); + }); + }); + + describe("setPermission", () => { + it("sets the all permission", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { all: true } as const; + + const permission = registry.setPermission(target, Permission.allow).permission(target); + + expect(permission).toEqual(Permission.allow); + }); + + it("sets a vendor permission", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { vendor: SomeVendorId }; + + const permission = registry.setPermission(target, Permission.allow).permission(target); + + expect(permission).toEqual(Permission.allow); + }); + + it("sets a site permission", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + const target = { site: SomeSiteId }; + + const permission = registry.setPermission(target, Permission.allow).permission(target); + + expect(permission).toEqual(Permission.allow); + }); + + it("ignores a site permission unless it is in the allowed sites list", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { site: SomeSiteId }; + + const permission = registry.setPermission(target, Permission.allow).permission(target); + + expect(permission).toBeUndefined(); + }); + + it("throws when a permission is invalid", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + + expect(() => registry.setPermission({ all: true }, "invalid" as any)).toThrow(); + }); + + it("throws when the extension set is the wrong type", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { invalid: "invalid" } as any; + + expect(() => registry.setPermission(target, Permission.allow)).toThrow(); + }); + }); + + describe("permission", () => { + it("gets the default all permission", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { all: true } as const; + + const permission = registry.permission(target); + + expect(permission).toEqual(Permission.default); + }); + + it("gets an all permission", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { all: true } as const; + registry.setPermission(target, Permission.none); + + const permission = registry.permission(target); + + expect(permission).toEqual(Permission.none); + }); + + it("gets a vendor permission", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { vendor: SomeVendorId }; + registry.setPermission(target, Permission.allow); + + const permission = registry.permission(target); + + expect(permission).toEqual(Permission.allow); + }); + + it("gets a site permission", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + const target = { site: SomeSiteId }; + registry.setPermission(target, Permission.allow); + + const permission = registry.permission(target); + + expect(permission).toEqual(Permission.allow); + }); + + it("gets a vendor permission", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { vendor: SomeVendorId }; + registry.setPermission(target, Permission.allow); + + const permission = registry.permission(target); + + expect(permission).toEqual(Permission.allow); + }); + + it("returns undefined when the extension set is the wrong type", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { invalid: "invalid" } as any; + + const permission = registry.permission(target); + + expect(permission).toBeUndefined(); + }); + }); + + describe("permissions", () => { + it("returns a default all permission by default", () => { + const registry = new RuntimeExtensionRegistry([], []); + + const permission = registry.permissions(); + + expect(permission).toEqual([{ set: { all: true }, permission: Permission.default }]); + }); + + it("returns the all permission", () => { + const registry = new RuntimeExtensionRegistry([], []); + registry.setPermission({ all: true }, Permission.none); + + const permission = registry.permissions(); + + expect(permission).toEqual([{ set: { all: true }, permission: Permission.none }]); + }); + + it("includes site permissions", () => { + const registry = new RuntimeExtensionRegistry([SomeSiteId, "bar"] as any[], DefaultFields); + registry.registerSite(SomeSite).setPermission({ site: SomeSiteId }, Permission.allow); + registry + .registerSite({ + id: "bar" as any, + availableFields: [], + }) + .setPermission({ site: "bar" as any }, Permission.deny); + + const result = registry.permissions(); + + expect( + result.some((p: any) => p.set.site === SomeSiteId && p.permission === Permission.allow), + ).toBe(true); + expect( + result.some((p: any) => p.set.site === "bar" && p.permission === Permission.deny), + ).toBe(true); + }); + + it("includes vendor permissions", () => { + const registry = new RuntimeExtensionRegistry([], DefaultFields); + registry.registerVendor(SomeVendor).setPermission({ vendor: SomeVendorId }, Permission.allow); + registry + .registerVendor(JustTrustUs) + .setPermission({ vendor: JustTrustUs.id }, Permission.deny); + + const result = registry.permissions(); + + expect( + result.some((p: any) => p.set.vendor === SomeVendorId && p.permission === Permission.allow), + ).toBe(true); + expect( + result.some( + (p: any) => p.set.vendor === JustTrustUs.id && p.permission === Permission.deny, + ), + ).toBe(true); + }); + }); + + describe("registerExtension", () => { + it("registers an extension", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerSite(SomeSite).registerVendor(SomeVendor); + + const result = registry.registerExtension(SomeExtension).extension(SomeSiteId, SomeVendorId); + + expect(result).toEqual(SomeExtension); + }); + + it("ignores extensions with nonregistered sites", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerVendor(SomeVendor); + + // precondition: the site is not registered + expect(registry.site(SomeSiteId)).toBeUndefined(); + + const result = registry.registerExtension(SomeExtension).extension(SomeSiteId, SomeVendorId); + + expect(result).toBeUndefined(); + }); + + it("ignores extensions with nonregistered vendors", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerSite(SomeSite); + + // precondition: the vendor is not registered + expect(registry.vendor(SomeVendorId)).toBeUndefined(); + + const result = registry.registerExtension(SomeExtension).extension(SomeSiteId, SomeVendorId); + + expect(result).toBeUndefined(); + }); + + it("ignores repeated extensions with nonregistered vendors", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerSite(SomeSite).registerVendor(SomeVendor).registerExtension(SomeExtension); + + // precondition: the vendor is already registered + expect(registry.extension(SomeSiteId, SomeVendorId)).toBeDefined(); + + const result = registry + .registerExtension({ + ...SomeExtension, + requestedFields: [Field.domain], + }) + .extension(SomeSiteId, SomeVendorId); + + expect(result).toEqual(SomeExtension); + }); + + it("interns site metadata", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerSite(SomeSite).registerVendor(SomeVendor); + + const internedSite = registry.site(SomeSiteId); + const result = registry.registerExtension(SomeExtension).extension(SomeSiteId, SomeVendorId)!; + + expect(result.site).toBe(internedSite); + }); + + it("interns vendor metadata", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerSite(SomeSite).registerVendor(SomeVendor); + + const internedVendor = registry.vendor(SomeVendorId); + const result = registry.registerExtension(SomeExtension).extension(SomeSiteId, SomeVendorId)!; + + expect(result.product.vendor).toBe(internedVendor); + }); + + it("freezes the extension metadata", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerSite(SomeSite).registerVendor(SomeVendor).registerExtension(SomeExtension); + const extension = registry.extension(SomeSiteId, SomeVendorId)!; + + // field assignments & mutation functions throw b/c the object is frozen + expect(() => ((extension.site as any) = SomeSite)).toThrow(); + expect(() => ((extension.product.vendor as any) = SomeVendor)).toThrow(); + expect(() => ((extension.product.name as any) = "SomeVendor")).toThrow(); + expect(() => ((extension.host as any) = {})).toThrow(); + expect(() => ((extension.host.selfHost as any) = {})).toThrow(); + expect(() => ((extension.host as any).authorization = "basic")).toThrow(); + expect(() => ((extension.host as any).baseUrl = "https://www.example.com")).toThrow(); + expect(() => ((extension.requestedFields as any) = [Field.baseUrl])).toThrow(); + expect(() => (extension.requestedFields as any).push(Field.baseUrl)).toThrow(); + }); + }); + + describe("extension", () => { + describe("extension", () => { + it("returns `undefined` for an unknown extension", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + + const result = registry.extension(SomeSiteId, SomeVendorId); + + expect(result).toBeUndefined(); + }); + + it("interns the extension", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + registry.registerSite(SomeSite).registerVendor(SomeVendor).registerExtension(SomeExtension); + + const first = registry.extension(SomeSiteId, SomeVendorId); + const second = registry.extension(SomeSiteId, SomeVendorId); + + expect(first).toBe(second); + }); + }); + + describe("extensions", () => { + it("lists registered extensions", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + registry.registerSite(SomeSite); + registry.registerVendor(SomeVendor).registerExtension(SomeExtension); + registry.registerVendor(JustTrustUs).registerExtension(JustTrustUsExtension); + + const result = registry.extensions(); + + expect( + result.some( + ({ extension }) => + extension.site.id === SomeSiteId && extension.product.vendor.id === SomeVendorId, + ), + ).toBe(true); + expect( + result.some( + ({ extension }) => + extension.site.id === SomeSiteId && extension.product.vendor.id === JustTrustUs.id, + ), + ).toBe(true); + }); + + it("includes permissions for extensions", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension) + .setPermission({ vendor: SomeVendorId }, Permission.allow); + + const result = registry.extensions(); + + expect( + result.some( + ({ extension, permissions }) => + extension.site.id === SomeSiteId && + extension.product.vendor.id === SomeVendorId && + permissions.includes(Permission.allow), + ), + ).toBe(true); + }); + }); + + describe("build", () => { + it("builds an empty extension site when no extensions are registered", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerSite(SomeSite).registerVendor(SomeVendor); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }); + + it("builds an extension site with all registered extensions", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerSite(SomeSite).registerVendor(SomeVendor).registerExtension(SomeExtension); + const expected = registry.extension(SomeSiteId, SomeVendorId); + + const result = registry.build(SomeSiteId)!; + + expect(result).toBeInstanceOf(ExtensionSite); + expect(result.extensions.get(SomeVendorId)).toBe(expected); + }); + + it("returns `undefined` for an unknown site", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + + const result = registry.build(SomeSiteId); + + expect(result).toBeUndefined(); + }); + + describe("when the all permission is `default`", () => { + const allPermission = Permission.default; + + it("builds an extension site with all registered extensions", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension) + .setPermission({ all: true }, Permission.default); + const expected = registry.extension(SomeSiteId, SomeVendorId); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.get(SomeVendorId)).toBe(expected); + }); + + it.each([[Permission.default], [Permission.allow]])( + "includes sites with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ site: SomeSiteId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.get(SomeVendorId)).toEqual(SomeExtension); + }, + ); + + it.each([[Permission.none], [Permission.deny]])( + "ignores sites with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ site: SomeSiteId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }, + ); + + it.each([[Permission.default], [Permission.allow]])( + "includes vendors with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ vendor: SomeVendorId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.get(SomeVendorId)).toEqual(SomeExtension); + }, + ); + + it.each([[Permission.none], [Permission.deny]])( + "ignores vendors with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ vendor: SomeVendorId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }, + ); + }); + + describe("when the all permission is `none`", () => { + const allPermission = Permission.none; + + it("builds an empty extension site", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension) + .setPermission({ all: true }, Permission.none); + + const result = registry.build(SomeSiteId)!; + + expect(result).toBeInstanceOf(ExtensionSite); + expect(result.extensions.size).toBe(0); + }); + + it.each([[Permission.allow]])("includes sites with `%p` permission", (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ site: SomeSiteId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.get(SomeVendorId)).toEqual(SomeExtension); + }); + + it.each([[Permission.default], [Permission.none], [Permission.deny]])( + "ignores sites with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ site: SomeSiteId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }, + ); + + it.each([[Permission.allow]])("includes vendors with `%p` permission", (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ vendor: SomeVendorId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.get(SomeVendorId)).toEqual(SomeExtension); + }); + + it.each([[Permission.default], [Permission.none], [Permission.deny]])( + "ignores vendors with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ vendor: SomeVendorId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }, + ); + }); + + describe("when the all permission is `allow`", () => { + const allPermission = Permission.allow; + + it("builds an extension site with all registered extensions", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension) + .setPermission({ all: true }, Permission.default); + const expected = registry.extension(SomeSiteId, SomeVendorId); + + const result = registry.build(SomeSiteId)!; + + expect(result).toBeInstanceOf(ExtensionSite); + expect(result.extensions.get(SomeVendorId)).toBe(expected); + }); + + it.each([[Permission.default], [Permission.none], [Permission.allow]])( + "includes sites with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ site: SomeSiteId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.get(SomeVendorId)).toEqual(SomeExtension); + }, + ); + + it.each([[Permission.deny]])("ignores sites with `%p` permission", (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ site: SomeSiteId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }); + + it.each([[Permission.default], [Permission.none], [Permission.allow]])( + "includes vendors with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ vendor: SomeVendorId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.get(SomeVendorId)).toEqual(SomeExtension); + }, + ); + + it.each([[Permission.deny]])("ignores vendors with `%p` permission", (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ vendor: SomeVendorId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }); + }); + + describe("when the all permission is `deny`", () => { + const allPermission = Permission.deny; + + it("builds an empty extension site", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension) + .setPermission({ all: true }, Permission.deny); + + const result = registry.build(SomeSiteId)!; + + expect(result).toBeInstanceOf(ExtensionSite); + expect(result.extensions.size).toBe(0); + }); + + it.each([[Permission.default], [Permission.none], [Permission.allow], [Permission.deny]])( + "ignores sites with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ site: SomeSiteId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }, + ); + + it.each([[Permission.default], [Permission.none], [Permission.allow], [Permission.deny]])( + "ignores vendors with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ vendor: SomeVendorId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }, + ); + }); + }); + }); +}); diff --git a/libs/common/src/tools/extension/runtime-extension-registry.ts b/libs/common/src/tools/extension/runtime-extension-registry.ts new file mode 100644 index 00000000000..1c630dcc915 --- /dev/null +++ b/libs/common/src/tools/extension/runtime-extension-registry.ts @@ -0,0 +1,286 @@ +import { deepFreeze } from "../util"; + +import { ExtensionRegistry } from "./extension-registry.abstraction"; +import { ExtensionSite } from "./extension-site"; +import { AllowedPermissions } from "./metadata"; +import { + ExtensionMetadata, + ExtensionPermission, + ExtensionSet, + FieldId, + ProductMetadata, + SiteMetadata, + SiteId, + VendorId, + VendorMetadata, +} from "./type"; + +/** Tracks extension sites and the vendors that extend them in application memory. */ +export class RuntimeExtensionRegistry implements ExtensionRegistry { + /** Instantiates the extension registry + * @param allowedSites sites that are valid for use by any extension; + * this is most useful to disable an extension site that is only + * available on a specific client. + * @param allowedFields fields that are valid for use by any extension; + * this is most useful to prohibit access to a field via policy. + */ + constructor( + private readonly allowedSites: SiteId[], + private readonly allowedFields: FieldId[], + ) { + Object.freeze(this.allowedFields); + Object.freeze(this.allowedSites); + } + + private allPermission: ExtensionPermission = "default"; + + private siteRegistrations = new Map(); + private sitePermissions = new Map(); + + private vendorRegistrations = new Map(); + private vendorPermissions = new Map(); + + private extensionRegistrations = new Array(); + private extensionsBySiteByVendor = new Map>(); + + registerSite(site: SiteMetadata): this { + if (!this.allowedSites.includes(site.id)) { + return this; + } + + // verify requested fields are on the list of valid fields to expose to + // an extension + const availableFields = site.availableFields.filter((field) => + this.allowedFields.includes(field), + ); + const validated: SiteMetadata = deepFreeze({ id: site.id, availableFields }); + + if (!this.siteRegistrations.has(site.id)) { + this.siteRegistrations.set(site.id, validated); + } + + return this; + } + + site(site: SiteId): SiteMetadata | undefined { + const result = this.siteRegistrations.get(site); + return result; + } + + sites() { + const sites: { site: SiteMetadata; permission?: ExtensionPermission }[] = []; + + for (const [k, site] of this.siteRegistrations.entries()) { + const s: (typeof sites)[number] = { site }; + const permission = this.sitePermissions.get(k); + if (permission) { + s.permission = permission; + } + + sites.push(s); + } + + return sites; + } + + registerVendor(vendor: VendorMetadata): this { + if (!this.vendorRegistrations.has(vendor.id)) { + const frozen = deepFreeze(vendor); + this.vendorRegistrations.set(vendor.id, frozen); + } + + return this; + } + + vendor(vendor: VendorId): VendorMetadata | undefined { + const result = this.vendorRegistrations.get(vendor); + return result; + } + + vendors() { + const vendors: { vendor: VendorMetadata; permission?: ExtensionPermission }[] = []; + + for (const [k, vendor] of this.vendorRegistrations.entries()) { + const s: (typeof vendors)[number] = { vendor }; + const permission = this.vendorPermissions.get(k); + if (permission) { + s.permission = permission; + } + + vendors.push(s); + } + + return vendors; + } + + setPermission(set: ExtensionSet, permission: ExtensionPermission): this { + if (!AllowedPermissions.includes(permission)) { + throw new Error(`invalid extension permission: ${permission}`); + } + + if ("all" in set && set.all) { + this.allPermission = permission; + } else if ("vendor" in set) { + this.vendorPermissions.set(set.vendor, permission); + } else if ("site" in set) { + if (this.allowedSites.includes(set.site)) { + this.sitePermissions.set(set.site, permission); + } + } else { + throw new Error(`Unrecognized extension set received: ${JSON.stringify(set)}.`); + } + + return this; + } + + permission(set: ExtensionSet) { + if ("all" in set && set.all) { + return this.allPermission; + } else if ("vendor" in set) { + return this.vendorPermissions.get(set.vendor); + } else if ("site" in set) { + return this.sitePermissions.get(set.site); + } else { + return undefined; + } + } + + permissions() { + const rules: { set: ExtensionSet; permission: ExtensionPermission }[] = []; + rules.push({ set: { all: true }, permission: this.allPermission }); + + for (const [site, permission] of this.sitePermissions.entries()) { + rules.push({ set: { site }, permission }); + } + + for (const [vendor, permission] of this.vendorPermissions.entries()) { + rules.push({ set: { vendor }, permission }); + } + + return rules; + } + + registerExtension(meta: ExtensionMetadata): this { + const site = this.siteRegistrations.get(meta.site.id); + const vendor = this.vendorRegistrations.get(meta.product.vendor.id); + if (!site || !vendor) { + return this; + } + + // exit early if the extension is already registered + const extensionsByVendor = + this.extensionsBySiteByVendor.get(meta.site.id) ?? new Map(); + if (extensionsByVendor.has(meta.product.vendor.id)) { + return this; + } + + // create immutable copy; this updates the vendor and site with + // their internalized representation to provide reference equality + // across registrations + const product: ProductMetadata = { vendor }; + if (meta.product.name) { + product.name = meta.product.name; + } + const extension: ExtensionMetadata = Object.freeze({ + site, + product: Object.freeze(product), + host: Object.freeze({ ...meta.host }), + requestedFields: Object.freeze([...meta.requestedFields]), + }); + + // register it + const index = this.extensionRegistrations.push(extension) - 1; + extensionsByVendor.set(vendor.id, index); + this.extensionsBySiteByVendor.set(site.id, extensionsByVendor); + + return this; + } + + extension(site: SiteId, vendor: VendorId): ExtensionMetadata | undefined { + const index = this.extensionsBySiteByVendor.get(site)?.get(vendor) ?? -1; + if (index < 0) { + return undefined; + } else { + return this.extensionRegistrations[index]; + } + } + + private getPermissions(site: SiteId, vendor: VendorId): ExtensionPermission[] { + const permissions = [ + this.sitePermissions.get(site), + this.vendorPermissions.get(vendor), + this.allPermission, + // Need to cast away `undefined` because typescript isn't + // aware that the filter eliminates undefined elements + ].filter((p) => !!p) as ExtensionPermission[]; + + return permissions; + } + + extensions(): ReadonlyArray<{ + extension: ExtensionMetadata; + permissions: ExtensionPermission[]; + }> { + const extensions = []; + for (const extension of this.extensionRegistrations) { + const permissions = this.getPermissions(extension.site.id, extension.product.vendor.id); + + extensions.push({ extension, permissions }); + } + + return extensions; + } + + build(id: SiteId): ExtensionSite | undefined { + const site = this.siteRegistrations.get(id); + if (!site) { + return undefined; + } + + if (this.allPermission === "deny") { + return new ExtensionSite(site, new Map()); + } + + const extensions = new Map(); + const entries = this.extensionsBySiteByVendor.get(id)?.entries() ?? ([] as const); + for (const [vendor, index] of entries) { + const permissions = this.getPermissions(id, vendor); + + const extension = evaluate(permissions, this.extensionRegistrations[index]); + if (extension) { + extensions.set(vendor, extension); + } + } + + const extensionSite = new ExtensionSite(site, extensions); + return extensionSite; + } +} + +function evaluate( + permissions: ExtensionPermission[], + value: ExtensionMetadata, +): ExtensionMetadata | undefined { + // deny always wins + if (permissions.includes("deny")) { + return undefined; + } + + // allow overrides implicit permissions + if (permissions.includes("allow")) { + return value; + } + + // none permission becomes a deny + if (permissions.includes("none")) { + return undefined; + } + + // default permission becomes an allow + if (permissions.includes("default")) { + return value; + } + + // if no permission is recognized, throw. This code is unreachable. + throw new Error("failed to recognize any permissions"); +} diff --git a/libs/common/src/tools/extension/type.ts b/libs/common/src/tools/extension/type.ts new file mode 100644 index 00000000000..26135e41421 --- /dev/null +++ b/libs/common/src/tools/extension/type.ts @@ -0,0 +1,142 @@ +import { Opaque } from "type-fest"; + +import { ObjectKey } from "../state/object-key"; + +import { Site, Field, Permission } from "./data"; + +/** well-known name for a feature extensible through an extension. */ +export type SiteId = keyof typeof Site; + +/** well-known name for a field surfaced from an extension site to a vendor. */ +export type FieldId = keyof typeof Field; + +/** Identifies a vendor extending bitwarden */ +export type VendorId = Opaque<"vendor", string>; + +/** uniquely identifies an extension. */ +export type ExtensionId = { site: SiteId; vendor: VendorId }; + +/** Permission levels for metadata. */ +export type ExtensionPermission = keyof typeof Permission; + +/** The preferred vendor to use at each site. */ +export type ExtensionPreferences = { + [key in SiteId]?: { vendor: VendorId; updated: Date }; +}; + +/** The capabilities and descriptive content for an extension */ +export type SiteMetadata = { + /** Uniquely identifies the extension site. */ + id: SiteId; + + /** Lists the fields disclosed by the extension to the vendor */ + availableFields: FieldId[]; +}; + +/** The capabilities and descriptive content for an extension */ +export type VendorMetadata = { + /** Uniquely identifies the vendor. */ + id: VendorId; + + /** Brand name of the service providing the extension. */ + name: string; +}; + +type TokenHeader = + | { + /** Transmit the token as the value of an `Authentication` header */ + authentication: true; + } + | { + /** Transmit the token as an `Authorization` header and a formatted value + * * `bearer` uses OAUTH-2.0 bearer token format + * * `token` prefixes the token with "Token" + * * `basic-username` uses HTTP Basic authentication format, encoding the + * token as the username. + */ + authorization: "bearer" | "token" | "basic-username"; + }; + +/** Catalogues an extension's hosting status. + * selfHost: "never" always uses the service's base URL + * selfHost: "maybe" allows the user to override the service's + * base URL with their own. + * selfHost: "always" requires a base URL. + */ +export type ApiHost = TokenHeader & + ( + | { selfHost: "never"; baseUrl: string } + | { selfHost: "maybe"; baseUrl: string } + | { selfHost: "always" } + ); + +/** Describes a branded product */ +export type ProductMetadata = { + /** The vendor providing the extension */ + vendor: VendorMetadata; + + /** The branded name of the product, if it varies from the Vendor name */ + name?: string; +}; + +/** Describes an extension provided by a vendor */ +export type ExtensionMetadata = { + /** The part of Bitwarden extended by the vendor's services */ + readonly site: Readonly; + + /** Product description */ + readonly product: Readonly; + + /** Hosting provider capabilities required by the extension */ + readonly host: Readonly; + + /** Lists the fields disclosed by the extension to the vendor. + * This should be a subset of the `availableFields` listed in + * the extension. + */ + readonly requestedFields: ReadonlyArray>; +}; + +/** Identifies a collection of extensions. + */ +export type ExtensionSet = + | { + /** A set of extensions sharing an extension point */ + site: SiteId; + } + | { + /** A set of extensions sharing a vendor */ + vendor: VendorId; + } + | { + /** The total set of extensions. This is used to set a categorical + * rule affecting all extensions. + */ + all: true; + }; + +/** A key for storing JavaScript objects (`{ an: "example" }`) + * in the extension profile system. + * @remarks The omitted keys are filled by the extension service. + */ +export type ExtensionStorageKey = Omit< + ObjectKey, + "target" | "state" | "format" | "classifier" +>; + +/** Extension profiles encapsulate data storage using the extension system. + */ +export type ExtensionProfileMetadata = { + /** distinguishes profile metadata types */ + type: "extension"; + + /** The extension site described by this metadata */ + site: Site; + + /** persistent storage location; `storage.key` is used to construct + * the extension key in the format `${extension.site}.${extension.vendor}.${storage.key}`, + * where `extension.`-prefixed fields are read from extension metadata. Extension + * settings always use the "classified" format and keep all fields private. + */ + storage: ExtensionStorageKey; +}; diff --git a/libs/common/src/tools/extension/util.spec.ts b/libs/common/src/tools/extension/util.spec.ts new file mode 100644 index 00000000000..f6f3341a986 --- /dev/null +++ b/libs/common/src/tools/extension/util.spec.ts @@ -0,0 +1,54 @@ +import { EXTENSION_DISK } from "../../platform/state"; +import { PrivateClassifier } from "../private-classifier"; +import { deepFreeze } from "../util"; + +import { Site } from "./data"; +import { ExtensionMetadata, ExtensionProfileMetadata } from "./type"; +import { toObjectKey } from "./util"; +import { Bitwarden } from "./vendor/bitwarden"; + +const ExampleProfile: ExtensionProfileMetadata = deepFreeze({ + type: "extension", + site: "forwarder", + storage: { + key: "example", + options: { + clearOn: [], + deserializer: (value) => value as any, + }, + initial: {}, + frame: 1, + }, +}); + +const ExampleMetadata: ExtensionMetadata = { + site: { id: Site.forwarder, availableFields: [] }, + product: { vendor: Bitwarden }, + host: { authentication: true, selfHost: "maybe", baseUrl: "http://example.com" }, + requestedFields: [], +}; + +describe("toObjectKey", () => { + it("sets static fields", () => { + const result = toObjectKey(ExampleProfile, ExampleMetadata); + + expect(result.target).toEqual("object"); + expect(result.format).toEqual("classified"); + expect(result.state).toBe(EXTENSION_DISK); + expect(result.classifier).toBeInstanceOf(PrivateClassifier); + }); + + it("creates a dynamic object key", () => { + const result = toObjectKey(ExampleProfile, ExampleMetadata); + + expect(result.key).toEqual("forwarder.bitwarden.example"); + }); + + it("copies the profile storage metadata", () => { + const result = toObjectKey(ExampleProfile, ExampleMetadata); + + expect(result.frame).toEqual(ExampleProfile.storage.frame); + expect(result.options).toBe(ExampleProfile.storage.options); + expect(result.initial).toBe(ExampleProfile.storage.initial); + }); +}); diff --git a/libs/common/src/tools/extension/util.ts b/libs/common/src/tools/extension/util.ts new file mode 100644 index 00000000000..f700e84c497 --- /dev/null +++ b/libs/common/src/tools/extension/util.ts @@ -0,0 +1,36 @@ +import { EXTENSION_DISK } from "../../platform/state"; +import { PrivateClassifier } from "../private-classifier"; +import { Classifier } from "../state/classifier"; +import { ObjectKey } from "../state/object-key"; + +import { ExtensionMetadata, ExtensionProfileMetadata, SiteId } from "./type"; + +/** Create an object key from an extension instance and a site profile. + * @param profile the extension profile to bind + * @param extension the extension metadata to bind + */ +export function toObjectKey( + profile: ExtensionProfileMetadata, + extension: ExtensionMetadata, +) { + // FIXME: eliminate this cast + const classifier = new PrivateClassifier() as Classifier< + Settings, + Record, + Settings + >; + + const result: ObjectKey = { + // copy storage to retain extensibility + ...profile.storage, + + // fields controlled by the extension system override those in the profile + target: "object", + key: `${extension.site.id}.${extension.product.vendor.id}.${profile.storage.key}`, + state: EXTENSION_DISK, + classifier, + format: "classified", + }; + + return result; +} diff --git a/libs/common/src/tools/extension/vendor/addyio.ts b/libs/common/src/tools/extension/vendor/addyio.ts new file mode 100644 index 00000000000..c33abd570ad --- /dev/null +++ b/libs/common/src/tools/extension/vendor/addyio.ts @@ -0,0 +1,25 @@ +import { Field } from "../data"; +import { Extension } from "../metadata"; +import { ExtensionMetadata, VendorMetadata } from "../type"; + +import { Vendor } from "./data"; + +export const AddyIo: VendorMetadata = { + id: Vendor.addyio, + name: "Addy.io", +}; + +export const AddyIoExtensions: ExtensionMetadata[] = [ + { + site: Extension.forwarder, + product: { + vendor: AddyIo, + }, + host: { + authorization: "bearer", + selfHost: "maybe", + baseUrl: "https://app.addy.io", + }, + requestedFields: [Field.token, Field.baseUrl, Field.domain], + }, +]; diff --git a/libs/common/src/tools/extension/vendor/bitwarden.ts b/libs/common/src/tools/extension/vendor/bitwarden.ts new file mode 100644 index 00000000000..7f659c2d07f --- /dev/null +++ b/libs/common/src/tools/extension/vendor/bitwarden.ts @@ -0,0 +1,8 @@ +import { VendorMetadata } from "../type"; + +import { Vendor } from "./data"; + +export const Bitwarden: VendorMetadata = Object.freeze({ + id: Vendor.bitwarden, + name: "Bitwarden", +}); diff --git a/libs/common/src/tools/extension/vendor/data.ts b/libs/common/src/tools/extension/vendor/data.ts new file mode 100644 index 00000000000..7f0802ef82f --- /dev/null +++ b/libs/common/src/tools/extension/vendor/data.ts @@ -0,0 +1,11 @@ +import { VendorId } from "../type"; + +export const Vendor = Object.freeze({ + addyio: "addyio" as VendorId, + bitwarden: "bitwarden" as VendorId, // RESERVED + duckduckgo: "duckduckgo" as VendorId, + fastmail: "fastmail" as VendorId, + forwardemail: "forwardemail" as VendorId, + mozilla: "mozilla" as VendorId, + simplelogin: "simplelogin" as VendorId, +} as const); diff --git a/libs/common/src/tools/extension/vendor/duckduckgo.ts b/libs/common/src/tools/extension/vendor/duckduckgo.ts new file mode 100644 index 00000000000..ca4634192f5 --- /dev/null +++ b/libs/common/src/tools/extension/vendor/duckduckgo.ts @@ -0,0 +1,25 @@ +import { Field } from "../data"; +import { Extension } from "../metadata"; +import { ExtensionMetadata, VendorMetadata } from "../type"; + +import { Vendor } from "./data"; + +export const DuckDuckGo: VendorMetadata = { + id: Vendor.duckduckgo, + name: "DuckDuckGo", +}; + +export const DuckDuckGoExtensions: ExtensionMetadata[] = [ + { + site: Extension.forwarder, + product: { + vendor: DuckDuckGo, + }, + host: { + authorization: "bearer", + selfHost: "never", + baseUrl: "https://quack.duckduckgo.com/api", + }, + requestedFields: [Field.token], + }, +]; diff --git a/libs/common/src/tools/extension/vendor/fastmail.ts b/libs/common/src/tools/extension/vendor/fastmail.ts new file mode 100644 index 00000000000..e6fb9ec16be --- /dev/null +++ b/libs/common/src/tools/extension/vendor/fastmail.ts @@ -0,0 +1,25 @@ +import { Field } from "../data"; +import { Extension } from "../metadata"; +import { ExtensionMetadata, VendorMetadata } from "../type"; + +import { Vendor } from "./data"; + +export const Fastmail: VendorMetadata = { + id: Vendor.fastmail, + name: "Fastmail", +}; + +export const FastmailExtensions: ExtensionMetadata[] = [ + { + site: Extension.forwarder, + product: { + vendor: Fastmail, + }, + host: { + authorization: "bearer", + selfHost: "maybe", + baseUrl: "https://api.fastmail.com", + }, + requestedFields: [Field.token], + }, +]; diff --git a/libs/common/src/tools/extension/vendor/forwardemail.ts b/libs/common/src/tools/extension/vendor/forwardemail.ts new file mode 100644 index 00000000000..4fbc8c139b1 --- /dev/null +++ b/libs/common/src/tools/extension/vendor/forwardemail.ts @@ -0,0 +1,25 @@ +import { Field } from "../data"; +import { Extension } from "../metadata"; +import { ExtensionMetadata, VendorMetadata } from "../type"; + +import { Vendor } from "./data"; + +export const ForwardEmail: VendorMetadata = { + id: Vendor.forwardemail, + name: "Forward Email", +}; + +export const ForwardEmailExtensions: ExtensionMetadata[] = [ + { + site: Extension.forwarder, + product: { + vendor: ForwardEmail, + }, + host: { + authorization: "basic-username", + selfHost: "never", + baseUrl: "https://api.forwardemail.net", + }, + requestedFields: [Field.domain, Field.token], + }, +]; diff --git a/libs/common/src/tools/extension/vendor/index.ts b/libs/common/src/tools/extension/vendor/index.ts new file mode 100644 index 00000000000..3bac78c80db --- /dev/null +++ b/libs/common/src/tools/extension/vendor/index.ts @@ -0,0 +1,30 @@ +import { deepFreeze } from "../../util"; + +import { AddyIo, AddyIoExtensions } from "./addyio"; +import { Bitwarden } from "./bitwarden"; +import { DuckDuckGo, DuckDuckGoExtensions } from "./duckduckgo"; +import { Fastmail, FastmailExtensions } from "./fastmail"; +import { ForwardEmail, ForwardEmailExtensions } from "./forwardemail"; +import { Mozilla, MozillaExtensions } from "./mozilla"; +import { SimpleLogin, SimpleLoginExtensions } from "./simplelogin"; + +export const Vendors = deepFreeze([ + AddyIo, + Bitwarden, + DuckDuckGo, + Fastmail, + ForwardEmail, + Mozilla, + SimpleLogin, +]); + +export const VendorExtensions = deepFreeze( + [ + AddyIoExtensions, + DuckDuckGoExtensions, + FastmailExtensions, + ForwardEmailExtensions, + MozillaExtensions, + SimpleLoginExtensions, + ].flat(), +); diff --git a/libs/common/src/tools/extension/vendor/mozilla.ts b/libs/common/src/tools/extension/vendor/mozilla.ts new file mode 100644 index 00000000000..b02b97d8777 --- /dev/null +++ b/libs/common/src/tools/extension/vendor/mozilla.ts @@ -0,0 +1,26 @@ +import { Field } from "../data"; +import { Extension } from "../metadata"; +import { ExtensionMetadata, VendorMetadata } from "../type"; + +import { Vendor } from "./data"; + +export const Mozilla: VendorMetadata = { + id: Vendor.mozilla, + name: "Mozilla", +}; + +export const MozillaExtensions: ExtensionMetadata[] = [ + { + site: Extension.forwarder, + product: { + vendor: Mozilla, + name: "Firefox Relay", + }, + host: { + authorization: "token", + selfHost: "never", + baseUrl: "https://relay.firefox.com/api", + }, + requestedFields: [Field.token], + }, +]; diff --git a/libs/common/src/tools/extension/vendor/readme.md b/libs/common/src/tools/extension/vendor/readme.md new file mode 100644 index 00000000000..507769edd4e --- /dev/null +++ b/libs/common/src/tools/extension/vendor/readme.md @@ -0,0 +1,33 @@ +# Vendors + +This folder contains vendor-specific logic that extends the +Bitwarden password manager. + +## Vendor IDs + +A vendor's ID is used to identify and trace the code provided by +a vendor across Bitwarden. There are a few rules that vendor ids +must follow: + +1. They should be human-readable. (No UUIDs.) +2. They may only contain lowercase ASCII characters and numbers. +3. They must retain backwards compatibility with prior versions. + +As such, any given ID may not not match the vendor's present +brand identity. Said branding may be stored in `VendorMetadata.name`. + +## Core files + +There are 4 vendor-independent files in this directory. + +- `data.ts` - core metadata used for system initialization +- `index.ts` - exports vendor metadata +- `README.md` - this file + +## Vendor definitions + +Each vendor should have one and only one definition, whose name +MUST match their `VendorId`. The vendor is free to use either a +single file (e.g. `bitwarden.ts`) or a folder containing multiple +files (e.g. `bitwarden/extension.ts`, `bitwarden/forwarder.ts`) to +host their files. diff --git a/libs/common/src/tools/extension/vendor/simplelogin.ts b/libs/common/src/tools/extension/vendor/simplelogin.ts new file mode 100644 index 00000000000..21ee969cebb --- /dev/null +++ b/libs/common/src/tools/extension/vendor/simplelogin.ts @@ -0,0 +1,25 @@ +import { Field } from "../data"; +import { Extension } from "../metadata"; +import { ExtensionMetadata, VendorMetadata } from "../type"; + +import { Vendor } from "./data"; + +export const SimpleLogin: VendorMetadata = { + id: Vendor.simplelogin, + name: "SimpleLogin", +}; + +export const SimpleLoginExtensions: ExtensionMetadata[] = [ + { + site: Extension.forwarder, + product: { + vendor: SimpleLogin, + }, + host: { + authentication: true, + selfHost: "maybe", + baseUrl: "https://app.simplelogin.io", + }, + requestedFields: [Field.baseUrl, Field.token, Field.domain], + }, +]; diff --git a/libs/common/src/tools/integration/integration-context.spec.ts b/libs/common/src/tools/integration/integration-context.spec.ts index 58115c783c7..42581c08dee 100644 --- a/libs/common/src/tools/integration/integration-context.spec.ts +++ b/libs/common/src/tools/integration/integration-context.spec.ts @@ -1,6 +1,6 @@ import { mock } from "jest-mock-extended"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { I18nService } from "../../platform/abstractions/i18n.service"; import { IntegrationContext } from "./integration-context"; import { IntegrationId } from "./integration-id"; diff --git a/libs/common/src/tools/integration/integration-context.ts b/libs/common/src/tools/integration/integration-context.ts index f30810ff81a..40648df6803 100644 --- a/libs/common/src/tools/integration/integration-context.ts +++ b/libs/common/src/tools/integration/integration-context.ts @@ -1,7 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { I18nService } from "../../platform/abstractions/i18n.service"; +import { Utils } from "../../platform/misc/utils"; import { IntegrationMetadata } from "./integration-metadata"; import { ApiSettings, IntegrationRequest } from "./rpc"; diff --git a/libs/common/src/tools/integration/integration-id.ts b/libs/common/src/tools/integration/integration-id.ts index a15db143ee1..a3d83aba46e 100644 --- a/libs/common/src/tools/integration/integration-id.ts +++ b/libs/common/src/tools/integration/integration-id.ts @@ -10,4 +10,4 @@ export const IntegrationIds = [ ] as const; /** Identifies a vendor integrated into bitwarden */ -export type IntegrationId = Opaque<(typeof IntegrationIds)[number], "IntegrationId">; +export type IntegrationId = Opaque; diff --git a/libs/common/src/tools/integration/rpc/rest-client.spec.ts b/libs/common/src/tools/integration/rpc/rest-client.spec.ts index e113ab9ff43..15e880485dc 100644 --- a/libs/common/src/tools/integration/rpc/rest-client.spec.ts +++ b/libs/common/src/tools/integration/rpc/rest-client.spec.ts @@ -1,7 +1,7 @@ import { mock } from "jest-mock-extended"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { ApiService } from "../../../abstractions/api.service"; +import { I18nService } from "../../../platform/abstractions/i18n.service"; import { IntegrationRequest } from "./integration-request"; import { RestClient } from "./rest-client"; @@ -51,52 +51,52 @@ describe("RestClient", () => { expect(api.nativeFetch).toHaveBeenCalledWith(expectedRpc.fetchRequest); }); - it.each([[401] /*,[403]*/])( - "throws an invalid token error when HTTP status is %i", - async (status) => { - const client = new RestClient(api, i18n); - const request: IntegrationRequest = { website: null }; - const response = mock({ status, statusText: null }); - api.nativeFetch.mockResolvedValue(response); + it.each([ + [401, "forwaderInvalidToken"], + [403, "forwaderInvalidOperation"], + ])("throws an invalid token error when HTTP status is %i", async (status, messageKey) => { + const client = new RestClient(api, i18n); + const request: IntegrationRequest = { website: null }; + const response = mock({ status, statusText: null }); + api.nativeFetch.mockResolvedValue(response); - const result = client.fetchJson(rpc, request); + const result = client.fetchJson(rpc, request); - await expect(result).rejects.toEqual("forwaderInvalidToken"); - }, - ); + await expect(result).rejects.toEqual(messageKey); + }); it.each([ - [401, null, null], - [401, undefined, undefined], - [401, undefined, null], - [403, null, null], - [403, undefined, undefined], - [403, undefined, null], + [401, null, null, "forwaderInvalidToken"], + [401, undefined, undefined, "forwaderInvalidToken"], + [401, undefined, null, "forwaderInvalidToken"], + [403, null, null, "forwaderInvalidOperation"], + [403, undefined, undefined, "forwaderInvalidOperation"], + [403, undefined, null, "forwaderInvalidOperation"], ])( "throws an invalid token error when HTTP status is %i, message is %p, and error is %p", - async (status) => { + async (status, message, error, messageKey) => { const client = new RestClient(api, i18n); const request: IntegrationRequest = { website: null }; const response = mock({ status, - text: () => Promise.resolve(`{ "message": null, "error": null }`), + text: () => Promise.resolve(JSON.stringify({ message, error })), }); api.nativeFetch.mockResolvedValue(response); const result = client.fetchJson(rpc, request); - await expect(result).rejects.toEqual("forwaderInvalidToken"); + await expect(result).rejects.toEqual(messageKey); }, ); it.each([ - [401, "message"], - [403, "message"], - [401, "error"], - [403, "error"], + [401, "message", "forwaderInvalidTokenWithMessage"], + [403, "message", "forwaderInvalidOperationWithMessage"], + [401, "error", "forwaderInvalidTokenWithMessage"], + [403, "error", "forwaderInvalidOperationWithMessage"], ])( "throws an invalid token detailed error when HTTP status is %i and the payload has a %s", - async (status, property) => { + async (status, property, messageKey) => { const client = new RestClient(api, i18n); const request: IntegrationRequest = { website: null }; const response = mock({ @@ -107,18 +107,17 @@ describe("RestClient", () => { const result = client.fetchJson(rpc, request); - await expect(result).rejects.toEqual("forwaderInvalidTokenWithMessage"); - expect(i18n.t).toHaveBeenCalledWith( - "forwaderInvalidTokenWithMessage", - "mock", - "expected message", - ); + await expect(result).rejects.toEqual(messageKey); + expect(i18n.t).toHaveBeenCalledWith(messageKey, "mock", "expected message"); }, ); - it.each([[401], [403]])( + it.each([ + [401, "forwaderInvalidTokenWithMessage"], + [403, "forwaderInvalidOperationWithMessage"], + ])( "throws an invalid token detailed error when HTTP status is %i and the payload has a %s", - async (status) => { + async (status, messageKey) => { const client = new RestClient(api, i18n); const request: IntegrationRequest = { website: null }; const response = mock({ @@ -130,12 +129,8 @@ describe("RestClient", () => { const result = client.fetchJson(rpc, request); - await expect(result).rejects.toEqual("forwaderInvalidTokenWithMessage"); - expect(i18n.t).toHaveBeenCalledWith( - "forwaderInvalidTokenWithMessage", - "mock", - "that happened: expected message", - ); + await expect(result).rejects.toEqual(messageKey); + expect(i18n.t).toHaveBeenCalledWith(messageKey, "mock", "that happened: expected message"); }, ); diff --git a/libs/common/src/tools/integration/rpc/rest-client.ts b/libs/common/src/tools/integration/rpc/rest-client.ts index 287bb4f2573..13e598cc05f 100644 --- a/libs/common/src/tools/integration/rpc/rest-client.ts +++ b/libs/common/src/tools/integration/rpc/rest-client.ts @@ -1,7 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { ApiService } from "../../../abstractions/api.service"; +import { I18nService } from "../../../platform/abstractions/i18n.service"; import { IntegrationRequest } from "./integration-request"; import { JsonRpc } from "./rpc"; @@ -46,10 +46,14 @@ export class RestClient { } private async detectCommonErrors(response: Response): Promise<[string, string] | undefined> { - if (response.status === 401 || response.status === 403) { + if (response.status === 401) { const message = await this.tryGetErrorMessage(response); const key = message ? "forwaderInvalidTokenWithMessage" : "forwaderInvalidToken"; return [key, message]; + } else if (response.status === 403) { + const message = await this.tryGetErrorMessage(response); + const key = message ? "forwaderInvalidOperationWithMessage" : "forwaderInvalidOperation"; + return [key, message]; } else if (response.status >= 400) { const message = await this.tryGetErrorMessage(response); const key = message ? "forwarderError" : "forwarderUnknownError"; diff --git a/libs/common/src/tools/log/default-semantic-logger.spec.ts b/libs/common/src/tools/log/default-semantic-logger.spec.ts new file mode 100644 index 00000000000..7f608fb40ef --- /dev/null +++ b/libs/common/src/tools/log/default-semantic-logger.spec.ts @@ -0,0 +1,214 @@ +import { mock } from "jest-mock-extended"; + +import { LogService } from "../../platform/abstractions/log.service"; +import { LogLevelType } from "../../platform/enums"; + +import { DefaultSemanticLogger } from "./default-semantic-logger"; + +const logger = mock(); + +describe("DefaultSemanticLogger", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe("debug", () => { + it("writes structural log messages to console.log", () => { + const log = new DefaultSemanticLogger(logger, {}, () => 0); + + log.debug("this is a debug message"); + + expect(logger.write).toHaveBeenCalledWith(LogLevelType.Debug, { + "@timestamp": 0, + message: "this is a debug message", + level: "debug", + }); + }); + + it("writes structural content to console.log", () => { + const log = new DefaultSemanticLogger(logger, {}, () => 0); + + log.debug({ example: "this is content" }); + + expect(logger.write).toHaveBeenCalledWith(LogLevelType.Debug, { + "@timestamp": 0, + content: { example: "this is content" }, + level: "debug", + }); + }); + + it("writes structural content to console.log with a message", () => { + const log = new DefaultSemanticLogger(logger, {}, () => 0); + + log.info({ example: "this is content" }, "this is a message"); + + expect(logger.write).toHaveBeenCalledWith(LogLevelType.Info, { + "@timestamp": 0, + content: { example: "this is content" }, + message: "this is a message", + level: "information", + }); + }); + }); + + describe("info", () => { + it("writes structural log messages to console.log", () => { + const log = new DefaultSemanticLogger(logger, {}, () => 0); + + log.info("this is an info message"); + + expect(logger.write).toHaveBeenCalledWith(LogLevelType.Info, { + "@timestamp": 0, + message: "this is an info message", + level: "information", + }); + }); + + it("writes structural content to console.log", () => { + const log = new DefaultSemanticLogger(logger, {}, () => 0); + + log.info({ example: "this is content" }); + + expect(logger.write).toHaveBeenCalledWith(LogLevelType.Info, { + "@timestamp": 0, + content: { example: "this is content" }, + level: "information", + }); + }); + + it("writes structural content to console.log with a message", () => { + const log = new DefaultSemanticLogger(logger, {}, () => 0); + + log.info({ example: "this is content" }, "this is a message"); + + expect(logger.write).toHaveBeenCalledWith(LogLevelType.Info, { + "@timestamp": 0, + content: { example: "this is content" }, + message: "this is a message", + level: "information", + }); + }); + }); + + describe("warn", () => { + it("writes structural log messages to console.warn", () => { + const log = new DefaultSemanticLogger(logger, {}, () => 0); + + log.warn("this is a warning message"); + + expect(logger.write).toHaveBeenCalledWith(LogLevelType.Warning, { + "@timestamp": 0, + message: "this is a warning message", + level: "warning", + }); + }); + + it("writes structural content to console.warn", () => { + const log = new DefaultSemanticLogger(logger, {}, () => 0); + + log.warn({ example: "this is content" }); + + expect(logger.write).toHaveBeenCalledWith(LogLevelType.Warning, { + "@timestamp": 0, + content: { example: "this is content" }, + level: "warning", + }); + }); + + it("writes structural content to console.warn with a message", () => { + const log = new DefaultSemanticLogger(logger, {}, () => 0); + + log.warn({ example: "this is content" }, "this is a message"); + + expect(logger.write).toHaveBeenCalledWith(LogLevelType.Warning, { + "@timestamp": 0, + content: { example: "this is content" }, + message: "this is a message", + level: "warning", + }); + }); + }); + + describe("error", () => { + it("writes structural log messages to console.error", () => { + const log = new DefaultSemanticLogger(logger, {}, () => 0); + + log.error("this is an error message"); + + expect(logger.write).toHaveBeenCalledWith(LogLevelType.Error, { + "@timestamp": 0, + message: "this is an error message", + level: "error", + }); + }); + + it("writes structural content to console.error", () => { + const log = new DefaultSemanticLogger(logger, {}, () => 0); + + log.error({ example: "this is content" }); + + expect(logger.write).toHaveBeenCalledWith(LogLevelType.Error, { + "@timestamp": 0, + content: { example: "this is content" }, + level: "error", + }); + }); + + it("writes structural content to console.error with a message", () => { + const log = new DefaultSemanticLogger(logger, {}, () => 0); + + log.error({ example: "this is content" }, "this is a message"); + + expect(logger.write).toHaveBeenCalledWith(LogLevelType.Error, { + "@timestamp": 0, + content: { example: "this is content" }, + message: "this is a message", + level: "error", + }); + }); + }); + + describe("panic", () => { + it("writes structural log messages to console.error before throwing the message", () => { + const log = new DefaultSemanticLogger(logger, {}, () => 0); + + expect(() => log.panic("this is an error message")).toThrow("this is an error message"); + + expect(logger.write).toHaveBeenCalledWith(LogLevelType.Error, { + "@timestamp": 0, + message: "this is an error message", + level: "error", + }); + }); + + it("writes structural log messages to console.error with a message before throwing the message", () => { + const log = new DefaultSemanticLogger(logger, {}, () => 0); + + expect(() => log.panic({ example: "this is content" }, "this is an error message")).toThrow( + "this is an error message", + ); + + expect(logger.write).toHaveBeenCalledWith(LogLevelType.Error, { + "@timestamp": 0, + content: { example: "this is content" }, + message: "this is an error message", + level: "error", + }); + }); + + it("writes structural log messages to console.error with a content before throwing the message", () => { + const log = new DefaultSemanticLogger(logger, {}, () => 0); + + expect(() => log.panic("this is content", "this is an error message")).toThrow( + "this is an error message", + ); + + expect(logger.write).toHaveBeenCalledWith(LogLevelType.Error, { + "@timestamp": 0, + content: "this is content", + message: "this is an error message", + level: "error", + }); + }); + }); +}); diff --git a/libs/common/src/tools/log/default-semantic-logger.ts b/libs/common/src/tools/log/default-semantic-logger.ts new file mode 100644 index 00000000000..eb1ecbe36c6 --- /dev/null +++ b/libs/common/src/tools/log/default-semantic-logger.ts @@ -0,0 +1,82 @@ +import { Jsonify } from "type-fest"; + +import { LogService } from "../../platform/abstractions/log.service"; +import { LogLevelType } from "../../platform/enums"; + +import { SemanticLogger } from "./semantic-logger.abstraction"; + +/** Sends semantic logs to the console. + * @remarks the behavior of this logger is based on `LogService`; it + * replaces dynamic messages (`%s`) with a JSON-formatted semantic log. + */ +export class DefaultSemanticLogger implements SemanticLogger { + /** Instantiates a console semantic logger + * @param context a static payload that is cloned when the logger + * logs a message. The `messages`, `level`, and `content` fields + * are reserved for use by loggers. + */ + constructor( + private logger: LogService, + context: Jsonify, + private now = () => Date.now(), + ) { + this.context = context && typeof context === "object" ? context : {}; + } + + readonly context: object; + + debug(content: Jsonify, message?: string): void { + this.log(content, LogLevelType.Debug, message); + } + + info(content: Jsonify, message?: string): void { + this.log(content, LogLevelType.Info, message); + } + + warn(content: Jsonify, message?: string): void { + this.log(content, LogLevelType.Warning, message); + } + + error(content: Jsonify, message?: string): void { + this.log(content, LogLevelType.Error, message); + } + + panic(content: Jsonify, message?: string): never { + this.log(content, LogLevelType.Error, message); + const panicMessage = + message ?? (typeof content === "string" ? content : "a fatal error occurred"); + throw new Error(panicMessage); + } + + private log(content: Jsonify, level: LogLevelType, message?: string) { + const log = { + ...this.context, + message, + content: content ?? undefined, + level: stringifyLevel(level), + "@timestamp": this.now(), + }; + + if (typeof content === "string" && !message) { + log.message = content; + delete log.content; + } + + this.logger.write(level, log); + } +} + +function stringifyLevel(level: LogLevelType) { + switch (level) { + case LogLevelType.Debug: + return "debug"; + case LogLevelType.Info: + return "information"; + case LogLevelType.Warning: + return "warning"; + case LogLevelType.Error: + return "error"; + default: + return `${level}`; + } +} diff --git a/libs/common/src/tools/log/disabled-semantic-logger.ts b/libs/common/src/tools/log/disabled-semantic-logger.ts new file mode 100644 index 00000000000..054c3ed390b --- /dev/null +++ b/libs/common/src/tools/log/disabled-semantic-logger.ts @@ -0,0 +1,18 @@ +import { Jsonify } from "type-fest"; + +import { SemanticLogger } from "./semantic-logger.abstraction"; + +/** Disables semantic logs. Still panics. */ +export class DisabledSemanticLogger implements SemanticLogger { + debug(_content: Jsonify, _message?: string): void {} + + info(_content: Jsonify, _message?: string): void {} + + warn(_content: Jsonify, _message?: string): void {} + + error(_content: Jsonify, _message?: string): void {} + + panic(_content: Jsonify, message?: string): never { + throw new Error(message); + } +} diff --git a/libs/common/src/tools/log/factory.ts b/libs/common/src/tools/log/factory.ts new file mode 100644 index 00000000000..f8abc4d2240 --- /dev/null +++ b/libs/common/src/tools/log/factory.ts @@ -0,0 +1,55 @@ +import { Jsonify } from "type-fest"; + +import { LogService } from "../../platform/abstractions/log.service"; + +import { DefaultSemanticLogger } from "./default-semantic-logger"; +import { DisabledSemanticLogger } from "./disabled-semantic-logger"; +import { SemanticLogger } from "./semantic-logger.abstraction"; + +/** A type for injection of a log provider */ +export type LogProvider = (context: Jsonify) => SemanticLogger; + +/** Instantiates a semantic logger that emits nothing when a message + * is logged. + * @param _context a static payload that is cloned when the logger + * logs a message. The `messages`, `level`, and `content` fields + * are reserved for use by loggers. + */ +export function disabledSemanticLoggerProvider( + _context: Jsonify, +): SemanticLogger { + return new DisabledSemanticLogger(); +} + +/** Instantiates a semantic logger that emits logs to the console. + * @param context a static payload that is cloned when the logger + * logs a message. The `messages`, `level`, and `content` fields + * are reserved for use by loggers. + * @param settings specializes how the semantic logger functions. + * If this is omitted, the logger suppresses debug messages. + */ +export function consoleSemanticLoggerProvider( + logger: LogService, + context: Jsonify, +): SemanticLogger { + return new DefaultSemanticLogger(logger, context); +} + +/** Instantiates a semantic logger that emits logs to the console. + * @param context a static payload that is cloned when the logger + * logs a message. The `messages`, `level`, and `content` fields + * are reserved for use by loggers. + * @param settings specializes how the semantic logger functions. + * If this is omitted, the logger suppresses debug messages. + */ +export function ifEnabledSemanticLoggerProvider( + enable: boolean, + logger: LogService, + context: Jsonify, +) { + if (enable) { + return consoleSemanticLoggerProvider(logger, context); + } else { + return disabledSemanticLoggerProvider(context); + } +} diff --git a/libs/common/src/tools/log/index.ts b/libs/common/src/tools/log/index.ts new file mode 100644 index 00000000000..22444d23e27 --- /dev/null +++ b/libs/common/src/tools/log/index.ts @@ -0,0 +1,2 @@ +export * from "./factory"; +export { SemanticLogger } from "./semantic-logger.abstraction"; diff --git a/libs/common/src/tools/log/semantic-logger.abstraction.ts b/libs/common/src/tools/log/semantic-logger.abstraction.ts new file mode 100644 index 00000000000..196d1f3f12c --- /dev/null +++ b/libs/common/src/tools/log/semantic-logger.abstraction.ts @@ -0,0 +1,94 @@ +import { Jsonify } from "type-fest"; + +/** Semantic/structural logging component */ +export interface SemanticLogger { + /** Logs a message at debug priority. + * Debug messages are used for diagnostics, and are typically disabled + * in production builds. + * @param message - a message to record in the log's `message` field. + */ + debug(message: string): void; + + /** Logs the content at debug priority. + * Debug messages are used for diagnostics, and are typically disabled + * in production builds. + * @param content - JSON content included in the log's `content` field. + * @param message - a message to record in the log's `message` field. + */ + debug(content: Jsonify, message?: string): void; + + /** combined signature for overloaded methods */ + debug(content: Jsonify | string, message?: string): void; + + /** Logs a message at informational priority. + * Information messages are used for status reports. + * @param message - a message to record in the log's `message` field. + */ + info(message: string): void; + + /** Logs the content at informational priority. + * Information messages are used for status reports. + * @param content - JSON content included in the log's `content` field. + * @param message - a message to record in the log's `message` field. + */ + info(content: Jsonify, message?: string): void; + + /** combined signature for overloaded methods */ + info(content: Jsonify | string, message?: string): void; + + /** Logs a message at warn priority. + * Warn messages are used to indicate a operation that may affect system + * stability occurred. + * @param message - a message to record in the log's `message` field. + */ + warn(message: string): void; + + /** Logs the content at warn priority. + * Warn messages are used to indicate a operation that may affect system + * stability occurred. + * @param content - JSON content included in the log's `content` field. + * @param message - a message to record in the log's `message` field. + */ + warn(content: Jsonify, message?: string): void; + + /** combined signature for overloaded methods */ + warn(content: Jsonify | string, message?: string): void; + + /** Logs a message at error priority. + * Error messages are used to indicate a operation that affects system + * stability occurred and the system was able to recover. + * @param message - a message to record in the log's `message` field. + */ + error(message: string): void; + + /** Logs the content at debug priority. + * Error messages are used to indicate a operation that affects system + * stability occurred and the system was able to recover. + * @param content - JSON content included in the log's `content` field. + * @param message - a message to record in the log's `message` field. + */ + error(content: Jsonify, message?: string): void; + + /** combined signature for overloaded methods */ + error(content: Jsonify | string, message?: string): void; + + /** Logs a message at panic priority and throws an error. + * Panic messages are used to indicate a operation that affects system + * stability occurred and the system cannot recover. Panic messages + * log an error and throw an `Error`. + * @param message - a message to record in the log's `message` field. + */ + panic(message: string): never; + + /** Logs the content at debug priority and throws an error. + * Panic messages are used to indicate a operation that affects system + * stability occurred and the system cannot recover. Panic messages + * log an error and throw an `Error`. + * @param content - JSON content included in the log's `content` field. + * @param message - a message to record in the log's `message` field. + */ + panic(content: Jsonify, message?: string): never; + + /** combined signature for overloaded methods */ + panic(content: Jsonify | string, message?: string): never; +} diff --git a/libs/common/src/tools/private-classifier.ts b/libs/common/src/tools/private-classifier.ts index de21f78f1c6..e2406d314c0 100644 --- a/libs/common/src/tools/private-classifier.ts +++ b/libs/common/src/tools/private-classifier.ts @@ -2,7 +2,7 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; -import { Classifier } from "@bitwarden/common/tools/state/classifier"; +import { Classifier } from "./state/classifier"; export class PrivateClassifier implements Classifier, Data> { constructor(private keys: (keyof Jsonify)[] = undefined) {} @@ -17,7 +17,7 @@ export class PrivateClassifier implements Classifier; - return { disclosed: null, secret }; + return { disclosed: {}, secret }; } declassify(_disclosed: Jsonify>, secret: Jsonify) { diff --git a/libs/common/src/tools/providers.ts b/libs/common/src/tools/providers.ts new file mode 100644 index 00000000000..a22a22addc5 --- /dev/null +++ b/libs/common/src/tools/providers.ts @@ -0,0 +1,16 @@ +import { PolicyService } from "../admin-console/abstractions/policy/policy.service.abstraction"; + +import { ExtensionService } from "./extension/extension.service"; +import { LogProvider } from "./log"; + +/** Provides access to commonly-used cross-cutting services. */ +export type SystemServiceProvider = { + /** Policy configured by the administrative console */ + readonly policy: PolicyService; + + /** Client extension metadata and profile access */ + readonly extension: ExtensionService; + + /** Event monitoring and diagnostic interfaces */ + readonly log: LogProvider; +}; diff --git a/libs/common/src/tools/public-classifier.ts b/libs/common/src/tools/public-classifier.ts index e7c8c24ba78..136bee555ac 100644 --- a/libs/common/src/tools/public-classifier.ts +++ b/libs/common/src/tools/public-classifier.ts @@ -2,7 +2,7 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; -import { Classifier } from "@bitwarden/common/tools/state/classifier"; +import { Classifier } from "./state/classifier"; export class PublicClassifier implements Classifier> { constructor(private keys: (keyof Jsonify)[]) {} @@ -16,7 +16,7 @@ export class PublicClassifier implements Classifier; - return { disclosed, secret: null }; + return { disclosed, secret: "" }; } declassify(disclosed: Jsonify, _secret: Jsonify>) { diff --git a/libs/common/src/tools/rx.spec.ts b/libs/common/src/tools/rx.spec.ts index 9ce147a3ff4..ee1de1c9118 100644 --- a/libs/common/src/tools/rx.spec.ts +++ b/libs/common/src/tools/rx.spec.ts @@ -2,6 +2,7 @@ * include structuredClone in test environment. * @jest-environment ../../../../shared/test.environment.ts */ +// @ts-strict-ignore this file explicitly tests what happens when types are ignored import { of, firstValueFrom, Subject, tap, EmptyError } from "rxjs"; import { awaitAsync, trackEmissions } from "../../spec"; @@ -14,6 +15,7 @@ import { ready, reduceCollection, withLatestReady, + pin, } from "./rx"; describe("errorOnChange", () => { @@ -56,7 +58,7 @@ describe("errorOnChange", () => { source$.complete(); - expect(complete).toBeTrue(); + expect(complete).toBe(true); }); it("errors when the input changes", async () => { @@ -675,3 +677,72 @@ describe("on", () => { expect(error).toEqual(expected); }); }); + +describe("pin", () => { + it("emits the first value", async () => { + const input = new Subject(); + const result: unknown[] = []; + + input.pipe(pin()).subscribe((v) => result.push(v)); + input.next(1); + + expect(result).toEqual([1]); + }); + + it("filters repeated emissions", async () => { + const input = new Subject(); + const result: unknown[] = []; + + input.pipe(pin({ distinct: (p, c) => p == c })).subscribe((v) => result.push(v)); + input.next(1); + input.next(1); + + expect(result).toEqual([1]); + }); + + it("errors if multiple emissions occur", async () => { + const input = new Subject(); + let error: any = null!; + + input.pipe(pin()).subscribe({ + error: (e: unknown) => { + error = e; + }, + }); + input.next(1); + input.next(1); + + expect(error).toBeInstanceOf(Error); + expect(error.message).toMatch(/^unknown/); + }); + + it("names the pinned observables if multiple emissions occur", async () => { + const input = new Subject(); + let error: any = null!; + + input.pipe(pin({ name: () => "example" })).subscribe({ + error: (e: unknown) => { + error = e; + }, + }); + input.next(1); + input.next(1); + + expect(error).toBeInstanceOf(Error); + expect(error.message).toMatch(/^example/); + }); + + it("errors if indistinct emissions occur", async () => { + const input = new Subject(); + let error: any = null!; + + input + .pipe(pin({ distinct: (p, c) => p == c })) + .subscribe({ error: (e: unknown) => (error = e) }); + input.next(1); + input.next(2); + + expect(error).toBeInstanceOf(Error); + expect(error.message).toMatch(/^unknown/); + }); +}); diff --git a/libs/common/src/tools/rx.ts b/libs/common/src/tools/rx.ts index bff28ee52f8..ea397135581 100644 --- a/libs/common/src/tools/rx.ts +++ b/libs/common/src/tools/rx.ts @@ -19,6 +19,7 @@ import { concatMap, startWith, pairwise, + MonoTypeOperatorFunction, } from "rxjs"; /** Returns its input. */ @@ -213,3 +214,27 @@ export function on(watch$: Observable) { }), ); } + +/** Create an observable that emits the first value from the source and + * throws if the observable emits another value. + * @param options.name names the pin to make discovering failing observables easier + * @param options.distinct compares two emissions with each other to determine whether + * the second emission is a duplicate. When this is specified, duplicates are ignored. + * When this isn't specified, any emission after the first causes the pin to throw + * an error. + */ +export function pin(options?: { + name?: () => string; + distinct?: (previous: T, current: T) => boolean; +}): MonoTypeOperatorFunction { + return pipe( + options?.distinct ? distinctUntilChanged(options.distinct) : (i) => i, + map((value, index) => { + if (index > 0) { + throw new Error(`${options?.name?.() ?? "unknown"} observable should only emit one value.`); + } else { + return value; + } + }), + ); +} diff --git a/libs/common/src/tools/send/models/domain/send-access.ts b/libs/common/src/tools/send/models/domain/send-access.ts index dcc2d3ef426..588c4e84aa1 100644 --- a/libs/common/src/tools/send/models/domain/send-access.ts +++ b/libs/common/src/tools/send/models/domain/send-access.ts @@ -54,14 +54,7 @@ export class SendAccess extends Domain { async decrypt(key: SymmetricCryptoKey): Promise { const model = new SendAccessView(this); - await this.decryptObj( - model, - { - name: null, - }, - null, - key, - ); + await this.decryptObj(this, model, ["name"], null, key); switch (this.type) { case SendType.File: diff --git a/libs/common/src/tools/send/models/domain/send-file.ts b/libs/common/src/tools/send/models/domain/send-file.ts index 90e40f3959a..b8d0a265081 100644 --- a/libs/common/src/tools/send/models/domain/send-file.ts +++ b/libs/common/src/tools/send/models/domain/send-file.ts @@ -34,15 +34,13 @@ export class SendFile extends Domain { } async decrypt(key: SymmetricCryptoKey): Promise { - const view = await this.decryptObj( + return await this.decryptObj( + this, new SendFileView(this), - { - fileName: null, - }, + ["fileName"], null, key, ); - return view; } static fromJSON(obj: Jsonify) { diff --git a/libs/common/src/tools/send/models/domain/send-text.ts b/libs/common/src/tools/send/models/domain/send-text.ts index b17e3f769fb..df33e555896 100644 --- a/libs/common/src/tools/send/models/domain/send-text.ts +++ b/libs/common/src/tools/send/models/domain/send-text.ts @@ -30,11 +30,10 @@ export class SendText extends Domain { } decrypt(key: SymmetricCryptoKey): Promise { - return this.decryptObj( + return this.decryptObj( + this, new SendTextView(this), - { - text: null, - }, + ["text"], null, key, ); diff --git a/libs/common/src/tools/send/models/domain/send.spec.ts b/libs/common/src/tools/send/models/domain/send.spec.ts index acdb96f0e0d..79f6c03adc8 100644 --- a/libs/common/src/tools/send/models/domain/send.spec.ts +++ b/libs/common/src/tools/send/models/domain/send.spec.ts @@ -1,12 +1,12 @@ import { mock } from "jest-mock-extended"; -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import { UserKey } from "@bitwarden/common/types/key"; +import { KeyService } from "@bitwarden/key-management"; -import { KeyService } from "../../../../../../key-management/src/abstractions/key.service"; import { makeStaticByteArray, mockEnc } from "../../../../../spec"; -import { EncryptService } from "../../../../platform/abstractions/encrypt.service"; +import { EncryptService } from "../../../../key-management/crypto/abstractions/encrypt.service"; +import { SymmetricCryptoKey } from "../../../../platform/models/domain/symmetric-crypto-key"; import { ContainerService } from "../../../../platform/services/container.service"; +import { UserKey } from "../../../../types/key"; import { SendType } from "../../enums/send-type"; import { SendData } from "../data/send.data"; diff --git a/libs/common/src/tools/send/models/domain/send.ts b/libs/common/src/tools/send/models/domain/send.ts index 43115b65937..f12a0010fab 100644 --- a/libs/common/src/tools/send/models/domain/send.ts +++ b/libs/common/src/tools/send/models/domain/send.ts @@ -81,19 +81,13 @@ export class Send extends Domain { const sendKeyEncryptionKey = await keyService.getUserKey(); model.key = await encryptService.decryptToBytes(this.key, sendKeyEncryptionKey); model.cryptoKey = await keyService.makeSendKey(model.key); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // TODO: error? } - await this.decryptObj( - model, - { - name: null, - notes: null, - }, - null, - model.cryptoKey, - ); + await this.decryptObj(this, model, ["name", "notes"], null, model.cryptoKey); switch (this.type) { case SendType.File: diff --git a/libs/common/src/tools/send/services/send-api.service.abstraction.ts b/libs/common/src/tools/send/services/send-api.service.abstraction.ts index a6427824a64..570f3e746a0 100644 --- a/libs/common/src/tools/send/services/send-api.service.abstraction.ts +++ b/libs/common/src/tools/send/services/send-api.service.abstraction.ts @@ -22,11 +22,6 @@ export abstract class SendApiService { postSend: (request: SendRequest) => Promise; postFileTypeSend: (request: SendRequest) => Promise; postSendFile: (sendId: string, fileId: string, data: FormData) => Promise; - /** - * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. - * This method still exists for backward compatibility with old server versions. - */ - postSendFileLegacy: (data: FormData) => Promise; putSend: (id: string, request: SendRequest) => Promise; putSendRemovePassword: (id: string) => Promise; deleteSend: (id: string) => Promise; diff --git a/libs/common/src/tools/send/services/send-api.service.ts b/libs/common/src/tools/send/services/send-api.service.ts index ff71408bce3..f709553646f 100644 --- a/libs/common/src/tools/send/services/send-api.service.ts +++ b/libs/common/src/tools/send/services/send-api.service.ts @@ -5,7 +5,6 @@ import { FileUploadApiMethods, FileUploadService, } from "../../../platform/abstractions/file-upload/file-upload.service"; -import { Utils } from "../../../platform/misc/utils"; import { EncArrayBuffer } from "../../../platform/models/domain/enc-array-buffer"; import { SendType } from "../enums/send-type"; import { SendData } from "../models/data/send.data"; @@ -106,15 +105,6 @@ export class SendApiService implements SendApiServiceAbstraction { return this.apiService.send("POST", "/sends/" + sendId + "/file/" + fileId, data, true, false); } - /** - * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. - * This method still exists for backward compatibility with old server versions. - */ - async postSendFileLegacy(data: FormData): Promise { - const r = await this.apiService.send("POST", "/sends/file", data, true, true); - return new SendResponse(r); - } - async putSend(id: string, request: SendRequest): Promise { const r = await this.apiService.send("PUT", "/sends/" + id, request, true, true); return new SendResponse(r); @@ -173,9 +163,7 @@ export class SendApiService implements SendApiServiceAbstraction { this.generateMethods(uploadDataResponse, response), ); } catch (e) { - if (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) { - response = await this.legacyServerSendFileUpload(sendData, request); - } else if (e instanceof ErrorResponse) { + if (e instanceof ErrorResponse) { throw new Error((e as ErrorResponse).getSingleMessage()); } else { throw e; @@ -219,35 +207,4 @@ export class SendApiService implements SendApiServiceAbstraction { return this.deleteSend(sendId); }; } - - /** - * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. - * This method still exists for backward compatibility with old server versions. - */ - async legacyServerSendFileUpload( - sendData: [Send, EncArrayBuffer], - request: SendRequest, - ): Promise { - const fd = new FormData(); - try { - const blob = new Blob([sendData[1].buffer], { type: "application/octet-stream" }); - fd.append("model", JSON.stringify(request)); - fd.append("data", blob, sendData[0].file.fileName.encryptedString); - } catch (e) { - if (Utils.isNode && !Utils.isBrowser) { - fd.append("model", JSON.stringify(request)); - fd.append( - "data", - Buffer.from(sendData[1].buffer) as any, - { - filepath: sendData[0].file.fileName.encryptedString, - contentType: "application/octet-stream", - } as any, - ); - } else { - throw e; - } - } - return await this.postSendFileLegacy(fd); - } } diff --git a/libs/common/src/tools/send/services/send.service.spec.ts b/libs/common/src/tools/send/services/send.service.spec.ts index 5aca3a4b5c9..26cc0a46708 100644 --- a/libs/common/src/tools/send/services/send.service.spec.ts +++ b/libs/common/src/tools/send/services/send.service.spec.ts @@ -1,10 +1,8 @@ import { mock } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { SelfHostedEnvironment } from "@bitwarden/common/platform/services/default-environment.service"; +import { KeyService } from "@bitwarden/key-management"; -import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; import { FakeAccountService, FakeActiveUserState, @@ -12,13 +10,15 @@ import { awaitAsync, mockAccountServiceWith, } from "../../../../spec"; -import { EncryptService } from "../../../platform/abstractions/encrypt.service"; +import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service"; +import { EnvironmentService } from "../../../platform/abstractions/environment.service"; import { I18nService } from "../../../platform/abstractions/i18n.service"; import { KeyGenerationService } from "../../../platform/abstractions/key-generation.service"; import { Utils } from "../../../platform/misc/utils"; import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { ContainerService } from "../../../platform/services/container.service"; +import { SelfHostedEnvironment } from "../../../platform/services/default-environment.service"; import { UserId } from "../../../types/guid"; import { UserKey } from "../../../types/key"; import { SendType } from "../enums/send-type"; diff --git a/libs/common/src/tools/send/services/send.service.ts b/libs/common/src/tools/send/services/send.service.ts index 7021c942d44..1b5e5f6aa31 100644 --- a/libs/common/src/tools/send/services/send.service.ts +++ b/libs/common/src/tools/send/services/send.service.ts @@ -4,7 +4,7 @@ import { Observable, concatMap, distinctUntilChanged, firstValueFrom, map } from import { PBKDF2KdfConfig, KeyService } from "@bitwarden/key-management"; -import { EncryptService } from "../../../platform/abstractions/encrypt.service"; +import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service"; import { I18nService } from "../../../platform/abstractions/i18n.service"; import { KeyGenerationService } from "../../../platform/abstractions/key-generation.service"; import { Utils } from "../../../platform/misc/utils"; diff --git a/libs/common/src/tools/state/classified-format.spec.ts b/libs/common/src/tools/state/classified-format.spec.ts new file mode 100644 index 00000000000..77d39ba4cac --- /dev/null +++ b/libs/common/src/tools/state/classified-format.spec.ts @@ -0,0 +1,27 @@ +import { isClassifiedFormat } from "./classified-format"; + +describe("isClassifiedFormat", () => { + it("returns `false` when the argument is `null`", () => { + expect(isClassifiedFormat(null)).toEqual(false); + }); + + it.each([ + [{ id: true, secret: "" }], + [{ secret: "", disclosed: {} }], + [{ id: true, disclosed: {} }], + ])("returns `false` when the argument is missing a required member (=%p).", (value) => { + expect(isClassifiedFormat(value)).toEqual(false); + }); + + it("returns `false` when 'secret' is not a string", () => { + expect(isClassifiedFormat({ id: true, secret: false, disclosed: {} })).toEqual(false); + }); + + it("returns `false` when 'disclosed' is not an object", () => { + expect(isClassifiedFormat({ id: true, secret: "", disclosed: false })).toEqual(false); + }); + + it("returns `true` when the argument has a `secret`, `disclosed`, and `id`.", () => { + expect(isClassifiedFormat({ id: true, secret: "", disclosed: {} })).toEqual(true); + }); +}); diff --git a/libs/common/src/tools/state/classified-format.ts b/libs/common/src/tools/state/classified-format.ts index 26aca0197c5..ea738dad58e 100644 --- a/libs/common/src/tools/state/classified-format.ts +++ b/libs/common/src/tools/state/classified-format.ts @@ -21,5 +21,12 @@ export type ClassifiedFormat = { export function isClassifiedFormat( value: any, ): value is ClassifiedFormat { - return "id" in value && "secret" in value && "disclosed" in value; + return ( + !!value && + "id" in value && + "secret" in value && + "disclosed" in value && + typeof value.secret === "string" && + typeof value.disclosed === "object" + ); } diff --git a/libs/common/src/tools/state/object-key.ts b/libs/common/src/tools/state/object-key.ts index 260a2412b2c..c3f7cbfb02e 100644 --- a/libs/common/src/tools/state/object-key.ts +++ b/libs/common/src/tools/state/object-key.ts @@ -10,9 +10,6 @@ import { Classifier } from "./classifier"; * when you are performing your own encryption and decryption. * `classified` uses the `ClassifiedFormat` type as its format. * `secret-state` uses `Array` with a length of 1. - * @remarks - CAUTION! If your on-disk data is not in a correct format, - * the storage system treats the data as corrupt and returns your initial - * value. */ export type ObjectStorageFormat = "plain" | "classified" | "secret-state"; @@ -27,19 +24,54 @@ export type ObjectStorageFormat = "plain" | "classified" | "secret-state"; // options. Also allow swap between "classifier" and "classification"; the // latter is a list of properties/arguments to the specific classifier in-use. export type ObjectKey> = { + /** Type of data stored by this key; Object keys always use "object" targets. + * "object" - a singleton value. + * "list" - multiple values identified by their list index. + * "record" - multiple values identified by a uuid. + */ target: "object"; + + /** Identifies the stored state */ key: string; + + /** Defines the storage location and parameters for this state */ state: StateDefinition; + + /** Defines the visibility and encryption treatment for the stored state. + * Disclosed data is written as plain-text. Secret data is protected with + * the user key. + */ classifier: Classifier; + + /** Specifies the format of data written to storage. + * @remarks - CAUTION! If your on-disk data is not in a correct format, + * the storage system treats the data as corrupt and returns your initial + * value. + */ format: ObjectStorageFormat; + + /** customizes the behavior of the storage location */ options: UserKeyDefinitionOptions; + + /** When this is defined, empty data is replaced with a copy of the initial data. + * This causes the state to always be defined from the perspective of the + * subject's consumer. + */ initial?: State; + + /** For encrypted outputs, determines how much padding is applied to + * encoded inputs. When this isn't specified, each frame is 32 bytes + * long. + */ + frame?: number; }; +/** Performs a type inference that identifies object keys. */ export function isObjectKey(key: any): key is ObjectKey { return key.target === "object" && "format" in key && "classifier" in key; } +/** Converts an object key to a plaform-compatible `UserKeyDefinition`. */ export function toUserKeyDefinition( key: ObjectKey, ) { diff --git a/libs/common/src/tools/state/user-state-subject-dependencies.ts b/libs/common/src/tools/state/user-state-subject-dependencies.ts index 0ba842334bf..70e2aa244c4 100644 --- a/libs/common/src/tools/state/user-state-subject-dependencies.ts +++ b/libs/common/src/tools/state/user-state-subject-dependencies.ts @@ -1,20 +1,13 @@ -import { RequireExactlyOne, Simplify } from "type-fest"; +import { Simplify } from "type-fest"; -import { - Dependencies, - SingleUserDependency, - SingleUserEncryptorDependency, - WhenDependency, -} from "../dependencies"; +import { Account } from "../../auth/abstractions/account.service"; +import { Dependencies, BoundDependency, WhenDependency } from "../dependencies"; import { SubjectConstraintsDependency } from "./state-constraints-dependency"; /** dependencies accepted by the user state subject */ export type UserStateSubjectDependencies = Simplify< - RequireExactlyOne< - SingleUserDependency & SingleUserEncryptorDependency, - "singleUserEncryptor$" | "singleUserId$" - > & + BoundDependency<"account", Account> & Partial & Partial> & Partial> & { diff --git a/libs/common/src/tools/state/user-state-subject-dependency-provider.ts b/libs/common/src/tools/state/user-state-subject-dependency-provider.ts new file mode 100644 index 00000000000..2763aac4830 --- /dev/null +++ b/libs/common/src/tools/state/user-state-subject-dependency-provider.ts @@ -0,0 +1,18 @@ +import { Jsonify } from "type-fest"; + +import { StateProvider } from "../../platform/state"; +import { LegacyEncryptorProvider } from "../cryptography/legacy-encryptor-provider"; +import { SemanticLogger } from "../log"; + +/** Aggregates user state subject dependencies */ +export abstract class UserStateSubjectDependencyProvider { + /** Provides objects that encrypt and decrypt user and organization data */ + abstract encryptor: LegacyEncryptorProvider; + + /** Provides local object persistence */ + abstract state: StateProvider; + + // FIXME: remove `log` and inject the system provider into the USS instead + /** Provides semantic logging */ + abstract log: (_context: Jsonify) => SemanticLogger; +} diff --git a/libs/common/src/tools/state/user-state-subject.spec.ts b/libs/common/src/tools/state/user-state-subject.spec.ts index 6a50a1dd668..20acff17877 100644 --- a/libs/common/src/tools/state/user-state-subject.spec.ts +++ b/libs/common/src/tools/state/user-state-subject.spec.ts @@ -1,34 +1,61 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { BehaviorSubject, of, Subject } from "rxjs"; -import { GENERATOR_DISK, UserKeyDefinition } from "@bitwarden/common/platform/state"; -import { UserId } from "@bitwarden/common/types/guid"; - -import { awaitAsync, FakeSingleUserState, ObservableTracker } from "../../../spec"; +import { + awaitAsync, + FakeAccountService, + FakeStateProvider, + ObservableTracker, +} from "../../../spec"; +import { Account } from "../../auth/abstractions/account.service"; +import { GENERATOR_DISK, UserKeyDefinition } from "../../platform/state"; +import { UserId } from "../../types/guid"; +import { LegacyEncryptorProvider } from "../cryptography/legacy-encryptor-provider"; import { UserEncryptor } from "../cryptography/user-encryptor.abstraction"; -import { UserBound } from "../dependencies"; +import { disabledSemanticLoggerProvider } from "../log"; import { PrivateClassifier } from "../private-classifier"; import { StateConstraints } from "../types"; -import { ClassifiedFormat } from "./classified-format"; import { ObjectKey } from "./object-key"; import { UserStateSubject } from "./user-state-subject"; const SomeUser = "some user" as UserId; +const SomeAccount = { + id: SomeUser, + email: "someone@example.com", + emailVerified: true, + name: "Someone", +}; +const SomeAccount$ = new BehaviorSubject(SomeAccount); + +const SomeOtherAccount = { + id: "some other user" as UserId, + email: "someone@example.com", + emailVerified: true, + name: "Someone", +}; + type TestType = { foo: string }; const SomeKey = new UserKeyDefinition(GENERATOR_DISK, "TestKey", { deserializer: (d) => d as TestType, clearOn: [], }); +const SomeObjectKeyDefinition = new UserKeyDefinition(GENERATOR_DISK, "TestKey", { + deserializer: (d) => d as unknown, + clearOn: ["logout"], +}); + const SomeObjectKey = { target: "object", - key: "TestObjectKey", - state: GENERATOR_DISK, + key: SomeObjectKeyDefinition.key, + state: SomeObjectKeyDefinition.stateDefinition, classifier: new PrivateClassifier(), format: "classified", options: { deserializer: (d) => d as TestType, - clearOn: ["logout"], + clearOn: SomeObjectKeyDefinition.clearOn, }, } satisfies ObjectKey; @@ -46,6 +73,25 @@ const SomeEncryptor: UserEncryptor = { }, }; +const SomeAccountService = new FakeAccountService({ + [SomeUser]: SomeAccount, +}); + +const SomeStateProvider = new FakeStateProvider(SomeAccountService); + +const SomeProvider = { + encryptor: { + userEncryptor$: jest.fn(() => { + return new BehaviorSubject({ encryptor: SomeEncryptor, userId: SomeUser }).asObservable(); + }), + organizationEncryptor$() { + throw new Error("`organizationEncryptor$` should never be invoked."); + }, + } as LegacyEncryptorProvider, + state: SomeStateProvider, + log: disabledSemanticLoggerProvider, +}; + function fooMaxLength(maxLength: number): StateConstraints { return Object.freeze({ constraints: { foo: { maxLength } }, @@ -69,18 +115,21 @@ const DynamicFooMaxLength = Object.freeze({ }, }); +const SomeKeySomeUserInitialValue = Object.freeze({ foo: "init" }); + describe("UserStateSubject", () => { + beforeEach(async () => { + await SomeStateProvider.setUserState(SomeKey, SomeKeySomeUserInitialValue, SomeUser); + }); + describe("dependencies", () => { it("ignores repeated when$ emissions", async () => { // this test looks for `nextValue` because a subscription isn't necessary for // the subject to update - const initialValue: TestType = { foo: "init" }; - const state = new FakeSingleUserState(SomeUser, initialValue); - const singleUserId$ = new BehaviorSubject(SomeUser); const nextValue = jest.fn((_, next) => next); const when$ = new BehaviorSubject(true); - const subject = new UserStateSubject(SomeKey, () => state, { - singleUserId$, + const subject = new UserStateSubject(SomeKey, SomeProvider, { + account$: SomeAccount$, nextValue, when$, }); @@ -97,90 +146,64 @@ describe("UserStateSubject", () => { expect(nextValue).toHaveBeenCalledTimes(1); }); - it("ignores repeated singleUserId$ emissions", async () => { - // this test looks for `nextValue` because a subscription isn't necessary for - // the subject to update - const initialValue: TestType = { foo: "init" }; - const state = new FakeSingleUserState(SomeUser, initialValue); - const singleUserId$ = new BehaviorSubject(SomeUser); - const nextValue = jest.fn((_, next) => next); - const when$ = new BehaviorSubject(true); - const subject = new UserStateSubject(SomeKey, () => state, { - singleUserId$, - nextValue, - when$, + it("errors when account$ changes accounts", async () => { + const account$ = new BehaviorSubject(SomeAccount); + const subject = new UserStateSubject(SomeKey, SomeProvider, { + account$, + }); + let error: any = null; + subject.subscribe({ + error(e: unknown) { + error = e; + }, }); - // the interleaved await asyncs are only necessary b/c `nextValue` is called asynchronously - subject.next({ foo: "next" }); - await awaitAsync(); - singleUserId$.next(SomeUser); - await awaitAsync(); - singleUserId$.next(SomeUser); - singleUserId$.next(SomeUser); + account$.next(SomeOtherAccount); await awaitAsync(); - expect(nextValue).toHaveBeenCalledTimes(1); + expect(error).toBeInstanceOf(Error); + expect(error.message).toMatch(/UserStateSubject\(generator, TestKey\) \{ account\$ \}/); }); - it("ignores repeated singleUserEncryptor$ emissions", async () => { - // this test looks for `nextValue` because a subscription isn't necessary for - // the subject to update - const initialValue: TestType = { foo: "init" }; - const state = new FakeSingleUserState(SomeUser, initialValue); - const nextValue = jest.fn((_, next) => next); - const singleUserEncryptor$ = new BehaviorSubject({ userId: SomeUser, encryptor: null }); - const subject = new UserStateSubject(SomeKey, () => state, { - nextValue, - singleUserEncryptor$, - }); + it("waits for account$", async () => { + await SomeStateProvider.setUserState( + SomeObjectKeyDefinition, + { id: null, secret: '{"foo":"init"}', disclosed: {} } as unknown, + SomeUser, + ); + const account$ = new Subject(); + const subject = new UserStateSubject(SomeObjectKey, SomeProvider, { account$ }); - // the interleaved await asyncs are only necessary b/c `nextValue` is called asynchronously - subject.next({ foo: "next" }); - await awaitAsync(); - singleUserEncryptor$.next({ userId: SomeUser, encryptor: null }); - await awaitAsync(); - singleUserEncryptor$.next({ userId: SomeUser, encryptor: null }); - singleUserEncryptor$.next({ userId: SomeUser, encryptor: null }); + const results = [] as any[]; + subject.subscribe((v) => results.push(v)); + // precondition: no immediate emission upon subscribe + expect(results).toEqual([]); + + account$.next(SomeAccount); await awaitAsync(); - expect(nextValue).toHaveBeenCalledTimes(1); + expect(results).toEqual([{ foo: "decrypt(init)" }]); }); it("waits for constraints$", async () => { - const state = new FakeSingleUserState(SomeUser, { foo: "init" }); - const singleUserId$ = new BehaviorSubject(SomeUser); const constraints$ = new Subject>(); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$, constraints$ }); - const tracker = new ObservableTracker(subject); + const subject = new UserStateSubject(SomeKey, SomeProvider, { + account$: SomeAccount$, + constraints$, + }); + const results = [] as any[]; + subject.subscribe((v) => results.push(v)); constraints$.next(fooMaxLength(3)); - const [initResult] = await tracker.pauseUntilReceived(1); + await awaitAsync(); - expect(initResult).toEqual({ foo: "ini" }); - }); - - it("waits for singleUserEncryptor$", async () => { - const state = new FakeSingleUserState>>( - SomeUser, - { id: null, secret: '{"foo":"init"}', disclosed: {} }, - ); - const singleUserEncryptor$ = new Subject>(); - const subject = new UserStateSubject(SomeObjectKey, () => state, { singleUserEncryptor$ }); - const tracker = new ObservableTracker(subject); - - singleUserEncryptor$.next({ userId: SomeUser, encryptor: SomeEncryptor }); - const [initResult] = await tracker.pauseUntilReceived(1); - - expect(initResult).toEqual({ foo: "decrypt(init)" }); + expect(results).toEqual([{ foo: "ini" }]); }); }); describe("next", () => { it("emits the next value", async () => { - const state = new FakeSingleUserState(SomeUser, { foo: "init" }); - const singleUserId$ = new BehaviorSubject(SomeUser); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$ }); + const subject = new UserStateSubject(SomeKey, SomeProvider, { account$: SomeAccount$ }); const expected: TestType = { foo: "next" }; let actual: TestType = null; @@ -194,44 +217,39 @@ describe("UserStateSubject", () => { }); it("ceases emissions once complete", async () => { - const initialState = { foo: "init" }; - const state = new FakeSingleUserState(SomeUser, initialState); - const singleUserId$ = new BehaviorSubject(SomeUser); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$ }); - + const subject = new UserStateSubject(SomeKey, SomeProvider, { account$: SomeAccount$ }); let actual: TestType = null; subject.subscribe((value) => { actual = value; }); + await awaitAsync(); + subject.complete(); subject.next({ foo: "ignored" }); await awaitAsync(); - expect(actual).toEqual(initialState); + expect(actual).toEqual(SomeKeySomeUserInitialValue); }); it("evaluates shouldUpdate", async () => { - const initialValue: TestType = { foo: "init" }; - const state = new FakeSingleUserState(SomeUser, initialValue); - const singleUserId$ = new BehaviorSubject(SomeUser); const shouldUpdate = jest.fn(() => true); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$, shouldUpdate }); + const subject = new UserStateSubject(SomeKey, SomeProvider, { + account$: SomeAccount$, + shouldUpdate, + }); const nextVal: TestType = { foo: "next" }; subject.next(nextVal); await awaitAsync(); - expect(shouldUpdate).toHaveBeenCalledWith(initialValue, nextVal, null); + expect(shouldUpdate).toHaveBeenCalledWith(SomeKeySomeUserInitialValue, nextVal, null); }); it("evaluates shouldUpdate with a dependency", async () => { - const initialValue: TestType = { foo: "init" }; - const state = new FakeSingleUserState(SomeUser, initialValue); - const singleUserId$ = new BehaviorSubject(SomeUser); const shouldUpdate = jest.fn(() => true); const dependencyValue = { bar: "dependency" }; - const subject = new UserStateSubject(SomeKey, () => state, { - singleUserId$, + const subject = new UserStateSubject(SomeKey, SomeProvider, { + account$: SomeAccount$, shouldUpdate, dependencies$: of(dependencyValue), }); @@ -240,15 +258,19 @@ describe("UserStateSubject", () => { subject.next(nextVal); await awaitAsync(); - expect(shouldUpdate).toHaveBeenCalledWith(initialValue, nextVal, dependencyValue); + expect(shouldUpdate).toHaveBeenCalledWith( + SomeKeySomeUserInitialValue, + nextVal, + dependencyValue, + ); }); it("emits a value when shouldUpdate returns `true`", async () => { - const initialValue: TestType = { foo: "init" }; - const state = new FakeSingleUserState(SomeUser, initialValue); - const singleUserId$ = new BehaviorSubject(SomeUser); const shouldUpdate = jest.fn(() => true); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$, shouldUpdate }); + const subject = new UserStateSubject(SomeKey, SomeProvider, { + account$: SomeAccount$, + shouldUpdate, + }); const expected: TestType = { foo: "next" }; let actual: TestType = null; @@ -262,11 +284,11 @@ describe("UserStateSubject", () => { }); it("retains the current value when shouldUpdate returns `false`", async () => { - const initialValue: TestType = { foo: "init" }; - const state = new FakeSingleUserState(SomeUser, initialValue); - const singleUserId$ = new BehaviorSubject(SomeUser); const shouldUpdate = jest.fn(() => false); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$, shouldUpdate }); + const subject = new UserStateSubject(SomeKey, SomeProvider, { + account$: SomeAccount$, + shouldUpdate, + }); subject.next({ foo: "next" }); await awaitAsync(); @@ -275,31 +297,28 @@ describe("UserStateSubject", () => { actual = value; }); - expect(actual).toEqual(initialValue); + expect(actual).toEqual(SomeKeySomeUserInitialValue); }); it("evaluates nextValue", async () => { - const initialValue: TestType = { foo: "init" }; - const state = new FakeSingleUserState(SomeUser, initialValue); - const singleUserId$ = new BehaviorSubject(SomeUser); const nextValue = jest.fn((_, next) => next); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$, nextValue }); + const subject = new UserStateSubject(SomeKey, SomeProvider, { + account$: SomeAccount$, + nextValue, + }); const nextVal: TestType = { foo: "next" }; subject.next(nextVal); await awaitAsync(); - expect(nextValue).toHaveBeenCalledWith(initialValue, nextVal, null); + expect(nextValue).toHaveBeenCalledWith(SomeKeySomeUserInitialValue, nextVal, null); }); it("evaluates nextValue with a dependency", async () => { - const initialValue: TestType = { foo: "init" }; - const state = new FakeSingleUserState(SomeUser, initialValue); - const singleUserId$ = new BehaviorSubject(SomeUser); const nextValue = jest.fn((_, next) => next); const dependencyValue = { bar: "dependency" }; - const subject = new UserStateSubject(SomeKey, () => state, { - singleUserId$, + const subject = new UserStateSubject(SomeKey, SomeProvider, { + account$: SomeAccount$, nextValue, dependencies$: of(dependencyValue), }); @@ -308,19 +327,16 @@ describe("UserStateSubject", () => { subject.next(nextVal); await awaitAsync(); - expect(nextValue).toHaveBeenCalledWith(initialValue, nextVal, dependencyValue); + expect(nextValue).toHaveBeenCalledWith(SomeKeySomeUserInitialValue, nextVal, dependencyValue); }); it("evaluates nextValue when when$ is true", async () => { // this test looks for `nextValue` because a subscription isn't necessary for // the subject to update - const initialValue: TestType = { foo: "init" }; - const state = new FakeSingleUserState(SomeUser, initialValue); - const singleUserId$ = new BehaviorSubject(SomeUser); const nextValue = jest.fn((_, next) => next); const when$ = new BehaviorSubject(true); - const subject = new UserStateSubject(SomeKey, () => state, { - singleUserId$, + const subject = new UserStateSubject(SomeKey, SomeProvider, { + account$: SomeAccount$, nextValue, when$, }); @@ -335,13 +351,10 @@ describe("UserStateSubject", () => { it("waits to evaluate nextValue until when$ is true", async () => { // this test looks for `nextValue` because a subscription isn't necessary for // the subject to update. - const initialValue: TestType = { foo: "init" }; - const state = new FakeSingleUserState(SomeUser, initialValue); - const singleUserId$ = new BehaviorSubject(SomeUser); const nextValue = jest.fn((_, next) => next); const when$ = new BehaviorSubject(false); - const subject = new UserStateSubject(SomeKey, () => state, { - singleUserId$, + const subject = new UserStateSubject(SomeKey, SomeProvider, { + account$: SomeAccount$, nextValue, when$, }); @@ -356,62 +369,31 @@ describe("UserStateSubject", () => { expect(nextValue).toHaveBeenCalled(); }); - it("waits to evaluate `UserState.update` until singleUserId$ emits", async () => { - // this test looks for `nextMock` because a subscription isn't necessary for + it("waits to evaluate `UserState.update` until account$ emits", async () => { + // this test looks for `nextValue` because a subscription isn't necessary for // the subject to update. - const initialValue: TestType = { foo: "init" }; - const state = new FakeSingleUserState(SomeUser, initialValue); - const singleUserId$ = new Subject(); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$ }); + const account$ = new Subject(); + const nextValue = jest.fn((_, pending) => pending); + const subject = new UserStateSubject(SomeKey, SomeProvider, { account$, nextValue }); // precondition: subject doesn't update after `next` const nextVal: TestType = { foo: "next" }; subject.next(nextVal); await awaitAsync(); - expect(state.nextMock).not.toHaveBeenCalled(); + expect(nextValue).not.toHaveBeenCalled(); - singleUserId$.next(SomeUser); + account$.next(SomeAccount); await awaitAsync(); - expect(state.nextMock).toHaveBeenCalledWith({ - foo: "next", - // FIXME: don't leak this detail into the test - "$^$ALWAYS_UPDATE_KLUDGE_PROPERTY$^$": 0, - }); - }); - - it("waits to evaluate `UserState.update` until singleUserEncryptor$ emits", async () => { - const state = new FakeSingleUserState>>( - SomeUser, - { id: null, secret: '{"foo":"init"}', disclosed: null }, - ); - const singleUserEncryptor$ = new Subject>(); - const subject = new UserStateSubject(SomeObjectKey, () => state, { singleUserEncryptor$ }); - - // precondition: subject doesn't update after `next` - const nextVal: TestType = { foo: "next" }; - subject.next(nextVal); - await awaitAsync(); - expect(state.nextMock).not.toHaveBeenCalled(); - - singleUserEncryptor$.next({ userId: SomeUser, encryptor: SomeEncryptor }); - await awaitAsync(); - - const encrypted = { foo: "encrypt(next)" }; - expect(state.nextMock).toHaveBeenCalledWith({ - id: null, - secret: encrypted, - disclosed: null, - // FIXME: don't leak this detail into the test - "$^$ALWAYS_UPDATE_KLUDGE_PROPERTY$^$": 0, - }); + expect(nextValue).toHaveBeenCalledWith(SomeKeySomeUserInitialValue, { foo: "next" }, null); }); it("applies dynamic constraints", async () => { - const state = new FakeSingleUserState(SomeUser, { foo: "init" }); - const singleUserId$ = new BehaviorSubject(SomeUser); const constraints$ = new BehaviorSubject(DynamicFooMaxLength); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$, constraints$ }); + const subject = new UserStateSubject(SomeKey, SomeProvider, { + account$: SomeAccount$, + constraints$, + }); const tracker = new ObservableTracker(subject); const expected: TestType = { foo: "next" }; const emission = tracker.expectEmission(); @@ -423,10 +405,11 @@ describe("UserStateSubject", () => { }); it("applies constraints$ on next", async () => { - const state = new FakeSingleUserState(SomeUser, { foo: "init" }); - const singleUserId$ = new BehaviorSubject(SomeUser); const constraints$ = new BehaviorSubject(fooMaxLength(2)); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$, constraints$ }); + const subject = new UserStateSubject(SomeKey, SomeProvider, { + account$: SomeAccount$, + constraints$, + }); const tracker = new ObservableTracker(subject); subject.next({ foo: "next" }); @@ -436,10 +419,11 @@ describe("UserStateSubject", () => { }); it("applies latest constraints$ on next", async () => { - const state = new FakeSingleUserState(SomeUser, { foo: "init" }); - const singleUserId$ = new BehaviorSubject(SomeUser); const constraints$ = new BehaviorSubject(fooMaxLength(2)); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$, constraints$ }); + const subject = new UserStateSubject(SomeKey, SomeProvider, { + account$: SomeAccount$, + constraints$, + }); const tracker = new ObservableTracker(subject); constraints$.next(fooMaxLength(3)); @@ -450,10 +434,11 @@ describe("UserStateSubject", () => { }); it("waits for constraints$", async () => { - const state = new FakeSingleUserState(SomeUser, { foo: "init" }); - const singleUserId$ = new BehaviorSubject(SomeUser); const constraints$ = new Subject>(); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$, constraints$ }); + const subject = new UserStateSubject(SomeKey, SomeProvider, { + account$: SomeAccount$, + constraints$, + }); const results: any[] = []; subject.subscribe((r) => { results.push(r); @@ -469,10 +454,11 @@ describe("UserStateSubject", () => { }); it("uses the last-emitted value from constraints$ when constraints$ errors", async () => { - const state = new FakeSingleUserState(SomeUser, { foo: "init" }); - const singleUserId$ = new BehaviorSubject(SomeUser); const constraints$ = new BehaviorSubject(fooMaxLength(3)); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$, constraints$ }); + const subject = new UserStateSubject(SomeKey, SomeProvider, { + account$: SomeAccount$, + constraints$, + }); const tracker = new ObservableTracker(subject); constraints$.error({ some: "error" }); @@ -483,10 +469,11 @@ describe("UserStateSubject", () => { }); it("uses the last-emitted value from constraints$ when constraints$ completes", async () => { - const state = new FakeSingleUserState(SomeUser, { foo: "init" }); - const singleUserId$ = new BehaviorSubject(SomeUser); const constraints$ = new BehaviorSubject(fooMaxLength(3)); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$, constraints$ }); + const subject = new UserStateSubject(SomeKey, SomeProvider, { + account$: SomeAccount$, + constraints$, + }); const tracker = new ObservableTracker(subject); constraints$.complete(); @@ -499,9 +486,7 @@ describe("UserStateSubject", () => { describe("error", () => { it("emits errors", async () => { - const state = new FakeSingleUserState(SomeUser, { foo: "init" }); - const singleUserId$ = new BehaviorSubject(SomeUser); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$ }); + const subject = new UserStateSubject(SomeKey, SomeProvider, { account$: SomeAccount$ }); const expected: TestType = { foo: "error" }; let actual: TestType = null; @@ -517,10 +502,7 @@ describe("UserStateSubject", () => { }); it("ceases emissions once errored", async () => { - const initialState = { foo: "init" }; - const state = new FakeSingleUserState(SomeUser, initialState); - const singleUserId$ = new BehaviorSubject(SomeUser); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$ }); + const subject = new UserStateSubject(SomeKey, SomeProvider, { account$: SomeAccount$ }); let actual: TestType = null; subject.subscribe({ @@ -536,10 +518,7 @@ describe("UserStateSubject", () => { }); it("ceases emissions once complete", async () => { - const initialState = { foo: "init" }; - const state = new FakeSingleUserState(SomeUser, initialState); - const singleUserId$ = new BehaviorSubject(SomeUser); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$ }); + const subject = new UserStateSubject(SomeKey, SomeProvider, { account$: SomeAccount$ }); let shouldNotRun = false; subject.subscribe({ @@ -557,9 +536,7 @@ describe("UserStateSubject", () => { describe("complete", () => { it("emits completes", async () => { - const state = new FakeSingleUserState(SomeUser, { foo: "init" }); - const singleUserId$ = new BehaviorSubject(SomeUser); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$ }); + const subject = new UserStateSubject(SomeKey, SomeProvider, { account$: SomeAccount$ }); let actual = false; subject.subscribe({ @@ -574,10 +551,7 @@ describe("UserStateSubject", () => { }); it("ceases emissions once errored", async () => { - const initialState = { foo: "init" }; - const state = new FakeSingleUserState(SomeUser, initialState); - const singleUserId$ = new BehaviorSubject(SomeUser); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$ }); + const subject = new UserStateSubject(SomeKey, SomeProvider, { account$: SomeAccount$ }); let shouldNotRun = false; subject.subscribe({ @@ -595,10 +569,7 @@ describe("UserStateSubject", () => { }); it("ceases emissions once complete", async () => { - const initialState = { foo: "init" }; - const state = new FakeSingleUserState(SomeUser, initialState); - const singleUserId$ = new BehaviorSubject(SomeUser); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$ }); + const subject = new UserStateSubject(SomeKey, SomeProvider, { account$: SomeAccount$ }); let timesRun = 0; subject.subscribe({ @@ -616,10 +587,11 @@ describe("UserStateSubject", () => { describe("subscribe", () => { it("applies constraints$ on init", async () => { - const state = new FakeSingleUserState(SomeUser, { foo: "init" }); - const singleUserId$ = new BehaviorSubject(SomeUser); const constraints$ = new BehaviorSubject(fooMaxLength(2)); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$, constraints$ }); + const subject = new UserStateSubject(SomeKey, SomeProvider, { + account$: SomeAccount$, + constraints$, + }); const tracker = new ObservableTracker(subject); const [result] = await tracker.pauseUntilReceived(1); @@ -628,10 +600,11 @@ describe("UserStateSubject", () => { }); it("applies constraints$ on constraints$ emission", async () => { - const state = new FakeSingleUserState(SomeUser, { foo: "init" }); - const singleUserId$ = new BehaviorSubject(SomeUser); const constraints$ = new BehaviorSubject(fooMaxLength(2)); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$, constraints$ }); + const subject = new UserStateSubject(SomeKey, SomeProvider, { + account$: SomeAccount$, + constraints$, + }); const tracker = new ObservableTracker(subject); constraints$.next(fooMaxLength(1)); @@ -640,11 +613,9 @@ describe("UserStateSubject", () => { expect(result).toEqual({ foo: "i" }); }); - it("completes when singleUserId$ completes", async () => { - const initialValue: TestType = { foo: "init" }; - const state = new FakeSingleUserState(SomeUser, initialValue); - const singleUserId$ = new BehaviorSubject(SomeUser); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$ }); + it("completes when account$ completes", async () => { + const account$ = new BehaviorSubject(SomeAccount); + const subject = new UserStateSubject(SomeKey, SomeProvider, { account$ }); let actual = false; subject.subscribe({ @@ -652,38 +623,18 @@ describe("UserStateSubject", () => { actual = true; }, }); - singleUserId$.complete(); - await awaitAsync(); - - expect(actual).toBeTruthy(); - }); - - it("completes when singleUserId$ completes", async () => { - const state = new FakeSingleUserState>>( - SomeUser, - { id: null, secret: '{"foo":"init"}', disclosed: null }, - ); - const singleUserEncryptor$ = new Subject>(); - const subject = new UserStateSubject(SomeObjectKey, () => state, { singleUserEncryptor$ }); - - let actual = false; - subject.subscribe({ - complete: () => { - actual = true; - }, - }); - singleUserEncryptor$.complete(); + account$.complete(); await awaitAsync(); expect(actual).toBeTruthy(); }); it("completes when when$ completes", async () => { - const initialValue: TestType = { foo: "init" }; - const state = new FakeSingleUserState(SomeUser, initialValue); - const singleUserId$ = new BehaviorSubject(SomeUser); const when$ = new BehaviorSubject(true); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$, when$ }); + const subject = new UserStateSubject(SomeKey, SomeProvider, { + account$: SomeAccount$, + when$, + }); let actual = false; subject.subscribe({ @@ -700,12 +651,9 @@ describe("UserStateSubject", () => { // FIXME: add test for `this.state.catch` once `FakeSingleUserState` supports // simulated errors - it("errors when singleUserId$ changes", async () => { - const initialValue: TestType = { foo: "init" }; - const state = new FakeSingleUserState(SomeUser, initialValue); - const singleUserId$ = new BehaviorSubject(SomeUser); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$ }); - const errorUserId = "error" as UserId; + it("errors when account$ changes", async () => { + const account$ = new BehaviorSubject(SomeAccount); + const subject = new UserStateSubject(SomeKey, SomeProvider, { account$ }); let error = false; subject.subscribe({ @@ -713,39 +661,15 @@ describe("UserStateSubject", () => { error = e as any; }, }); - singleUserId$.next(errorUserId); + account$.next(SomeOtherAccount); await awaitAsync(); - expect(error).toEqual({ expectedUserId: SomeUser, actualUserId: errorUserId }); + expect(error).toBeInstanceOf(Error); }); - it("errors when singleUserEncryptor$ changes", async () => { - const state = new FakeSingleUserState>>( - SomeUser, - { id: null, secret: '{"foo":"init"}', disclosed: null }, - ); - const singleUserEncryptor$ = new Subject>(); - const subject = new UserStateSubject(SomeObjectKey, () => state, { singleUserEncryptor$ }); - const errorUserId = "error" as UserId; - - let error = false; - subject.subscribe({ - error: (e: unknown) => { - error = e as any; - }, - }); - singleUserEncryptor$.next({ userId: SomeUser, encryptor: SomeEncryptor }); - singleUserEncryptor$.next({ userId: errorUserId, encryptor: SomeEncryptor }); - await awaitAsync(); - - expect(error).toEqual({ expectedUserId: SomeUser, actualUserId: errorUserId }); - }); - - it("errors when singleUserId$ errors", async () => { - const initialValue: TestType = { foo: "init" }; - const state = new FakeSingleUserState(SomeUser, initialValue); - const singleUserId$ = new BehaviorSubject(SomeUser); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$ }); + it("errors when account$ errors", async () => { + const account$ = new BehaviorSubject(SomeAccount); + const subject = new UserStateSubject(SomeKey, SomeProvider, { account$ }); const expected = { error: "description" }; let actual = false; @@ -754,37 +678,18 @@ describe("UserStateSubject", () => { actual = e as any; }, }); - singleUserId$.error(expected); - await awaitAsync(); - - expect(actual).toEqual(expected); - }); - - it("errors when singleUserEncryptor$ errors", async () => { - const initialValue: TestType = { foo: "init" }; - const state = new FakeSingleUserState(SomeUser, initialValue); - const singleUserEncryptor$ = new Subject>(); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserEncryptor$ }); - const expected = { error: "description" }; - - let actual = false; - subject.subscribe({ - error: (e: unknown) => { - actual = e as any; - }, - }); - singleUserEncryptor$.error(expected); + account$.error(expected); await awaitAsync(); expect(actual).toEqual(expected); }); it("errors when when$ errors", async () => { - const initialValue: TestType = { foo: "init" }; - const state = new FakeSingleUserState(SomeUser, initialValue); - const singleUserId$ = new BehaviorSubject(SomeUser); const when$ = new BehaviorSubject(true); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$, when$ }); + const subject = new UserStateSubject(SomeKey, SomeProvider, { + account$: SomeAccount$, + when$, + }); const expected = { error: "description" }; let actual = false; @@ -800,21 +705,9 @@ describe("UserStateSubject", () => { }); }); - describe("userId", () => { - it("returns the userId to which the subject is bound", () => { - const state = new FakeSingleUserState(SomeUser, { foo: "init" }); - const singleUserId$ = new Subject(); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$ }); - - expect(subject.userId).toEqual(SomeUser); - }); - }); - describe("withConstraints$", () => { it("emits the next value with an empty constraint", async () => { - const state = new FakeSingleUserState(SomeUser, { foo: "init" }); - const singleUserId$ = new BehaviorSubject(SomeUser); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$ }); + const subject = new UserStateSubject(SomeKey, SomeProvider, { account$: SomeAccount$ }); const tracker = new ObservableTracker(subject.withConstraints$); const expected: TestType = { foo: "next" }; const emission = tracker.expectEmission(); @@ -827,25 +720,23 @@ describe("UserStateSubject", () => { }); it("ceases emissions once the subject completes", async () => { - const initialState = { foo: "init" }; - const state = new FakeSingleUserState(SomeUser, initialState); - const singleUserId$ = new BehaviorSubject(SomeUser); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$ }); + const subject = new UserStateSubject(SomeKey, SomeProvider, { account$: SomeAccount$ }); const tracker = new ObservableTracker(subject.withConstraints$); subject.complete(); subject.next({ foo: "ignored" }); const [result] = await tracker.pauseUntilReceived(1); - expect(result.state).toEqual(initialState); + expect(result.state).toEqual(SomeKeySomeUserInitialValue); expect(tracker.emissions.length).toEqual(1); }); it("emits constraints$ on constraints$ emission", async () => { - const state = new FakeSingleUserState(SomeUser, { foo: "init" }); - const singleUserId$ = new BehaviorSubject(SomeUser); const constraints$ = new BehaviorSubject(fooMaxLength(2)); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$, constraints$ }); + const subject = new UserStateSubject(SomeKey, SomeProvider, { + account$: SomeAccount$, + constraints$, + }); const tracker = new ObservableTracker(subject.withConstraints$); const expected = fooMaxLength(1); const emission = tracker.expectEmission(); @@ -858,10 +749,11 @@ describe("UserStateSubject", () => { }); it("emits dynamic constraints", async () => { - const state = new FakeSingleUserState(SomeUser, { foo: "init" }); - const singleUserId$ = new BehaviorSubject(SomeUser); const constraints$ = new BehaviorSubject(DynamicFooMaxLength); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$, constraints$ }); + const subject = new UserStateSubject(SomeKey, SomeProvider, { + account$: SomeAccount$, + constraints$, + }); const tracker = new ObservableTracker(subject.withConstraints$); const expected: TestType = { foo: "next" }; const emission = tracker.expectEmission(); @@ -874,11 +766,12 @@ describe("UserStateSubject", () => { }); it("emits constraints$ on next", async () => { - const state = new FakeSingleUserState(SomeUser, { foo: "init" }); - const singleUserId$ = new BehaviorSubject(SomeUser); const expected = fooMaxLength(2); const constraints$ = new BehaviorSubject(expected); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$, constraints$ }); + const subject = new UserStateSubject(SomeKey, SomeProvider, { + account$: SomeAccount$, + constraints$, + }); const tracker = new ObservableTracker(subject.withConstraints$); const emission = tracker.expectEmission(); @@ -890,10 +783,11 @@ describe("UserStateSubject", () => { }); it("emits the latest constraints$ on next", async () => { - const state = new FakeSingleUserState(SomeUser, { foo: "init" }); - const singleUserId$ = new BehaviorSubject(SomeUser); const constraints$ = new BehaviorSubject(fooMaxLength(2)); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$, constraints$ }); + const subject = new UserStateSubject(SomeKey, SomeProvider, { + account$: SomeAccount$, + constraints$, + }); const tracker = new ObservableTracker(subject.withConstraints$); const expected = fooMaxLength(3); constraints$.next(expected); @@ -907,10 +801,11 @@ describe("UserStateSubject", () => { }); it("waits for constraints$", async () => { - const state = new FakeSingleUserState(SomeUser, { foo: "init" }); - const singleUserId$ = new BehaviorSubject(SomeUser); const constraints$ = new Subject>(); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$, constraints$ }); + const subject = new UserStateSubject(SomeKey, SomeProvider, { + account$: SomeAccount$, + constraints$, + }); const tracker = new ObservableTracker(subject.withConstraints$); const expected = fooMaxLength(3); @@ -924,11 +819,12 @@ describe("UserStateSubject", () => { }); it("emits the last-emitted value from constraints$ when constraints$ errors", async () => { - const state = new FakeSingleUserState(SomeUser, { foo: "init" }); - const singleUserId$ = new BehaviorSubject(SomeUser); const expected = fooMaxLength(3); const constraints$ = new BehaviorSubject(expected); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$, constraints$ }); + const subject = new UserStateSubject(SomeKey, SomeProvider, { + account$: SomeAccount$, + constraints$, + }); const tracker = new ObservableTracker(subject.withConstraints$); constraints$.error({ some: "error" }); @@ -940,11 +836,12 @@ describe("UserStateSubject", () => { }); it("emits the last-emitted value from constraints$ when constraints$ completes", async () => { - const state = new FakeSingleUserState(SomeUser, { foo: "init" }); - const singleUserId$ = new BehaviorSubject(SomeUser); const expected = fooMaxLength(3); const constraints$ = new BehaviorSubject(expected); - const subject = new UserStateSubject(SomeKey, () => state, { singleUserId$, constraints$ }); + const subject = new UserStateSubject(SomeKey, SomeProvider, { + account$: SomeAccount$, + constraints$, + }); const tracker = new ObservableTracker(subject.withConstraints$); constraints$.complete(); diff --git a/libs/common/src/tools/state/user-state-subject.ts b/libs/common/src/tools/state/user-state-subject.ts index d508cf71663..dd88ec2fb20 100644 --- a/libs/common/src/tools/state/user-state-subject.ts +++ b/libs/common/src/tools/state/user-state-subject.ts @@ -24,15 +24,17 @@ import { withLatestFrom, scan, skip, + shareReplay, + tap, + switchMap, } from "rxjs"; -import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; -import { SingleUserState, UserKeyDefinition } from "@bitwarden/common/platform/state"; -import { UserId } from "@bitwarden/common/types/guid"; - +import { Account } from "../../auth/abstractions/account.service"; +import { EncString } from "../../platform/models/domain/enc-string"; +import { SingleUserState, UserKeyDefinition } from "../../platform/state"; import { UserEncryptor } from "../cryptography/user-encryptor.abstraction"; -import { UserBound } from "../dependencies"; -import { anyComplete, errorOnChange, ready, withLatestReady } from "../rx"; +import { SemanticLogger } from "../log"; +import { anyComplete, pin, ready, withLatestReady } from "../rx"; import { Constraints, SubjectConstraints, WithConstraints } from "../types"; import { ClassifiedFormat, isClassifiedFormat } from "./classified-format"; @@ -40,6 +42,7 @@ import { unconstrained$ } from "./identity-state-constraint"; import { isObjectKey, ObjectKey, toUserKeyDefinition } from "./object-key"; import { isDynamic } from "./state-constraints-dependency"; import { UserStateSubjectDependencies } from "./user-state-subject-dependencies"; +import { UserStateSubjectDependencyProvider } from "./user-state-subject-dependency-provider"; type Constrained = { constraints: Readonly>; state: State }; @@ -60,6 +63,9 @@ type Constrained = { constraints: Readonly>; state: St // update b/c their IVs change. const ALWAYS_UPDATE_KLUDGE = "$^$ALWAYS_UPDATE_KLUDGE_PROPERTY$^$"; +/** Default frame size for data packing */ +const DEFAULT_FRAME_SIZE = 32; + /** * Adapt a state provider to an rxjs subject. * @@ -75,7 +81,7 @@ const ALWAYS_UPDATE_KLUDGE = "$^$ALWAYS_UPDATE_KLUDGE_PROPERTY$^$"; export class UserStateSubject< State extends object, Secret = State, - Disclosed = never, + Disclosed = Record, Dependencies = null, > extends Observable @@ -91,12 +97,12 @@ export class UserStateSubject< * this becomes true. When this occurs, only the last-received update * is applied. The blocked update is kept in memory. It does not persist * to disk. - * @param dependencies.singleUserId$ writes block until the singleUserId$ + * @param dependencies.account$ writes block until the account$ * is available. */ constructor( private key: UserKeyDefinition | ObjectKey, - getState: (key: UserKeyDefinition) => SingleUserState, + private providers: UserStateSubjectDependencyProvider, private context: UserStateSubjectDependencies, ) { super(); @@ -105,42 +111,60 @@ export class UserStateSubject< // classification and encryption only supported with `ObjectKey` this.objectKey = this.key; this.stateKey = toUserKeyDefinition(this.key); - this.state = getState(this.stateKey); } else { // raw state access granted with `UserKeyDefinition` this.objectKey = null; this.stateKey = this.key as UserKeyDefinition; - this.state = getState(this.stateKey); } + this.log = this.providers.log({ + contextId: this.contextId, + type: "UserStateSubject", + storage: { + state: this.stateKey.stateDefinition.name, + key: this.stateKey.key, + }, + }); + // normalize dependencies const when$ = (this.context.when$ ?? new BehaviorSubject(true)).pipe(distinctUntilChanged()); + const account$ = context.account$.pipe( + pin({ + name: () => `${this.contextId} { account$ }`, + distinct(prev, current) { + return prev.id === current.id; + }, + }), + shareReplay({ refCount: true, bufferSize: 1 }), + ); + const encryptor$ = this.encryptor(account$); + const constraints$ = (this.context.constraints$ ?? unconstrained$()).pipe( + catchError((e: unknown) => { + this.log.error(e as object, "constraints$ dependency failed; using last-known constraints"); + return EMPTY; + }), + shareReplay({ refCount: true, bufferSize: 1 }), + ); + const dependencies$ = ( + this.context.dependencies$ ?? new BehaviorSubject(null) + ).pipe(shareReplay({ refCount: true, bufferSize: 1 })); - // manage dependencies through replay subjects since `UserStateSubject` - // reads them in multiple places - const encryptor$ = new ReplaySubject(1); - const { singleUserId$, singleUserEncryptor$ } = this.context; - this.encryptor(singleUserEncryptor$ ?? singleUserId$).subscribe(encryptor$); - - const constraints$ = new ReplaySubject>(1); - (this.context.constraints$ ?? unconstrained$()) - .pipe( - // FIXME: this should probably log that an error occurred - catchError(() => EMPTY), - ) - .subscribe(constraints$); - - const dependencies$ = new ReplaySubject(1); - if (this.context.dependencies$) { - this.context.dependencies$.subscribe(dependencies$); - } else { - dependencies$.next(null); - } + // load state once the account becomes available + const userState$ = account$.pipe( + tap((account) => this.log.debug({ accountId: account.id }, "loading user state")), + map((account) => this.providers.state.getUser(account.id, this.stateKey)), + shareReplay({ refCount: true, bufferSize: 1 }), + ); // wire output before input so that output normalizes the current state // before any `next` value is processed - this.outputSubscription = this.state.state$ - .pipe(this.declassify(encryptor$), this.adjust(combineLatestWith(constraints$))) + this.outputSubscription = userState$ + .pipe( + switchMap((userState) => userState.state$), + this.declassify(encryptor$), + this.adjust(combineLatestWith(constraints$)), + takeUntil(anyComplete(account$)), + ) .subscribe(this.output); const last$ = new ReplaySubject(1); @@ -169,45 +193,42 @@ export class UserStateSubject< // // FIXME: this should probably timeout when a lock occurs this.inputSubscription = updates$ - .pipe(this.classify(encryptor$), takeUntil(anyComplete([when$, this.input, encryptor$]))) + .pipe( + this.classify(encryptor$), + withLatestFrom(userState$), + takeUntil(anyComplete([when$, this.input, encryptor$])), + ) .subscribe({ - next: (state) => this.onNext(state), + next: ([input, state]) => this.onNext(input, state), error: (e: unknown) => this.onError(e), complete: () => this.onComplete(), }); } - private stateKey: UserKeyDefinition; - private objectKey: ObjectKey; + private get contextId() { + return `UserStateSubject(${this.stateKey.stateDefinition.name}, ${this.stateKey.key})`; + } - private encryptor( - singleUserEncryptor$: Observable | UserId>, - ): Observable { - return singleUserEncryptor$.pipe( - // normalize inputs - map((maybe): UserBound<"encryptor", UserEncryptor> => { - if (typeof maybe === "object" && "encryptor" in maybe) { - return maybe; - } else if (typeof maybe === "string") { - return { encryptor: null, userId: maybe as UserId }; - } else { - throw new Error(`Invalid encryptor input received for ${this.key.key}.`); - } - }), - // fail the stream if the state desyncs from the bound userId - errorOnChange( - ({ userId }) => userId, - (expectedUserId, actualUserId) => ({ expectedUserId, actualUserId }), - ), - // reduce emissions to when encryptor changes + private readonly log: SemanticLogger; + + private readonly stateKey: UserKeyDefinition; + private readonly objectKey: ObjectKey; + + private encryptor(account$: Observable): Observable { + const singleUserId$ = account$.pipe(map((account) => account.id)); + const frameSize = this.objectKey?.frame ?? DEFAULT_FRAME_SIZE; + const encryptor$ = this.providers.encryptor.userEncryptor$(frameSize, { singleUserId$ }).pipe( + tap(() => this.log.debug("encryptor constructed")), map(({ encryptor }) => encryptor), - distinctUntilChanged(), + shareReplay({ refCount: true, bufferSize: 1 }), ); + return encryptor$; } private when(when$: Observable): OperatorFunction { return pipe( combineLatestWith(when$.pipe(distinctUntilChanged())), + tap(([_, when]) => this.log.debug({ when }, "when status")), filter(([_, when]) => !!when), map(([input]) => input), ); @@ -222,7 +243,7 @@ export class UserStateSubject< // `init$` becomes the accumulator for `scan` init$.pipe( first(), - map((init) => [init, null] as const), + map((init) => [init, null] as [State, Dependencies]), ), input$.pipe( map((constrained) => constrained.state), @@ -235,9 +256,10 @@ export class UserStateSubject< if (shouldUpdate) { // actual update const next = this.context.nextValue?.(prev, pending, dependencies) ?? pending; - return [next, dependencies]; + return [next, dependencies] as const; } else { // false update + this.log.debug("shouldUpdate prevented write"); return [prev, null]; } }), @@ -259,20 +281,22 @@ export class UserStateSubject< // * `input` needs to wait until a message flows through the pipe withConstraints, map(([loadedState, constraints]) => { - // bypass nulls if (!loadedState && !this.objectKey?.initial) { + this.log.debug("no value; bypassing adjustment"); return { constraints: {} as Constraints, state: null, } satisfies Constrained; } + this.log.debug("adjusting"); const unconstrained = loadedState ?? structuredClone(this.objectKey.initial); const calibration = isDynamic(constraints) ? constraints.calibrate(unconstrained) : constraints; const adjusted = calibration.adjust(unconstrained); + this.log.debug("adjusted"); return { constraints: calibration.constraints, state: adjusted, @@ -287,11 +311,14 @@ export class UserStateSubject< return pipe( combineLatestWith(constraints$), map(([loadedState, constraints]) => { + this.log.debug("fixing"); + const calibration = isDynamic(constraints) ? constraints.calibrate(loadedState) : constraints; const fixed = calibration.fix(loadedState); + this.log.debug("fixed"); return { constraints: calibration.constraints, state: fixed, @@ -303,6 +330,7 @@ export class UserStateSubject< private declassify(encryptor$: Observable): OperatorFunction { // short-circuit if they key lacks encryption support if (!this.objectKey || this.objectKey.format === "plain") { + this.log.debug("key uses plain format; bypassing declassification"); return (input$) => input$ as Observable; } @@ -313,9 +341,12 @@ export class UserStateSubject< concatMap(async ([input, encryptor]) => { // pass through null values if (input === null || input === undefined) { + this.log.debug("no value; bypassing declassification"); return null; } + this.log.debug("declassifying"); + // decrypt classified data const { secret, disclosed } = input; const encrypted = EncString.fromJSON(secret); @@ -325,6 +356,7 @@ export class UserStateSubject< const declassified = this.objectKey.classifier.declassify(disclosed, decryptedSecret); const state = this.objectKey.options.deserializer(declassified); + this.log.debug("declassified"); return state; }), ); @@ -339,6 +371,7 @@ export class UserStateSubject< if (this.objectKey && this.objectKey.format === "classified") { return map((input) => { if (!isClassifiedFormat(input)) { + this.log.warn("classified data must be in classified format; dropping"); return null; } @@ -350,11 +383,13 @@ export class UserStateSubject< if (this.objectKey && this.objectKey.format === "secret-state") { return map((input) => { if (!Array.isArray(input)) { + this.log.warn("secret-state requires array formatting; dropping"); return null; } const [unwrapped] = input; if (!isClassifiedFormat(unwrapped)) { + this.log.warn("unwrapped secret-state must be in classified format; dropping"); return null; } @@ -362,13 +397,14 @@ export class UserStateSubject< }); } - throw new Error(`unsupported serialization format: ${this.objectKey.format}`); + this.log.panic({ format: this.objectKey.format }, "unsupported serialization format"); } private classify(encryptor$: Observable): OperatorFunction { // short-circuit if they key lacks encryption support; `encryptor` is // readied to preserve `dependencies.singleUserId$` emission contract if (!this.objectKey || this.objectKey.format === "plain") { + this.log.debug("key uses plain format; bypassing classification"); return pipe( ready(encryptor$), map((input) => input as unknown), @@ -381,9 +417,12 @@ export class UserStateSubject< concatMap(async ([input, encryptor]) => { // fail fast if there's no value if (input === null || input === undefined) { + this.log.debug("no value; bypassing classification"); return null; } + this.log.debug("classifying"); + // split data by classification level const serialized = JSON.parse(JSON.stringify(input)); const classified = this.objectKey.classifier.classify(serialized); @@ -399,6 +438,7 @@ export class UserStateSubject< disclosed: classified.disclosed, } satisfies ClassifiedFormat; + this.log.debug("classified"); // deliberate type erasure; the type is restored during `declassify` return envelope as ClassifiedFormat; }), @@ -417,13 +457,7 @@ export class UserStateSubject< return map((input) => [input] as unknown); } - throw new Error(`unsupported serialization format: ${this.objectKey.format}`); - } - - /** The userId to which the subject is bound. - */ - get userId() { - return this.state.userId; + this.log.panic({ format: this.objectKey.format }, "unsupported serialization format"); } next(value: State) { @@ -450,7 +484,6 @@ export class UserStateSubject< // if greater efficiency becomes desirable, consider implementing // `SubjectLike` directly private input = new ReplaySubject(1); - private state: SingleUserState; private readonly output = new ReplaySubject>(1); /** A stream containing settings and their last-applied constraints. */ @@ -463,9 +496,11 @@ export class UserStateSubject< private counter = 0; - private onNext(value: unknown) { - this.state + private onNext(value: unknown, state: SingleUserState) { + state .update(() => { + this.log.debug("updating"); + if (typeof value === "object") { // related: ALWAYS_UPDATE_KLUDGE FIXME const counter = this.counter++; @@ -473,17 +508,22 @@ export class UserStateSubject< this.counter = 0; } - const kludge = value as any; + const kludge = { ...value } as any; kludge[ALWAYS_UPDATE_KLUDGE] = counter; } + this.log.debug("updated"); return value; }) - .catch((e: any) => this.onError(e)); + .catch((e: any) => { + this.log.error(e as object, "updating failed"); + this.onError(e); + }); } private onError(value: any) { if (!this.isDisposed) { + this.log.debug(value, "forwarding error to subscribers"); this.output.error(value); } @@ -504,6 +544,8 @@ export class UserStateSubject< private dispose() { if (!this.isDisposed) { + this.log.debug("disposing"); + // clean up internal subscriptions this.inputSubscription?.unsubscribe(); this.outputSubscription?.unsubscribe(); @@ -512,6 +554,8 @@ export class UserStateSubject< // drop input to ensure its value is removed from memory this.input = null; + + this.log.debug("disposed"); } } } diff --git a/libs/common/src/tools/util.ts b/libs/common/src/tools/util.ts new file mode 100644 index 00000000000..9a3a14c1c83 --- /dev/null +++ b/libs/common/src/tools/util.ts @@ -0,0 +1,19 @@ +/** Recursively freeze an object's own keys + * @param value the value to freeze + * @returns `value` + * @remarks this function is derived from MDN's `deepFreeze`, which + * has been committed to the public domain. + */ +export function deepFreeze(value: T): Readonly { + const keys = Reflect.ownKeys(value) as (keyof T)[]; + + for (const key of keys) { + const own = value[key]; + + if ((own && typeof own === "object") || typeof own === "function") { + deepFreeze(own); + } + } + + return Object.freeze(value); +} diff --git a/libs/common/src/types/guid.ts b/libs/common/src/types/guid.ts index 59dbe28f907..7a0ec4c1d58 100644 --- a/libs/common/src/types/guid.ts +++ b/libs/common/src/types/guid.ts @@ -10,3 +10,5 @@ export type PolicyId = Opaque; export type CipherId = Opaque; export type SendId = Opaque; export type IndexedEntityId = Opaque; +export type SecurityTaskId = Opaque; +export type NotificationId = Opaque; diff --git a/libs/common/src/vault/abstractions/cipher.service.ts b/libs/common/src/vault/abstractions/cipher.service.ts index 068b2fc52d9..1e4275ff89b 100644 --- a/libs/common/src/vault/abstractions/cipher.service.ts +++ b/libs/common/src/vault/abstractions/cipher.service.ts @@ -2,7 +2,6 @@ // @ts-strict-ignore import { Observable } from "rxjs"; -import { LocalData } from "@bitwarden/common/vault/models/data/local.data"; import { UserKeyRotationDataProvider } from "@bitwarden/key-management"; import { UriMatchStrategySetting } from "../../models/domain/domain-service"; @@ -11,6 +10,7 @@ import { CipherId, CollectionId, OrganizationId, UserId } from "../../types/guid import { UserKey } from "../../types/key"; import { CipherType } from "../enums/cipher-type"; import { CipherData } from "../models/data/cipher.data"; +import { LocalData } from "../models/data/local.data"; import { Cipher } from "../models/domain/cipher"; import { Field } from "../models/domain/field"; import { CipherWithIdRequest } from "../models/request/cipher-with-id.request"; @@ -19,57 +19,70 @@ import { FieldView } from "../models/view/field.view"; import { AddEditCipherInfo } from "../types/add-edit-cipher-info"; export abstract class CipherService implements UserKeyRotationDataProvider { - cipherViews$: Observable; - ciphers$: Observable>; - localData$: Observable>; + abstract cipherViews$(userId: UserId): Observable; + abstract ciphers$(userId: UserId): Observable>; + abstract localData$(userId: UserId): Observable>; /** * An observable monitoring the add/edit cipher info saved to memory. */ - addEditCipherInfo$: Observable; + abstract addEditCipherInfo$(userId: UserId): Observable; /** * Observable that emits an array of cipherViews that failed to decrypt. Does not emit until decryption has completed. * * An empty array indicates that all ciphers were successfully decrypted. */ - failedToDecryptCiphers$: Observable; - clearCache: (userId?: string) => Promise; - encrypt: ( + abstract failedToDecryptCiphers$(userId: UserId): Observable; + abstract clearCache(userId: UserId): Promise; + abstract encrypt( model: CipherView, userId: UserId, keyForEncryption?: SymmetricCryptoKey, keyForCipherKeyDecryption?: SymmetricCryptoKey, originalCipher?: Cipher, - ) => Promise; - encryptFields: (fieldsModel: FieldView[], key: SymmetricCryptoKey) => Promise; - encryptField: (fieldModel: FieldView, key: SymmetricCryptoKey) => Promise; - get: (id: string) => Promise; - getAll: () => Promise; - getAllDecrypted: () => Promise; - getAllDecryptedForGrouping: (groupingId: string, folder?: boolean) => Promise; - getAllDecryptedForUrl: ( + ): Promise; + abstract encryptFields(fieldsModel: FieldView[], key: SymmetricCryptoKey): Promise; + abstract encryptField(fieldModel: FieldView, key: SymmetricCryptoKey): Promise; + abstract get(id: string, userId: UserId): Promise; + abstract getAll(userId: UserId): Promise; + abstract getAllDecrypted(userId: UserId): Promise; + abstract getAllDecryptedForGrouping( + groupingId: string, + userId: UserId, + folder?: boolean, + ): Promise; + abstract getAllDecryptedForUrl( url: string, + userId: UserId, includeOtherTypes?: CipherType[], defaultMatch?: UriMatchStrategySetting, - ) => Promise; - filterCiphersForUrl: ( + ): Promise; + abstract filterCiphersForUrl( ciphers: CipherView[], url: string, includeOtherTypes?: CipherType[], defaultMatch?: UriMatchStrategySetting, - ) => Promise; - getAllFromApiForOrganization: (organizationId: string) => Promise; + ): Promise; + abstract getAllFromApiForOrganization(organizationId: string): Promise; /** * Gets ciphers belonging to the specified organization that the user has explicit collection level access to. * Ciphers that are not assigned to any collections are only included for users with admin access. */ - getManyFromApiForOrganization: (organizationId: string) => Promise; - getLastUsedForUrl: (url: string, autofillOnPageLoad: boolean) => Promise; - getLastLaunchedForUrl: (url: string, autofillOnPageLoad: boolean) => Promise; - getNextCipherForUrl: (url: string) => Promise; - updateLastUsedIndexForUrl: (url: string) => void; - updateLastUsedDate: (id: string) => Promise; - updateLastLaunchedDate: (id: string) => Promise; - saveNeverDomain: (domain: string) => Promise; + abstract getManyFromApiForOrganization(organizationId: string): Promise; + abstract getLastUsedForUrl( + url: string, + userId: UserId, + autofillOnPageLoad: boolean, + ): Promise; + abstract getLastLaunchedForUrl( + url: string, + userId: UserId, + autofillOnPageLoad: boolean, + ): Promise; + abstract getNextCipherForUrl(url: string, userId: UserId): Promise; + abstract updateLastUsedIndexForUrl(url: string): void; + abstract updateLastUsedDate(id: string, userId: UserId): Promise; + abstract updateLastLaunchedDate(id: string, userId: UserId): Promise; + abstract saveNeverDomain(domain: string): Promise; /** * Create a cipher with the server * @@ -78,7 +91,7 @@ export abstract class CipherService implements UserKeyRotationDataProvider Promise; + abstract createWithServer(cipher: Cipher, orgAdmin?: boolean): Promise; /** * Update a cipher with the server * @param cipher The cipher to update @@ -87,88 +100,105 @@ export abstract class CipherService implements UserKeyRotationDataProvider Promise; - shareWithServer: ( + abstract updateWithServer( + cipher: Cipher, + orgAdmin?: boolean, + isNotClone?: boolean, + ): Promise; + abstract shareWithServer( cipher: CipherView, organizationId: string, collectionIds: string[], userId: UserId, - ) => Promise; - shareManyWithServer: ( + ): Promise; + abstract shareManyWithServer( ciphers: CipherView[], organizationId: string, collectionIds: string[], userId: UserId, - ) => Promise; - saveAttachmentWithServer: ( + ): Promise; + abstract saveAttachmentWithServer( cipher: Cipher, unencryptedFile: any, userId: UserId, admin?: boolean, - ) => Promise; - saveAttachmentRawWithServer: ( + ): Promise; + abstract saveAttachmentRawWithServer( cipher: Cipher, filename: string, data: ArrayBuffer, userId: UserId, admin?: boolean, - ) => Promise; + ): Promise; /** * Save the collections for a cipher with the server * * @param cipher The cipher to save collections for + * @param userId The user ID * * @returns A promise that resolves when the collections have been saved */ - saveCollectionsWithServer: (cipher: Cipher) => Promise; + abstract saveCollectionsWithServer(cipher: Cipher, userId: UserId): Promise; /** * Save the collections for a cipher with the server as an admin. * Used for Unassigned ciphers or when the user only has admin access to the cipher (not assigned normally). * @param cipher */ - saveCollectionsWithServerAdmin: (cipher: Cipher) => Promise; + abstract saveCollectionsWithServerAdmin(cipher: Cipher): Promise; /** * Bulk update collections for many ciphers with the server * @param orgId + * @param userId * @param cipherIds * @param collectionIds * @param removeCollections - If true, the collections will be removed from the ciphers, otherwise they will be added */ - bulkUpdateCollectionsWithServer: ( + abstract bulkUpdateCollectionsWithServer( orgId: OrganizationId, + userId: UserId, cipherIds: CipherId[], collectionIds: CollectionId[], removeCollections: boolean, - ) => Promise; + ): Promise; /** * Update the local store of CipherData with the provided data. Values are upserted into the existing store. * * @param cipher The cipher data to upsert. Can be a single CipherData object or an array of CipherData objects. * @returns A promise that resolves to a record of updated cipher store, keyed by their cipher ID. Returns all ciphers, not just those updated */ - upsert: (cipher: CipherData | CipherData[]) => Promise>; - replace: (ciphers: { [id: string]: CipherData }, userId: UserId) => Promise; - clear: (userId?: string) => Promise; - moveManyWithServer: (ids: string[], folderId: string) => Promise; - delete: (id: string | string[]) => Promise; - deleteWithServer: (id: string, asAdmin?: boolean) => Promise; - deleteManyWithServer: (ids: string[], asAdmin?: boolean) => Promise; - deleteAttachment: (id: string, attachmentId: string) => Promise; - deleteAttachmentWithServer: (id: string, attachmentId: string) => Promise; - sortCiphersByLastUsed: (a: CipherView, b: CipherView) => number; - sortCiphersByLastUsedThenName: (a: CipherView, b: CipherView) => number; - getLocaleSortingFunction: () => (a: CipherView, b: CipherView) => number; - softDelete: (id: string | string[]) => Promise; - softDeleteWithServer: (id: string, asAdmin?: boolean) => Promise; - softDeleteManyWithServer: (ids: string[], asAdmin?: boolean) => Promise; - restore: ( + abstract upsert(cipher: CipherData | CipherData[]): Promise>; + abstract replace(ciphers: { [id: string]: CipherData }, userId: UserId): Promise; + abstract clear(userId?: string): Promise; + abstract moveManyWithServer(ids: string[], folderId: string, userId: UserId): Promise; + abstract delete(id: string | string[], userId: UserId): Promise; + abstract deleteWithServer(id: string, userId: UserId, asAdmin?: boolean): Promise; + abstract deleteManyWithServer(ids: string[], userId: UserId, asAdmin?: boolean): Promise; + abstract deleteAttachment( + id: string, + revisionDate: string, + attachmentId: string, + userId: UserId, + ): Promise; + abstract deleteAttachmentWithServer( + id: string, + attachmentId: string, + userId: UserId, + ): Promise; + abstract sortCiphersByLastUsed(a: CipherView, b: CipherView): number; + abstract sortCiphersByLastUsedThenName(a: CipherView, b: CipherView): number; + abstract getLocaleSortingFunction(): (a: CipherView, b: CipherView) => number; + abstract softDelete(id: string | string[], userId: UserId): Promise; + abstract softDeleteWithServer(id: string, userId: UserId, asAdmin?: boolean): Promise; + abstract softDeleteManyWithServer(ids: string[], userId: UserId, asAdmin?: boolean): Promise; + abstract restore( cipher: { id: string; revisionDate: string } | { id: string; revisionDate: string }[], - ) => Promise; - restoreWithServer: (id: string, asAdmin?: boolean) => Promise; - restoreManyWithServer: (ids: string[], orgId?: string) => Promise; - getKeyForCipherKeyDecryption: (cipher: Cipher, userId: UserId) => Promise; - setAddEditCipherInfo: (value: AddEditCipherInfo) => Promise; + userId: UserId, + ): Promise; + abstract restoreWithServer(id: string, userId: UserId, asAdmin?: boolean): Promise; + abstract restoreManyWithServer(ids: string[], orgId?: string): Promise; + abstract getKeyForCipherKeyDecryption(cipher: Cipher, userId: UserId): Promise; + abstract setAddEditCipherInfo(value: AddEditCipherInfo, userId: UserId): Promise; /** * Returns user ciphers re-encrypted with the new user key. * @param originalUserKey the original user key @@ -177,11 +207,11 @@ export abstract class CipherService implements UserKeyRotationDataProvider Promise; - getNextCardCipher: () => Promise; - getNextIdentityCipher: () => Promise; + ): Promise; + abstract getNextCardCipher(userId: UserId): Promise; + abstract getNextIdentityCipher(userId: UserId): Promise; } diff --git a/libs/common/src/vault/abstractions/folder/folder-api.service.abstraction.ts b/libs/common/src/vault/abstractions/folder/folder-api.service.abstraction.ts index 859f2183edb..1bb4a52e929 100644 --- a/libs/common/src/vault/abstractions/folder/folder-api.service.abstraction.ts +++ b/libs/common/src/vault/abstractions/folder/folder-api.service.abstraction.ts @@ -1,12 +1,13 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { UserId } from "@bitwarden/common/types/guid"; +import { UserId } from "../../../types/guid"; +import { FolderData } from "../../models/data/folder.data"; import { Folder } from "../../models/domain/folder"; import { FolderResponse } from "../../models/response/folder.response"; export class FolderApiServiceAbstraction { - save: (folder: Folder, userId: UserId) => Promise; + save: (folder: Folder, userId: UserId) => Promise; delete: (id: string, userId: UserId) => Promise; get: (id: string) => Promise; deleteAll: (userId: UserId) => Promise; diff --git a/libs/common/src/vault/abstractions/totp.service.ts b/libs/common/src/vault/abstractions/totp.service.ts index af4409a15a6..f07b84e3bd2 100644 --- a/libs/common/src/vault/abstractions/totp.service.ts +++ b/libs/common/src/vault/abstractions/totp.service.ts @@ -1,6 +1,15 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore +import { Observable } from "rxjs"; + +import { TotpResponse } from "@bitwarden/sdk-internal"; + export abstract class TotpService { - getCode: (key: string) => Promise; - getTimeInterval: (key: string) => number; + /** + * Gets an observable that emits TOTP codes at regular intervals + * @param key - Can be: + * - A base32 encoded string + * - OTP Auth URI + * - Steam URI + * @returns Observable that emits TotpResponse containing the code and period + */ + abstract getCode$(key: string): Observable; } diff --git a/libs/common/src/vault/enums/vault-messages.enum.ts b/libs/common/src/vault/enums/vault-messages.enum.ts new file mode 100644 index 00000000000..4cc038be849 --- /dev/null +++ b/libs/common/src/vault/enums/vault-messages.enum.ts @@ -0,0 +1,8 @@ +const VaultMessages = { + HasBwInstalled: "hasBwInstalled", + checkBwInstalled: "checkIfBWExtensionInstalled", + OpenPopup: "openPopup", + PopupOpened: "popupOpened", +} as const; + +export { VaultMessages }; diff --git a/libs/common/src/vault/enums/vault-onboarding.enum.ts b/libs/common/src/vault/enums/vault-onboarding.enum.ts deleted file mode 100644 index 11e072b3284..00000000000 --- a/libs/common/src/vault/enums/vault-onboarding.enum.ts +++ /dev/null @@ -1,6 +0,0 @@ -const VaultOnboardingMessages = { - HasBwInstalled: "hasBwInstalled", - checkBwInstalled: "checkIfBWExtensionInstalled", -} as const; - -export { VaultOnboardingMessages }; diff --git a/libs/common/src/vault/icon/build-cipher-icon.spec.ts b/libs/common/src/vault/icon/build-cipher-icon.spec.ts new file mode 100644 index 00000000000..90ccaaec3a6 --- /dev/null +++ b/libs/common/src/vault/icon/build-cipher-icon.spec.ts @@ -0,0 +1,141 @@ +import { CipherType } from "../enums"; +import { CipherView } from "../models/view/cipher.view"; + +import { buildCipherIcon } from "./build-cipher-icon"; + +describe("buildCipherIcon", () => { + const iconServerUrl = "https://icons.example"; + describe("Login cipher", () => { + const cipher = { + type: CipherType.Login, + login: { + uri: "https://test.example", + }, + } as any as CipherView; + + it.each([true, false])("handles android app URIs for showFavicon setting %s", (showFavicon) => { + setUri("androidapp://test.example"); + + const iconDetails = buildCipherIcon(iconServerUrl, cipher, showFavicon); + + expect(iconDetails).toEqual({ + icon: "bwi-android", + image: null, + fallbackImage: "", + imageEnabled: showFavicon, + }); + }); + + it("does not mark as an android app if the protocol is not androidapp", () => { + // This weird URI points to test.androidapp with a default port and path of /.example + setUri("https://test.androidapp://.example"); + + const iconDetails = buildCipherIcon(iconServerUrl, cipher, true); + + expect(iconDetails).toEqual({ + icon: "bwi-globe", + image: "https://icons.example/test.androidapp/icon.png", + fallbackImage: "images/bwi-globe.png", + imageEnabled: true, + }); + }); + + it.each([true, false])("handles ios app URIs for showFavicon setting %s", (showFavicon) => { + setUri("iosapp://test.example"); + + const iconDetails = buildCipherIcon(iconServerUrl, cipher, showFavicon); + + expect(iconDetails).toEqual({ + icon: "bwi-apple", + image: null, + fallbackImage: "", + imageEnabled: showFavicon, + }); + }); + + it("does not mark as an ios app if the protocol is not iosapp", () => { + // This weird URI points to test.iosapp with a default port and path of /.example + setUri("https://test.iosapp://.example"); + + const iconDetails = buildCipherIcon(iconServerUrl, cipher, true); + + expect(iconDetails).toEqual({ + icon: "bwi-globe", + image: "https://icons.example/test.iosapp/icon.png", + fallbackImage: "images/bwi-globe.png", + imageEnabled: true, + }); + }); + + const testUris = ["test.example", "https://test.example"]; + + it.each(testUris)("resolves favicon for %s", (uri) => { + setUri(uri); + + const iconDetails = buildCipherIcon(iconServerUrl, cipher, true); + + expect(iconDetails).toEqual({ + icon: "bwi-globe", + image: "https://icons.example/test.example/icon.png", + fallbackImage: "images/bwi-globe.png", + imageEnabled: true, + }); + }); + + it.each(testUris)("does not resolve favicon for %s if showFavicon is false", () => { + setUri("https://test.example"); + + const iconDetails = buildCipherIcon(iconServerUrl, cipher, false); + + expect(iconDetails).toEqual({ + icon: "bwi-globe", + image: null, + fallbackImage: "", + imageEnabled: false, + }); + }); + + it("does not resolve a favicon if the URI is missing a `.`", () => { + setUri("test"); + + const iconDetails = buildCipherIcon(iconServerUrl, cipher, true); + + expect(iconDetails).toEqual({ + icon: "bwi-globe", + image: null, + fallbackImage: "", + imageEnabled: true, + }); + }); + + it.each(["test.onion", "test.i2p"])("does not resolve a favicon for %s", (uri) => { + setUri(`https://${uri}`); + + const iconDetails = buildCipherIcon(iconServerUrl, cipher, true); + + expect(iconDetails).toEqual({ + icon: "bwi-globe", + image: null, + fallbackImage: "images/bwi-globe.png", + imageEnabled: true, + }); + }); + + it.each([null, undefined])("does not resolve a favicon if there is no uri", (nullish) => { + setUri(nullish as any as string); + + const iconDetails = buildCipherIcon(iconServerUrl, cipher, true); + + expect(iconDetails).toEqual({ + icon: "bwi-globe", + image: null, + fallbackImage: "", + imageEnabled: true, + }); + }); + + function setUri(uri: string) { + (cipher.login as { uri: string }).uri = uri; + } + }); +}); diff --git a/libs/common/src/vault/icon/build-cipher-icon.ts b/libs/common/src/vault/icon/build-cipher-icon.ts index 8ffe4749568..b7456e1ae96 100644 --- a/libs/common/src/vault/icon/build-cipher-icon.ts +++ b/libs/common/src/vault/icon/build-cipher-icon.ts @@ -2,9 +2,23 @@ import { Utils } from "../../platform/misc/utils"; import { CipherType } from "../enums/cipher-type"; import { CipherView } from "../models/view/cipher.view"; -export function buildCipherIcon(iconsServerUrl: string, cipher: CipherView, showFavicon: boolean) { - let icon; - let image; +export interface CipherIconDetails { + imageEnabled: boolean; + image: string | null; + /** + * @deprecated Fallback to `icon` instead which will default to "bwi-globe" if no other icon is applicable. + */ + fallbackImage: string; + icon: string; +} + +export function buildCipherIcon( + iconsServerUrl: string | null, + cipher: CipherView, + showFavicon: boolean, +): CipherIconDetails { + let icon: string = "bwi-globe"; + let image: string | null = null; let fallbackImage = ""; const cardIcons: Record = { Visa: "card-visa", @@ -18,6 +32,10 @@ export function buildCipherIcon(iconsServerUrl: string, cipher: CipherView, show RuPay: "card-ru-pay", }; + if (iconsServerUrl == null) { + showFavicon = false; + } + switch (cipher.type) { case CipherType.Login: icon = "bwi-globe"; @@ -53,7 +71,7 @@ export function buildCipherIcon(iconsServerUrl: string, cipher: CipherView, show try { image = `${iconsServerUrl}/${Utils.getHostname(hostnameUri)}/icon.png`; fallbackImage = "images/bwi-globe.png"; - } catch (e) { + } catch { // Ignore error since the fallback icon will be shown if image is null. } } diff --git a/libs/common/src/vault/models/api/cipher-permissions.api.ts b/libs/common/src/vault/models/api/cipher-permissions.api.ts new file mode 100644 index 00000000000..4df7f891e26 --- /dev/null +++ b/libs/common/src/vault/models/api/cipher-permissions.api.ts @@ -0,0 +1,21 @@ +import { Jsonify } from "type-fest"; + +import { BaseResponse } from "../../../models/response/base.response"; + +export class CipherPermissionsApi extends BaseResponse { + delete: boolean = false; + restore: boolean = false; + + constructor(data: any = null) { + super(data); + if (data == null) { + return; + } + this.delete = this.getResponseProperty("Delete"); + this.restore = this.getResponseProperty("Restore"); + } + + static fromJSON(obj: Jsonify) { + return Object.assign(new CipherPermissionsApi(), obj); + } +} diff --git a/libs/common/src/vault/models/data/cipher.data.ts b/libs/common/src/vault/models/data/cipher.data.ts index 1c86f91c82f..ee5e5b3e72b 100644 --- a/libs/common/src/vault/models/data/cipher.data.ts +++ b/libs/common/src/vault/models/data/cipher.data.ts @@ -4,6 +4,7 @@ import { Jsonify } from "type-fest"; import { CipherRepromptType } from "../../enums/cipher-reprompt-type"; import { CipherType } from "../../enums/cipher-type"; +import { CipherPermissionsApi } from "../api/cipher-permissions.api"; import { CipherResponse } from "../response/cipher.response"; import { AttachmentData } from "./attachment.data"; @@ -21,6 +22,7 @@ export class CipherData { folderId: string; edit: boolean; viewPassword: boolean; + permissions: CipherPermissionsApi; organizationUseTotp: boolean; favorite: boolean; revisionDate: string; @@ -51,6 +53,7 @@ export class CipherData { this.folderId = response.folderId; this.edit = response.edit; this.viewPassword = response.viewPassword; + this.permissions = response.permissions; this.organizationUseTotp = response.organizationUseTotp; this.favorite = response.favorite; this.revisionDate = response.revisionDate; @@ -95,6 +98,8 @@ export class CipherData { } static fromJSON(obj: Jsonify) { - return Object.assign(new CipherData(), obj); + const result = Object.assign(new CipherData(), obj); + result.permissions = CipherPermissionsApi.fromJSON(obj.permissions); + return result; } } diff --git a/libs/common/src/vault/models/domain/attachment.spec.ts b/libs/common/src/vault/models/domain/attachment.spec.ts index b074e7a46ad..d1ee1dcc1ef 100644 --- a/libs/common/src/vault/models/domain/attachment.spec.ts +++ b/libs/common/src/vault/models/domain/attachment.spec.ts @@ -1,8 +1,9 @@ import { mock, MockProxy } from "jest-mock-extended"; -import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; +import { KeyService } from "@bitwarden/key-management"; + import { makeStaticByteArray, mockEnc, mockFromJson } from "../../../../spec"; -import { EncryptService } from "../../../platform/abstractions/encrypt.service"; +import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service"; import { EncryptedString, EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { ContainerService } from "../../../platform/services/container.service"; diff --git a/libs/common/src/vault/models/domain/attachment.ts b/libs/common/src/vault/models/domain/attachment.ts index 2b893e33f49..16f3adbe307 100644 --- a/libs/common/src/vault/models/domain/attachment.ts +++ b/libs/common/src/vault/models/domain/attachment.ts @@ -43,11 +43,10 @@ export class Attachment extends Domain { context = "No Cipher Context", encKey?: SymmetricCryptoKey, ): Promise { - const view = await this.decryptObj( + const view = await this.decryptObj( + this, new AttachmentView(this), - { - fileName: null, - }, + ["fileName"], orgId, encKey, "DomainType: Attachment; " + context, @@ -69,6 +68,8 @@ export class Attachment extends Domain { const encryptService = Utils.getContainerService().getEncryptService(); const decValue = await encryptService.decryptToBytes(this.key, encKey); return new SymmetricCryptoKey(decValue); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // TODO: error? } diff --git a/libs/common/src/vault/models/domain/card.ts b/libs/common/src/vault/models/domain/card.ts index fccfe3f595b..3d73a8f527c 100644 --- a/libs/common/src/vault/models/domain/card.ts +++ b/libs/common/src/vault/models/domain/card.ts @@ -42,16 +42,10 @@ export class Card extends Domain { context = "No Cipher Context", encKey?: SymmetricCryptoKey, ): Promise { - return this.decryptObj( + return this.decryptObj( + this, new CardView(), - { - cardholderName: null, - brand: null, - number: null, - expMonth: null, - expYear: null, - code: null, - }, + ["cardholderName", "brand", "number", "expMonth", "expYear", "code"], orgId, encKey, "DomainType: Card; " + context, diff --git a/libs/common/src/vault/models/domain/cipher.spec.ts b/libs/common/src/vault/models/domain/cipher.spec.ts index 509a17a8a0e..1b2b093a553 100644 --- a/libs/common/src/vault/models/domain/cipher.spec.ts +++ b/libs/common/src/vault/models/domain/cipher.spec.ts @@ -1,15 +1,15 @@ import { mock } from "jest-mock-extended"; import { Jsonify } from "type-fest"; -import { UserId } from "@bitwarden/common/types/guid"; +import { KeyService } from "@bitwarden/key-management"; -import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; import { makeStaticByteArray, mockEnc, mockFromJson } from "../../../../spec/utils"; +import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service"; import { UriMatchStrategy } from "../../../models/domain/domain-service"; -import { EncryptService } from "../../../platform/abstractions/encrypt.service"; import { EncString } from "../../../platform/models/domain/enc-string"; import { ContainerService } from "../../../platform/services/container.service"; import { InitializerKey } from "../../../platform/services/cryptography/initializer-key"; +import { UserId } from "../../../types/guid"; import { CipherService } from "../../abstractions/cipher.service"; import { FieldType, SecureNoteType } from "../../enums"; import { CipherRepromptType } from "../../enums/cipher-reprompt-type"; @@ -26,6 +26,7 @@ import { SecureNote } from "../../models/domain/secure-note"; import { CardView } from "../../models/view/card.view"; import { IdentityView } from "../../models/view/identity.view"; import { LoginView } from "../../models/view/login.view"; +import { CipherPermissionsApi } from "../api/cipher-permissions.api"; describe("Cipher DTO", () => { it("Convert from empty CipherData", () => { @@ -54,6 +55,7 @@ describe("Cipher DTO", () => { fields: null, passwordHistory: null, key: null, + permissions: undefined, }); }); @@ -75,6 +77,7 @@ describe("Cipher DTO", () => { notes: "EncryptedString", creationDate: "2022-01-01T12:00:00.000Z", deletedDate: null, + permissions: new CipherPermissionsApi(), reprompt: CipherRepromptType.None, key: "EncryptedString", login: { @@ -149,6 +152,7 @@ describe("Cipher DTO", () => { localData: null, creationDate: new Date("2022-01-01T12:00:00.000Z"), deletedDate: null, + permissions: new CipherPermissionsApi(), reprompt: 0, key: { encryptedString: "EncryptedString", encryptionType: 0 }, login: { @@ -228,6 +232,7 @@ describe("Cipher DTO", () => { cipher.deletedDate = null; cipher.reprompt = CipherRepromptType.None; cipher.key = mockEnc("EncKey"); + cipher.permissions = new CipherPermissionsApi(); const loginView = new LoginView(); loginView.username = "username"; @@ -270,6 +275,7 @@ describe("Cipher DTO", () => { deletedDate: null, reprompt: 0, localData: undefined, + permissions: new CipherPermissionsApi(), }); }); }); @@ -297,6 +303,7 @@ describe("Cipher DTO", () => { secureNote: { type: SecureNoteType.Generic, }, + permissions: new CipherPermissionsApi(), }; }); @@ -326,6 +333,7 @@ describe("Cipher DTO", () => { fields: null, passwordHistory: null, key: { encryptedString: "EncKey", encryptionType: 0 }, + permissions: new CipherPermissionsApi(), }); }); @@ -353,6 +361,7 @@ describe("Cipher DTO", () => { cipher.secureNote = new SecureNote(); cipher.secureNote.type = SecureNoteType.Generic; cipher.key = mockEnc("EncKey"); + cipher.permissions = new CipherPermissionsApi(); const keyService = mock(); const encryptService = mock(); @@ -387,6 +396,7 @@ describe("Cipher DTO", () => { deletedDate: null, reprompt: 0, localData: undefined, + permissions: new CipherPermissionsApi(), }); }); }); @@ -409,6 +419,7 @@ describe("Cipher DTO", () => { notes: "EncryptedString", creationDate: "2022-01-01T12:00:00.000Z", deletedDate: null, + permissions: new CipherPermissionsApi(), reprompt: CipherRepromptType.None, card: { cardholderName: "EncryptedString", @@ -455,6 +466,7 @@ describe("Cipher DTO", () => { fields: null, passwordHistory: null, key: { encryptedString: "EncKey", encryptionType: 0 }, + permissions: new CipherPermissionsApi(), }); }); @@ -480,6 +492,7 @@ describe("Cipher DTO", () => { cipher.deletedDate = null; cipher.reprompt = CipherRepromptType.None; cipher.key = mockEnc("EncKey"); + cipher.permissions = new CipherPermissionsApi(); const cardView = new CardView(); cardView.cardholderName = "cardholderName"; @@ -522,6 +535,7 @@ describe("Cipher DTO", () => { deletedDate: null, reprompt: 0, localData: undefined, + permissions: new CipherPermissionsApi(), }); }); }); @@ -544,6 +558,7 @@ describe("Cipher DTO", () => { notes: "EncryptedString", creationDate: "2022-01-01T12:00:00.000Z", deletedDate: null, + permissions: new CipherPermissionsApi(), reprompt: CipherRepromptType.None, key: "EncKey", identity: { @@ -614,6 +629,7 @@ describe("Cipher DTO", () => { fields: null, passwordHistory: null, key: { encryptedString: "EncKey", encryptionType: 0 }, + permissions: new CipherPermissionsApi(), }); }); @@ -639,6 +655,7 @@ describe("Cipher DTO", () => { cipher.deletedDate = null; cipher.reprompt = CipherRepromptType.None; cipher.key = mockEnc("EncKey"); + cipher.permissions = new CipherPermissionsApi(); const identityView = new IdentityView(); identityView.firstName = "firstName"; @@ -681,6 +698,7 @@ describe("Cipher DTO", () => { deletedDate: null, reprompt: 0, localData: undefined, + permissions: new CipherPermissionsApi(), }); }); }); diff --git a/libs/common/src/vault/models/domain/cipher.ts b/libs/common/src/vault/models/domain/cipher.ts index d82f4585e65..f23e3c0c579 100644 --- a/libs/common/src/vault/models/domain/cipher.ts +++ b/libs/common/src/vault/models/domain/cipher.ts @@ -10,9 +10,13 @@ import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-cr import { InitializerKey } from "../../../platform/services/cryptography/initializer-key"; import { CipherRepromptType } from "../../enums/cipher-reprompt-type"; import { CipherType } from "../../enums/cipher-type"; +import { CipherPermissionsApi } from "../api/cipher-permissions.api"; import { CipherData } from "../data/cipher.data"; import { LocalData } from "../data/local.data"; +import { AttachmentView } from "../view/attachment.view"; import { CipherView } from "../view/cipher.view"; +import { FieldView } from "../view/field.view"; +import { PasswordHistoryView } from "../view/password-history.view"; import { Attachment } from "./attachment"; import { Card } from "./card"; @@ -36,6 +40,7 @@ export class Cipher extends Domain implements Decryptable { organizationUseTotp: boolean; edit: boolean; viewPassword: boolean; + permissions: CipherPermissionsApi; revisionDate: Date; localData: LocalData; login: Login; @@ -81,6 +86,7 @@ export class Cipher extends Domain implements Decryptable { } else { this.viewPassword = true; // Default for already synced Ciphers without viewPassword } + this.permissions = obj.permissions; this.revisionDate = obj.revisionDate != null ? new Date(obj.revisionDate) : null; this.collectionIds = obj.collectionIds; this.localData = localData; @@ -136,6 +142,7 @@ export class Cipher extends Domain implements Decryptable { if (this.key != null) { const encryptService = Utils.getContainerService().getEncryptService(); + const keyBytes = await encryptService.decryptToBytes( this.key, encKey, @@ -150,12 +157,10 @@ export class Cipher extends Domain implements Decryptable { bypassValidation = false; } - await this.decryptObj( + await this.decryptObj( + this, model, - { - name: null, - notes: null, - }, + ["name", "notes"], this.organizationId, encKey, ); @@ -198,44 +203,28 @@ export class Cipher extends Domain implements Decryptable { } if (this.attachments != null && this.attachments.length > 0) { - const attachments: any[] = []; - await this.attachments.reduce((promise, attachment) => { - return promise - .then(() => { - return attachment.decrypt(this.organizationId, `Cipher Id: ${this.id}`, encKey); - }) - .then((decAttachment) => { - attachments.push(decAttachment); - }); - }, Promise.resolve()); + const attachments: AttachmentView[] = []; + for (const attachment of this.attachments) { + attachments.push( + await attachment.decrypt(this.organizationId, `Cipher Id: ${this.id}`, encKey), + ); + } model.attachments = attachments; } if (this.fields != null && this.fields.length > 0) { - const fields: any[] = []; - await this.fields.reduce((promise, field) => { - return promise - .then(() => { - return field.decrypt(this.organizationId, encKey); - }) - .then((decField) => { - fields.push(decField); - }); - }, Promise.resolve()); + const fields: FieldView[] = []; + for (const field of this.fields) { + fields.push(await field.decrypt(this.organizationId, encKey)); + } model.fields = fields; } if (this.passwordHistory != null && this.passwordHistory.length > 0) { - const passwordHistory: any[] = []; - await this.passwordHistory.reduce((promise, ph) => { - return promise - .then(() => { - return ph.decrypt(this.organizationId, encKey); - }) - .then((decPh) => { - passwordHistory.push(decPh); - }); - }, Promise.resolve()); + const passwordHistory: PasswordHistoryView[] = []; + for (const ph of this.passwordHistory) { + passwordHistory.push(await ph.decrypt(this.organizationId, encKey)); + } model.passwordHistory = passwordHistory; } @@ -258,6 +247,7 @@ export class Cipher extends Domain implements Decryptable { c.deletedDate = this.deletedDate != null ? this.deletedDate.toISOString() : null; c.reprompt = this.reprompt; c.key = this.key?.encryptedString; + c.permissions = this.permissions; this.buildDataModel(this, c, { name: null, diff --git a/libs/common/src/vault/models/domain/fido2-credential.ts b/libs/common/src/vault/models/domain/fido2-credential.ts index 9aa2c753d7c..8b0082892e4 100644 --- a/libs/common/src/vault/models/domain/fido2-credential.ts +++ b/libs/common/src/vault/models/domain/fido2-credential.ts @@ -52,41 +52,38 @@ export class Fido2Credential extends Domain { } async decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { - const view = await this.decryptObj( + const view = await this.decryptObj( + this, new Fido2CredentialView(), - { - credentialId: null, - keyType: null, - keyAlgorithm: null, - keyCurve: null, - keyValue: null, - rpId: null, - userHandle: null, - userName: null, - rpName: null, - userDisplayName: null, - discoverable: null, - }, + [ + "credentialId", + "keyType", + "keyAlgorithm", + "keyCurve", + "keyValue", + "rpId", + "userHandle", + "userName", + "rpName", + "userDisplayName", + ], orgId, encKey, ); - const { counter } = await this.decryptObj( - { counter: "" }, + const { counter } = await this.decryptObj< + Fido2Credential, { - counter: null, - }, - orgId, - encKey, - ); + counter: string; + } + >(this, { counter: "" }, ["counter"], orgId, encKey); // Counter will end up as NaN if this fails view.counter = parseInt(counter); - const { discoverable } = await this.decryptObj( + const { discoverable } = await this.decryptObj( + this, { discoverable: "" }, - { - discoverable: null, - }, + ["discoverable"], orgId, encKey, ); diff --git a/libs/common/src/vault/models/domain/field.ts b/libs/common/src/vault/models/domain/field.ts index f836184da6a..c0f08a38bcc 100644 --- a/libs/common/src/vault/models/domain/field.ts +++ b/libs/common/src/vault/models/domain/field.ts @@ -35,12 +35,10 @@ export class Field extends Domain { } decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { - return this.decryptObj( + return this.decryptObj( + this, new FieldView(this), - { - name: null, - value: null, - }, + ["name", "value"], orgId, encKey, ); diff --git a/libs/common/src/vault/models/domain/folder.spec.ts b/libs/common/src/vault/models/domain/folder.spec.ts index 785852b884e..ff1c38bdd45 100644 --- a/libs/common/src/vault/models/domain/folder.spec.ts +++ b/libs/common/src/vault/models/domain/folder.spec.ts @@ -1,7 +1,7 @@ import { mock, MockProxy } from "jest-mock-extended"; import { makeEncString, makeSymmetricCryptoKey, mockEnc, mockFromJson } from "../../../../spec"; -import { EncryptService } from "../../../platform/abstractions/encrypt.service"; +import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service"; import { EncryptedString, EncString } from "../../../platform/models/domain/enc-string"; import { FolderData } from "../../models/data/folder.data"; import { Folder } from "../../models/domain/folder"; diff --git a/libs/common/src/vault/models/domain/folder.ts b/libs/common/src/vault/models/domain/folder.ts index 93d04607af5..8749a92fb65 100644 --- a/libs/common/src/vault/models/domain/folder.ts +++ b/libs/common/src/vault/models/domain/folder.ts @@ -2,7 +2,7 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; -import { EncryptService } from "../../../platform/abstractions/encrypt.service"; +import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service"; import Domain from "../../../platform/models/domain/domain-base"; import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; @@ -40,13 +40,7 @@ export class Folder extends Domain { } decrypt(): Promise { - return this.decryptObj( - new FolderView(this), - { - name: null, - }, - null, - ); + return this.decryptObj(this, new FolderView(this), ["name"], null); } async decryptWithKey( diff --git a/libs/common/src/vault/models/domain/identity.ts b/libs/common/src/vault/models/domain/identity.ts index 570e6c0b4d5..5d8c20ef2b3 100644 --- a/libs/common/src/vault/models/domain/identity.ts +++ b/libs/common/src/vault/models/domain/identity.ts @@ -66,28 +66,29 @@ export class Identity extends Domain { context: string = "No Cipher Context", encKey?: SymmetricCryptoKey, ): Promise { - return this.decryptObj( + return this.decryptObj( + this, new IdentityView(), - { - title: null, - firstName: null, - middleName: null, - lastName: null, - address1: null, - address2: null, - address3: null, - city: null, - state: null, - postalCode: null, - country: null, - company: null, - email: null, - phone: null, - ssn: null, - username: null, - passportNumber: null, - licenseNumber: null, - }, + [ + "title", + "firstName", + "middleName", + "lastName", + "address1", + "address2", + "address3", + "city", + "state", + "postalCode", + "country", + "company", + "email", + "phone", + "ssn", + "username", + "passportNumber", + "licenseNumber", + ], orgId, encKey, "DomainType: Identity; " + context, diff --git a/libs/common/src/vault/models/domain/login-uri.spec.ts b/libs/common/src/vault/models/domain/login-uri.spec.ts index a1ecb473597..6346f38f0de 100644 --- a/libs/common/src/vault/models/domain/login-uri.spec.ts +++ b/libs/common/src/vault/models/domain/login-uri.spec.ts @@ -2,8 +2,8 @@ import { MockProxy, mock } from "jest-mock-extended"; import { Jsonify } from "type-fest"; import { mockEnc, mockFromJson } from "../../../../spec"; +import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service"; import { UriMatchStrategy } from "../../../models/domain/domain-service"; -import { EncryptService } from "../../../platform/abstractions/encrypt.service"; import { EncString } from "../../../platform/models/domain/enc-string"; import { LoginUriData } from "../data/login-uri.data"; diff --git a/libs/common/src/vault/models/domain/login-uri.ts b/libs/common/src/vault/models/domain/login-uri.ts index 36782a81502..883f8c9a616 100644 --- a/libs/common/src/vault/models/domain/login-uri.ts +++ b/libs/common/src/vault/models/domain/login-uri.ts @@ -38,11 +38,10 @@ export class LoginUri extends Domain { context: string = "No Cipher Context", encKey?: SymmetricCryptoKey, ): Promise { - return this.decryptObj( + return this.decryptObj( + this, new LoginUriView(this), - { - uri: null, - }, + ["uri"], orgId, encKey, context, diff --git a/libs/common/src/vault/models/domain/login.ts b/libs/common/src/vault/models/domain/login.ts index f9a85cd818e..b29b42bf3de 100644 --- a/libs/common/src/vault/models/domain/login.ts +++ b/libs/common/src/vault/models/domain/login.ts @@ -58,13 +58,10 @@ export class Login extends Domain { context: string = "No Cipher Context", encKey?: SymmetricCryptoKey, ): Promise { - const view = await this.decryptObj( + const view = await this.decryptObj( + this, new LoginView(this), - { - username: null, - password: null, - totp: null, - }, + ["username", "password", "totp"], orgId, encKey, `DomainType: Login; ${context}`, diff --git a/libs/common/src/vault/models/domain/password.ts b/libs/common/src/vault/models/domain/password.ts index 48063f495f0..8573c224416 100644 --- a/libs/common/src/vault/models/domain/password.ts +++ b/libs/common/src/vault/models/domain/password.ts @@ -25,11 +25,10 @@ export class Password extends Domain { } decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { - return this.decryptObj( + return this.decryptObj( + this, new PasswordHistoryView(this), - { - password: null, - }, + ["password"], orgId, encKey, "DomainType: PasswordHistory", diff --git a/libs/common/src/vault/models/domain/ssh-key.ts b/libs/common/src/vault/models/domain/ssh-key.ts index 9ce16fe4557..f32a1a913ca 100644 --- a/libs/common/src/vault/models/domain/ssh-key.ts +++ b/libs/common/src/vault/models/domain/ssh-key.ts @@ -2,9 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; -import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; - import Domain from "../../../platform/models/domain/domain-base"; +import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { SshKeyData } from "../data/ssh-key.data"; import { SshKeyView } from "../view/ssh-key.view"; @@ -37,13 +36,10 @@ export class SshKey extends Domain { context = "No Cipher Context", encKey?: SymmetricCryptoKey, ): Promise { - return this.decryptObj( + return this.decryptObj( + this, new SshKeyView(), - { - privateKey: null, - publicKey: null, - keyFingerprint: null, - }, + ["privateKey", "publicKey", "keyFingerprint"], orgId, encKey, "DomainType: SshKey; " + context, diff --git a/libs/common/src/vault/models/response/cipher.response.ts b/libs/common/src/vault/models/response/cipher.response.ts index ee0d36aed97..944a19e088b 100644 --- a/libs/common/src/vault/models/response/cipher.response.ts +++ b/libs/common/src/vault/models/response/cipher.response.ts @@ -3,6 +3,7 @@ import { BaseResponse } from "../../../models/response/base.response"; import { CipherRepromptType } from "../../enums/cipher-reprompt-type"; import { CardApi } from "../api/card.api"; +import { CipherPermissionsApi } from "../api/cipher-permissions.api"; import { FieldApi } from "../api/field.api"; import { IdentityApi } from "../api/identity.api"; import { LoginApi } from "../api/login.api"; @@ -28,6 +29,7 @@ export class CipherResponse extends BaseResponse { favorite: boolean; edit: boolean; viewPassword: boolean; + permissions: CipherPermissionsApi; organizationUseTotp: boolean; revisionDate: string; attachments: AttachmentResponse[]; @@ -53,6 +55,7 @@ export class CipherResponse extends BaseResponse { } else { this.viewPassword = this.getResponseProperty("ViewPassword"); } + this.permissions = new CipherPermissionsApi(this.getResponseProperty("Permissions")); this.organizationUseTotp = this.getResponseProperty("OrganizationUseTotp"); this.revisionDate = this.getResponseProperty("RevisionDate"); this.collectionIds = this.getResponseProperty("CollectionIds"); diff --git a/libs/common/src/vault/models/view/cipher.view.ts b/libs/common/src/vault/models/view/cipher.view.ts index c5de01293ee..7ddba9e2ed5 100644 --- a/libs/common/src/vault/models/view/cipher.view.ts +++ b/libs/common/src/vault/models/view/cipher.view.ts @@ -6,6 +6,7 @@ import { InitializerKey } from "../../../platform/services/cryptography/initiali import { DeepJsonify } from "../../../types/deep-jsonify"; import { CipherType, LinkedIdType } from "../../enums"; import { CipherRepromptType } from "../../enums/cipher-reprompt-type"; +import { CipherPermissionsApi } from "../api/cipher-permissions.api"; import { LocalData } from "../data/local.data"; import { Cipher } from "../domain/cipher"; @@ -29,6 +30,7 @@ export class CipherView implements View, InitializerMetadata { type: CipherType = null; favorite = false; organizationUseTotp = false; + permissions: CipherPermissionsApi = new CipherPermissionsApi(); edit = false; viewPassword = true; localData: LocalData; @@ -63,6 +65,7 @@ export class CipherView implements View, InitializerMetadata { this.organizationUseTotp = c.organizationUseTotp; this.edit = c.edit; this.viewPassword = c.viewPassword; + this.permissions = c.permissions; this.type = c.type; this.localData = c.localData; this.collectionIds = c.collectionIds; @@ -142,6 +145,13 @@ export class CipherView implements View, InitializerMetadata { ); } + get canAssignToCollections(): boolean { + if (this.organizationId == null) { + return true; + } + + return this.edit && this.viewPassword; + } /** * Determines if the cipher can be launched in a new browser tab. */ @@ -155,6 +165,8 @@ export class CipherView implements View, InitializerMetadata { return null; } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars const item = this.item; return this.item[linkedFieldOption.propertyKey as keyof typeof item]; } diff --git a/libs/common/src/vault/models/view/login-uri.view.ts b/libs/common/src/vault/models/view/login-uri.view.ts index a6b7a0c0a22..315adb87c75 100644 --- a/libs/common/src/vault/models/view/login-uri.view.ts +++ b/libs/common/src/vault/models/view/login-uri.view.ts @@ -142,6 +142,8 @@ export class LoginUriView implements View { try { const regex = new RegExp(this.uri, "i"); return regex.test(targetUri); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // Invalid regex return false; diff --git a/libs/common/src/vault/services/cipher-authorization.service.spec.ts b/libs/common/src/vault/services/cipher-authorization.service.spec.ts index cccd29ad697..33af28842ca 100644 --- a/libs/common/src/vault/services/cipher-authorization.service.spec.ts +++ b/libs/common/src/vault/services/cipher-authorization.service.spec.ts @@ -1,11 +1,15 @@ import { mock } from "jest-mock-extended"; -import { firstValueFrom, of } from "rxjs"; +import { Observable, firstValueFrom, of } from "rxjs"; import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { CollectionId } from "@bitwarden/common/types/guid"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { CollectionId, UserId } from "@bitwarden/common/types/guid"; +import { FakeAccountService, mockAccountServiceWith } from "../../../spec"; +import { ConfigService } from "../../platform/abstractions/config/config.service"; +import { CipherPermissionsApi } from "../models/api/cipher-permissions.api"; import { CipherView } from "../models/view/cipher.view"; import { @@ -18,16 +22,21 @@ describe("CipherAuthorizationService", () => { const mockCollectionService = mock(); const mockOrganizationService = mock(); + const mockConfigService = mock(); + const mockUserId = Utils.newGuid() as UserId; + let mockAccountService: FakeAccountService; // Mock factories const createMockCipher = ( organizationId: string | null, collectionIds: string[], edit: boolean = true, + permissions: CipherPermissionsApi = new CipherPermissionsApi(), ) => ({ organizationId, collectionIds, edit, + permissions, }); const createMockCollection = (id: string, manage: boolean) => ({ @@ -42,6 +51,7 @@ describe("CipherAuthorizationService", () => { isAdmin = false, editAnyCollection = false, } = {}) => ({ + id: "org1", allowAdminAccessToAllCollectionItems, canEditAllCiphers, canEditUnassignedCiphers, @@ -53,10 +63,83 @@ describe("CipherAuthorizationService", () => { beforeEach(() => { jest.clearAllMocks(); + mockAccountService = mockAccountServiceWith(mockUserId); cipherAuthorizationService = new DefaultCipherAuthorizationService( mockCollectionService, mockOrganizationService, + mockAccountService, + mockConfigService, ); + + mockConfigService.getFeatureFlag$.mockReturnValue(of(false)); + }); + + describe("canRestoreCipher$", () => { + it("should return true if isAdminConsoleAction and cipher is unassigned", (done) => { + const cipher = createMockCipher("org1", []) as CipherView; + const organization = createMockOrganization({ canEditUnassignedCiphers: true }); + mockOrganizationService.organizations$.mockReturnValue( + of([organization]) as Observable, + ); + + cipherAuthorizationService.canRestoreCipher$(cipher, true).subscribe((result) => { + expect(result).toBe(true); + done(); + }); + }); + + it("should return true if isAdminConsleAction and user can edit all ciphers in the org", (done) => { + const cipher = createMockCipher("org1", ["col1"]) as CipherView; + const organization = createMockOrganization({ canEditAllCiphers: true }); + mockOrganizationService.organizations$.mockReturnValue( + of([organization]) as Observable, + ); + + cipherAuthorizationService.canRestoreCipher$(cipher, true).subscribe((result) => { + expect(result).toBe(true); + expect(mockOrganizationService.organizations$).toHaveBeenCalledWith(mockUserId); + done(); + }); + }); + + it("should return false if isAdminConsoleAction is true but user does not have permission to edit unassigned ciphers", (done) => { + const cipher = createMockCipher("org1", []) as CipherView; + const organization = createMockOrganization({ canEditUnassignedCiphers: false }); + mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[])); + + cipherAuthorizationService.canRestoreCipher$(cipher, true).subscribe((result) => { + expect(result).toBe(false); + done(); + }); + }); + + it("should return false if cipher.permission.restore is false and is not an admin action", (done) => { + const cipher = createMockCipher("org1", [], true, { + restore: false, + } as CipherPermissionsApi) as CipherView; + const organization = createMockOrganization(); + mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[])); + + cipherAuthorizationService.canRestoreCipher$(cipher, false).subscribe((result) => { + expect(result).toBe(false); + expect(mockCollectionService.decryptedCollectionViews$).not.toHaveBeenCalled(); + done(); + }); + }); + + it("should return true if cipher.permission.restore is true and is not an admin action", (done) => { + const cipher = createMockCipher("org1", [], true, { + restore: true, + } as CipherPermissionsApi) as CipherView; + const organization = createMockOrganization(); + mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[])); + + cipherAuthorizationService.canRestoreCipher$(cipher, false).subscribe((result) => { + expect(result).toBe(true); + expect(mockCollectionService.decryptedCollectionViews$).not.toHaveBeenCalled(); + done(); + }); + }); }); describe("canDeleteCipher$", () => { @@ -72,7 +155,9 @@ describe("CipherAuthorizationService", () => { it("should return true if isAdminConsoleAction is true and cipher is unassigned", (done) => { const cipher = createMockCipher("org1", []) as CipherView; const organization = createMockOrganization({ canEditUnassignedCiphers: true }); - mockOrganizationService.get$.mockReturnValue(of(organization as Organization)); + mockOrganizationService.organizations$.mockReturnValue( + of([organization]) as Observable, + ); cipherAuthorizationService.canDeleteCipher$(cipher, [], true).subscribe((result) => { expect(result).toBe(true); @@ -83,11 +168,13 @@ describe("CipherAuthorizationService", () => { it("should return true if isAdminConsoleAction is true and user can edit all ciphers in the org", (done) => { const cipher = createMockCipher("org1", ["col1"]) as CipherView; const organization = createMockOrganization({ canEditAllCiphers: true }); - mockOrganizationService.get$.mockReturnValue(of(organization as Organization)); + mockOrganizationService.organizations$.mockReturnValue( + of([organization]) as Observable, + ); cipherAuthorizationService.canDeleteCipher$(cipher, [], true).subscribe((result) => { expect(result).toBe(true); - expect(mockOrganizationService.get$).toHaveBeenCalledWith("org1"); + expect(mockOrganizationService.organizations$).toHaveBeenCalledWith(mockUserId); done(); }); }); @@ -95,7 +182,7 @@ describe("CipherAuthorizationService", () => { it("should return false if isAdminConsoleAction is true but user does not have permission to edit unassigned ciphers", (done) => { const cipher = createMockCipher("org1", []) as CipherView; const organization = createMockOrganization({ canEditUnassignedCiphers: false }); - mockOrganizationService.get$.mockReturnValue(of(organization as Organization)); + mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[])); cipherAuthorizationService.canDeleteCipher$(cipher, [], true).subscribe((result) => { expect(result).toBe(false); @@ -106,8 +193,8 @@ describe("CipherAuthorizationService", () => { it("should return true if activeCollectionId is provided and has manage permission", (done) => { const cipher = createMockCipher("org1", ["col1", "col2"]) as CipherView; const activeCollectionId = "col1" as CollectionId; - const org = createMockOrganization(); - mockOrganizationService.get$.mockReturnValue(of(org as Organization)); + const organization = createMockOrganization(); + mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[])); const allCollections = [ createMockCollection("col1", true), @@ -132,8 +219,8 @@ describe("CipherAuthorizationService", () => { it("should return false if activeCollectionId is provided and manage permission is not present", (done) => { const cipher = createMockCipher("org1", ["col1", "col2"]) as CipherView; const activeCollectionId = "col1" as CollectionId; - const org = createMockOrganization(); - mockOrganizationService.get$.mockReturnValue(of(org as Organization)); + const organization = createMockOrganization(); + mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[])); const allCollections = [ createMockCollection("col1", false), @@ -157,8 +244,8 @@ describe("CipherAuthorizationService", () => { it("should return true if any collection has manage permission", (done) => { const cipher = createMockCipher("org1", ["col1", "col2", "col3"]) as CipherView; - const org = createMockOrganization(); - mockOrganizationService.get$.mockReturnValue(of(org as Organization)); + const organization = createMockOrganization(); + mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[])); const allCollections = [ createMockCollection("col1", false), @@ -182,8 +269,8 @@ describe("CipherAuthorizationService", () => { it("should return false if no collection has manage permission", (done) => { const cipher = createMockCipher("org1", ["col1", "col2"]) as CipherView; - const org = createMockOrganization(); - mockOrganizationService.get$.mockReturnValue(of(org as Organization)); + const organization = createMockOrganization(); + mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[])); const allCollections = [ createMockCollection("col1", false), @@ -202,6 +289,34 @@ describe("CipherAuthorizationService", () => { done(); }); }); + + it("should return true if feature flag enabled and cipher.permissions.delete is true", (done) => { + const cipher = createMockCipher("org1", [], true, { + delete: true, + } as CipherPermissionsApi) as CipherView; + const organization = createMockOrganization(); + mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[])); + mockConfigService.getFeatureFlag$.mockReturnValue(of(true)); + + cipherAuthorizationService.canDeleteCipher$(cipher, [], false).subscribe((result) => { + expect(result).toBe(true); + expect(mockCollectionService.decryptedCollectionViews$).not.toHaveBeenCalled(); + done(); + }); + }); + + it("should return false if feature flag enabled and cipher.permissions.delete is false", (done) => { + const cipher = createMockCipher("org1", []) as CipherView; + const organization = createMockOrganization(); + mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[])); + mockConfigService.getFeatureFlag$.mockReturnValue(of(true)); + + cipherAuthorizationService.canDeleteCipher$(cipher, [], false).subscribe((result) => { + expect(result).toBe(false); + expect(mockCollectionService.decryptedCollectionViews$).not.toHaveBeenCalled(); + done(); + }); + }); }); describe("canCloneCipher$", () => { @@ -216,7 +331,9 @@ describe("CipherAuthorizationService", () => { it("should return true for admin users", async () => { const cipher = createMockCipher("org1", []) as CipherView; const organization = createMockOrganization({ isAdmin: true }); - mockOrganizationService.get$.mockReturnValue(of(organization as Organization)); + mockOrganizationService.organizations$.mockReturnValue( + of([organization] as Organization[]), + ); const result = await firstValueFrom( cipherAuthorizationService.canCloneCipher$(cipher, true), @@ -227,7 +344,9 @@ describe("CipherAuthorizationService", () => { it("should return true for custom user with canEditAnyCollection", async () => { const cipher = createMockCipher("org1", []) as CipherView; const organization = createMockOrganization({ editAnyCollection: true }); - mockOrganizationService.get$.mockReturnValue(of(organization as Organization)); + mockOrganizationService.organizations$.mockReturnValue( + of([organization] as Organization[]), + ); const result = await firstValueFrom( cipherAuthorizationService.canCloneCipher$(cipher, true), @@ -240,7 +359,9 @@ describe("CipherAuthorizationService", () => { it("should return true if at least one cipher collection has manage permission", async () => { const cipher = createMockCipher("org1", ["col1", "col2"]) as CipherView; const organization = createMockOrganization(); - mockOrganizationService.get$.mockReturnValue(of(organization as Organization)); + mockOrganizationService.organizations$.mockReturnValue( + of([organization] as Organization[]), + ); const allCollections = [ createMockCollection("col1", true), @@ -257,7 +378,9 @@ describe("CipherAuthorizationService", () => { it("should return false if no collection has manage permission", async () => { const cipher = createMockCipher("org1", ["col1", "col2"]) as CipherView; const organization = createMockOrganization(); - mockOrganizationService.get$.mockReturnValue(of(organization as Organization)); + mockOrganizationService.organizations$.mockReturnValue( + of([organization] as Organization[]), + ); const allCollections = [ createMockCollection("col1", false), diff --git a/libs/common/src/vault/services/cipher-authorization.service.ts b/libs/common/src/vault/services/cipher-authorization.service.ts index 260d1eeece7..b415760a035 100644 --- a/libs/common/src/vault/services/cipher-authorization.service.ts +++ b/libs/common/src/vault/services/cipher-authorization.service.ts @@ -1,11 +1,13 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { map, Observable, of, shareReplay, switchMap } from "rxjs"; +import { combineLatest, map, Observable, of, shareReplay, switchMap } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CollectionId } from "@bitwarden/common/types/guid"; +import { getUserId } from "../../auth/services/account.service"; +import { FeatureFlag } from "../../enums/feature-flag.enum"; import { Cipher } from "../models/domain/cipher"; import { CipherView } from "../models/view/cipher.view"; @@ -27,12 +29,25 @@ export abstract class CipherAuthorizationService { * * @returns {Observable} - An observable that emits a boolean value indicating if the user can delete the cipher. */ - canDeleteCipher$: ( + abstract canDeleteCipher$: ( cipher: CipherLike, allowedCollections?: CollectionId[], isAdminConsoleAction?: boolean, ) => Observable; + /** + * Determines if the user can restore the specified cipher. + * + * @param {CipherLike} cipher - The cipher object to evaluate for restore permissions. + * @param {boolean} isAdminConsoleAction - Optional. A flag indicating if the action is being performed from the admin console. + * + * @returns {Observable} - An observable that emits a boolean value indicating if the user can restore the cipher. + */ + abstract canRestoreCipher$: ( + cipher: CipherLike, + isAdminConsoleAction?: boolean, + ) => Observable; + /** * Determines if the user can clone the specified cipher. * @@ -41,7 +56,10 @@ export abstract class CipherAuthorizationService { * * @returns {Observable} - An observable that emits a boolean value indicating if the user can clone the cipher. */ - canCloneCipher$: (cipher: CipherLike, isAdminConsoleAction?: boolean) => Observable; + abstract canCloneCipher$: ( + cipher: CipherLike, + isAdminConsoleAction?: boolean, + ) => Observable; } /** @@ -51,8 +69,17 @@ export class DefaultCipherAuthorizationService implements CipherAuthorizationSer constructor( private collectionService: CollectionService, private organizationService: OrganizationService, + private accountService: AccountService, + private configService: ConfigService, ) {} + private organization$ = (cipher: CipherLike) => + this.accountService.activeAccount$.pipe( + getUserId, + switchMap((userId) => this.organizationService.organizations$(userId)), + map((orgs) => orgs.find((org) => org.id === cipher.organizationId)), + ); + /** * * {@link CipherAuthorizationService.canDeleteCipher$} @@ -62,12 +89,11 @@ export class DefaultCipherAuthorizationService implements CipherAuthorizationSer allowedCollections?: CollectionId[], isAdminConsoleAction?: boolean, ): Observable { - if (cipher.organizationId == null) { - return of(true); - } - - return this.organizationService.get$(cipher.organizationId).pipe( - switchMap((organization) => { + return combineLatest([ + this.organization$(cipher), + this.configService.getFeatureFlag$(FeatureFlag.LimitItemDeletion), + ]).pipe( + switchMap(([organization, featureFlagEnabled]) => { if (isAdminConsoleAction) { // If the user is an admin, they can delete an unassigned cipher if (!cipher.collectionIds || cipher.collectionIds.length === 0) { @@ -79,6 +105,14 @@ export class DefaultCipherAuthorizationService implements CipherAuthorizationSer } } + if (featureFlagEnabled) { + return of(cipher.permissions.delete); + } + + if (cipher.organizationId == null) { + return of(true); + } + return this.collectionService .decryptedCollectionViews$(cipher.collectionIds as CollectionId[]) .pipe( @@ -86,7 +120,7 @@ export class DefaultCipherAuthorizationService implements CipherAuthorizationSer const shouldFilter = allowedCollections?.some(Boolean); const collections = shouldFilter - ? allCollections.filter((c) => allowedCollections.includes(c.id as CollectionId)) + ? allCollections.filter((c) => allowedCollections?.includes(c.id as CollectionId)) : allCollections; return collections.some((collection) => collection.manage); @@ -96,6 +130,29 @@ export class DefaultCipherAuthorizationService implements CipherAuthorizationSer ); } + /** + * + * {@link CipherAuthorizationService.canRestoreCipher$} + */ + canRestoreCipher$(cipher: CipherLike, isAdminConsoleAction?: boolean): Observable { + return this.organization$(cipher).pipe( + map((organization) => { + if (isAdminConsoleAction) { + // If the user is an admin, they can restore an unassigned cipher + if (!cipher.collectionIds || cipher.collectionIds.length === 0) { + return organization?.canEditUnassignedCiphers === true; + } + + if (organization?.canEditAllCiphers) { + return true; + } + } + + return cipher.permissions.restore; + }), + ); + } + /** * {@link CipherAuthorizationService.canCloneCipher$} */ @@ -104,11 +161,12 @@ export class DefaultCipherAuthorizationService implements CipherAuthorizationSer return of(true); } - return this.organizationService.get$(cipher.organizationId).pipe( + return this.organization$(cipher).pipe( switchMap((organization) => { // Admins and custom users can always clone when in the Admin Console if ( isAdminConsoleAction && + organization && (organization.isAdmin || organization.permissions?.editAnyCollection) ) { return of(true); diff --git a/libs/common/src/vault/services/cipher.service.spec.ts b/libs/common/src/vault/services/cipher.service.spec.ts index e6ea762c125..def0c04dd16 100644 --- a/libs/common/src/vault/services/cipher.service.spec.ts +++ b/libs/common/src/vault/services/cipher.service.spec.ts @@ -1,12 +1,8 @@ import { mock } from "jest-mock-extended"; import { BehaviorSubject, map, of } from "rxjs"; -import { BulkEncryptService } from "@bitwarden/common/platform/abstractions/bulk-encrypt.service"; +import { CipherDecryptionKeys, KeyService } from "@bitwarden/key-management"; -import { - CipherDecryptionKeys, - KeyService, -} from "../../../../key-management/src/abstractions/key.service"; import { FakeAccountService, mockAccountServiceWith } from "../../../spec/fake-account-service"; import { FakeStateProvider } from "../../../spec/fake-state-provider"; import { makeStaticByteArray } from "../../../spec/utils"; @@ -14,9 +10,10 @@ import { ApiService } from "../../abstractions/api.service"; import { SearchService } from "../../abstractions/search.service"; import { AutofillSettingsService } from "../../autofill/services/autofill-settings.service"; import { DomainSettingsService } from "../../autofill/services/domain-settings.service"; +import { BulkEncryptService } from "../../key-management/crypto/abstractions/bulk-encrypt.service"; +import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service"; import { UriMatchStrategy } from "../../models/domain/domain-service"; import { ConfigService } from "../../platform/abstractions/config/config.service"; -import { EncryptService } from "../../platform/abstractions/encrypt.service"; import { I18nService } from "../../platform/abstractions/i18n.service"; import { StateService } from "../../platform/abstractions/state.service"; import { Utils } from "../../platform/misc/utils"; @@ -30,6 +27,7 @@ import { CipherFileUploadService } from "../abstractions/file-upload/cipher-file import { FieldType } from "../enums"; import { CipherRepromptType } from "../enums/cipher-reprompt-type"; import { CipherType } from "../enums/cipher-type"; +import { CipherPermissionsApi } from "../models/api/cipher-permissions.api"; import { CipherData } from "../models/data/cipher.data"; import { Cipher } from "../models/domain/cipher"; import { CipherCreateRequest } from "../models/request/cipher-create.request"; @@ -60,6 +58,7 @@ const cipherData: CipherData = { notes: "EncryptedString", creationDate: "2022-01-01T12:00:00.000Z", deletedDate: null, + permissions: new CipherPermissionsApi(), key: "EncKey", reprompt: CipherRepromptType.None, login: { @@ -366,7 +365,8 @@ describe("Cipher Service", () => { configService.getFeatureFlag.mockResolvedValue(true); configService.checkServerMeetsVersionRequirement$.mockReturnValue(of(true)); - searchService.indexedEntityId$ = of(null); + searchService.indexedEntityId$.mockReturnValue(of(null)); + stateService.getUserId.mockResolvedValue(mockUserId); const keys = { @@ -385,8 +385,16 @@ describe("Cipher Service", () => { Cipher1: cipher1, Cipher2: cipher2, }); - cipherService.cipherViews$ = decryptedCiphers.pipe(map((ciphers) => Object.values(ciphers))); - cipherService.failedToDecryptCiphers$ = failedCiphers = new BehaviorSubject([]); + jest + .spyOn(cipherService, "cipherViews$") + .mockImplementation((userId: UserId) => + decryptedCiphers.pipe(map((ciphers) => Object.values(ciphers))), + ); + + failedCiphers = new BehaviorSubject([]); + jest + .spyOn(cipherService, "failedToDecryptCiphers$") + .mockImplementation((userId: UserId) => failedCiphers); encryptService.decryptToBytes.mockResolvedValue(new Uint8Array(32)); encryptedKey = new EncString("Re-encrypted Cipher Key"); diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index 36db5b87079..d774277c4a0 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -14,21 +14,21 @@ import { } from "rxjs"; import { SemVer } from "semver"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { BulkEncryptService } from "@bitwarden/common/platform/abstractions/bulk-encrypt.service"; +import { KeyService } from "@bitwarden/key-management"; -import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { ApiService } from "../../abstractions/api.service"; import { SearchService } from "../../abstractions/search.service"; +import { AccountService } from "../../auth/abstractions/account.service"; import { AutofillSettingsServiceAbstraction } from "../../autofill/services/autofill-settings.service"; import { DomainSettingsService } from "../../autofill/services/domain-settings.service"; +import { FeatureFlag } from "../../enums/feature-flag.enum"; +import { BulkEncryptService } from "../../key-management/crypto/abstractions/bulk-encrypt.service"; +import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service"; import { UriMatchStrategySetting } from "../../models/domain/domain-service"; import { ErrorResponse } from "../../models/response/error.response"; import { ListResponse } from "../../models/response/list.response"; import { View } from "../../models/view/view"; import { ConfigService } from "../../platform/abstractions/config/config.service"; -import { EncryptService } from "../../platform/abstractions/encrypt.service"; import { I18nService } from "../../platform/abstractions/i18n.service"; import { StateService } from "../../platform/abstractions/state.service"; import { sequentialize } from "../../platform/misc/sequentialize"; @@ -37,7 +37,7 @@ import Domain from "../../platform/models/domain/domain-base"; import { EncArrayBuffer } from "../../platform/models/domain/enc-array-buffer"; import { EncString } from "../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key"; -import { ActiveUserState, StateProvider } from "../../platform/state"; +import { StateProvider } from "../../platform/state"; import { CipherId, CollectionId, OrganizationId, UserId } from "../../types/guid"; import { OrgKey, UserKey } from "../../types/key"; import { CipherService as CipherServiceAbstraction } from "../abstractions/cipher.service"; @@ -97,33 +97,6 @@ export class CipherService implements CipherServiceAbstraction { */ private forceCipherViews$: Subject = new Subject(); - localData$: Observable>; - ciphers$: Observable>; - - /** - * Observable that emits an array of decrypted ciphers for the active user. - * This observable will not emit until the encrypted ciphers have either been loaded from state or after sync. - * - * A `null` value indicates that the latest encrypted ciphers have not been decrypted yet and that - * decryption is in progress. The latest decrypted ciphers will be emitted once decryption is complete. - * - */ - cipherViews$: Observable; - addEditCipherInfo$: Observable; - - /** - * Observable that emits an array of cipherViews that failed to decrypt. Does not emit until decryption has completed. - * - * An empty array indicates that all ciphers were successfully decrypted. - */ - failedToDecryptCiphers$: Observable; - - private localDataState: ActiveUserState>; - private encryptedCiphersState: ActiveUserState>; - private decryptedCiphersState: ActiveUserState>; - private failedToDecryptCiphersState: ActiveUserState; - private addEditCipherInfoState: ActiveUserState; - constructor( private keyService: KeyService, private domainSettingsService: DomainSettingsService, @@ -138,30 +111,49 @@ export class CipherService implements CipherServiceAbstraction { private configService: ConfigService, private stateProvider: StateProvider, private accountService: AccountService, - ) { - this.localDataState = this.stateProvider.getActive(LOCAL_DATA_KEY); - this.encryptedCiphersState = this.stateProvider.getActive(ENCRYPTED_CIPHERS); - this.decryptedCiphersState = this.stateProvider.getActive(DECRYPTED_CIPHERS); - this.failedToDecryptCiphersState = this.stateProvider.getActive(FAILED_DECRYPTED_CIPHERS); - this.addEditCipherInfoState = this.stateProvider.getActive(ADD_EDIT_CIPHER_INFO_KEY); + ) {} - this.localData$ = this.localDataState.state$.pipe(map((data) => data ?? {})); - this.ciphers$ = this.encryptedCiphersState.state$.pipe(map((ciphers) => ciphers ?? {})); + localData$(userId: UserId): Observable> { + return this.localDataState(userId).state$.pipe(map((data) => data ?? {})); + } - // Decrypted ciphers depend on both ciphers and local data and need to be updated when either changes - this.cipherViews$ = combineLatest([this.encryptedCiphersState.state$, this.localData$]).pipe( + /** + * Observable that emits an object of encrypted ciphers for the active user. + */ + ciphers$(userId: UserId): Observable> { + return this.encryptedCiphersState(userId).state$.pipe(map((ciphers) => ciphers ?? {})); + } + + /** + * Observable that emits an array of decrypted ciphers for the active user. + * This observable will not emit until the encrypted ciphers have either been loaded from state or after sync. + * + * A `null` value indicates that the latest encrypted ciphers have not been decrypted yet and that + * decryption is in progress. The latest decrypted ciphers will be emitted once decryption is complete. + */ + cipherViews$(userId: UserId): Observable { + return combineLatest([this.encryptedCiphersState(userId).state$, this.localData$(userId)]).pipe( filter(([ciphers]) => ciphers != null), // Skip if ciphers haven't been loaded yor synced yet - switchMap(() => merge(this.forceCipherViews$, this.getAllDecrypted())), + switchMap(() => merge(this.forceCipherViews$, this.getAllDecrypted(userId))), shareReplay({ bufferSize: 1, refCount: true }), ); + } - this.failedToDecryptCiphers$ = this.failedToDecryptCiphersState.state$.pipe( + addEditCipherInfo$(userId: UserId): Observable { + return this.addEditCipherInfoState(userId).state$; + } + + /** + * Observable that emits an array of cipherViews that failed to decrypt. Does not emit until decryption has completed. + * + * An empty array indicates that all ciphers were successfully decrypted. + */ + failedToDecryptCiphers$(userId: UserId): Observable { + return this.failedToDecryptCiphersState(userId).state$.pipe( filter((ciphers) => ciphers != null), switchMap((ciphers) => merge(this.forceCipherViews$, of(ciphers))), shareReplay({ bufferSize: 1, refCount: true }), ); - - this.addEditCipherInfo$ = this.addEditCipherInfoState.state$; } async setDecryptedCipherCache(value: CipherView[], userId: UserId) { @@ -173,9 +165,9 @@ export class CipherService implements CipherServiceAbstraction { } if (this.searchService != null) { if (value == null) { - await this.searchService.clearIndex(); + await this.searchService.clearIndex(userId); } else { - await this.searchService.indexCiphers(value); + await this.searchService.indexCiphers(userId, value); } } } @@ -212,7 +204,7 @@ export class CipherService implements CipherServiceAbstraction { ): Promise { if (model.id != null) { if (originalCipher == null) { - originalCipher = await this.get(model.id); + originalCipher = await this.get(model.id, userId); } if (originalCipher != null) { await this.updateModelfromExistingCipher(model, originalCipher, userId); @@ -366,22 +358,22 @@ export class CipherService implements CipherServiceAbstraction { return ph; } - async get(id: string): Promise { - const ciphers = await firstValueFrom(this.ciphers$); + async get(id: string, userId: UserId): Promise { + const ciphers = await firstValueFrom(this.ciphers$(userId)); // eslint-disable-next-line if (ciphers == null || !ciphers.hasOwnProperty(id)) { return null; } - const localData = await firstValueFrom(this.localData$); + const localData = await firstValueFrom(this.localData$(userId)); const cipherId = id as CipherId; return new Cipher(ciphers[cipherId], localData ? localData[cipherId] : null); } - async getAll(): Promise { - const localData = await firstValueFrom(this.localData$); - const ciphers = await firstValueFrom(this.ciphers$); + async getAll(userId: UserId): Promise { + const localData = await firstValueFrom(this.localData$(userId)); + const ciphers = await firstValueFrom(this.ciphers$(userId)); const response: Cipher[] = []; for (const id in ciphers) { // eslint-disable-next-line @@ -399,33 +391,27 @@ export class CipherService implements CipherServiceAbstraction { * @deprecated Use `cipherViews$` observable instead */ @sequentialize(() => "getAllDecrypted") - async getAllDecrypted(): Promise { - const decCiphers = await this.getDecryptedCiphers(); + async getAllDecrypted(userId: UserId): Promise { + const decCiphers = await this.getDecryptedCiphers(userId); if (decCiphers != null && decCiphers.length !== 0) { - await this.reindexCiphers(); - return await this.getDecryptedCiphers(); - } - - const activeUserId = await firstValueFrom(this.stateProvider.activeUserId$); - - if (activeUserId == null) { - return []; + await this.reindexCiphers(userId); + return await this.getDecryptedCiphers(userId); } const [newDecCiphers, failedCiphers] = await this.decryptCiphers( - await this.getAll(), - activeUserId, + await this.getAll(userId), + userId, ); - await this.setDecryptedCipherCache(newDecCiphers, activeUserId); - await this.setFailedDecryptedCiphers(failedCiphers, activeUserId); + await this.setDecryptedCipherCache(newDecCiphers, userId); + await this.setFailedDecryptedCiphers(failedCiphers, userId); return newDecCiphers; } - private async getDecryptedCiphers() { + private async getDecryptedCiphers(userId: UserId) { return Object.values( - await firstValueFrom(this.decryptedCiphersState.state$.pipe(map((c) => c ?? {}))), + await firstValueFrom(this.decryptedCiphersState(userId).state$.pipe(map((c) => c ?? {}))), ); } @@ -444,7 +430,7 @@ export class CipherService implements CipherServiceAbstraction { if (keys == null || (keys.userKey == null && Object.keys(keys.orgKeys).length === 0)) { // return early if there are no keys to decrypt with - return; + return [[], []]; } // Group ciphers by orgId or under 'null' for the user's ciphers @@ -491,18 +477,21 @@ export class CipherService implements CipherServiceAbstraction { ); } - private async reindexCiphers() { - const userId = await this.stateService.getUserId(); + private async reindexCiphers(userId: UserId) { const reindexRequired = this.searchService != null && - ((await firstValueFrom(this.searchService.indexedEntityId$)) ?? userId) !== userId; + ((await firstValueFrom(this.searchService.indexedEntityId$(userId))) ?? userId) !== userId; if (reindexRequired) { - await this.searchService.indexCiphers(await this.getDecryptedCiphers(), userId); + await this.searchService.indexCiphers(userId, await this.getDecryptedCiphers(userId), userId); } } - async getAllDecryptedForGrouping(groupingId: string, folder = true): Promise { - const ciphers = await this.getAllDecrypted(); + async getAllDecryptedForGrouping( + groupingId: string, + userId: UserId, + folder = true, + ): Promise { + const ciphers = await this.getAllDecrypted(userId); return ciphers.filter((cipher) => { if (cipher.isDeleted) { @@ -524,10 +513,11 @@ export class CipherService implements CipherServiceAbstraction { async getAllDecryptedForUrl( url: string, + userId: UserId, includeOtherTypes?: CipherType[], defaultMatch: UriMatchStrategySetting = null, ): Promise { - const ciphers = await this.getAllDecrypted(); + const ciphers = await this.getAllDecrypted(userId); return await this.filterCiphersForUrl(ciphers, url, includeOtherTypes, defaultMatch); } @@ -569,8 +559,11 @@ export class CipherService implements CipherServiceAbstraction { }); } - private async getAllDecryptedCiphersOfType(type: CipherType[]): Promise { - const ciphers = await this.getAllDecrypted(); + private async getAllDecryptedCiphersOfType( + type: CipherType[], + userId: UserId, + ): Promise { + const ciphers = await this.getAllDecrypted(userId); return ciphers .filter((cipher) => cipher.deletedDate == null && type.includes(cipher.type)) .sort((a, b) => this.sortCiphersByLastUsedThenName(a, b)); @@ -613,23 +606,31 @@ export class CipherService implements CipherServiceAbstraction { return decCiphers; } - async getLastUsedForUrl(url: string, autofillOnPageLoad = false): Promise { - return this.getCipherForUrl(url, true, false, autofillOnPageLoad); + async getLastUsedForUrl( + url: string, + userId: UserId, + autofillOnPageLoad = false, + ): Promise { + return this.getCipherForUrl(url, userId, true, false, autofillOnPageLoad); } - async getLastLaunchedForUrl(url: string, autofillOnPageLoad = false): Promise { - return this.getCipherForUrl(url, false, true, autofillOnPageLoad); + async getLastLaunchedForUrl( + url: string, + userId: UserId, + autofillOnPageLoad = false, + ): Promise { + return this.getCipherForUrl(url, userId, false, true, autofillOnPageLoad); } - async getNextCipherForUrl(url: string): Promise { - return this.getCipherForUrl(url, false, false, false); + async getNextCipherForUrl(url: string, userId: UserId): Promise { + return this.getCipherForUrl(url, userId, false, false, false); } - async getNextCardCipher(): Promise { + async getNextCardCipher(userId: UserId): Promise { const cacheKey = "cardCiphers"; if (!this.sortedCiphersCache.isCached(cacheKey)) { - const ciphers = await this.getAllDecryptedCiphersOfType([CipherType.Card]); + const ciphers = await this.getAllDecryptedCiphersOfType([CipherType.Card], userId); if (!ciphers?.length) { return null; } @@ -640,11 +641,11 @@ export class CipherService implements CipherServiceAbstraction { return this.sortedCiphersCache.getNext(cacheKey); } - async getNextIdentityCipher(): Promise { + async getNextIdentityCipher(userId: UserId): Promise { const cacheKey = "identityCiphers"; if (!this.sortedCiphersCache.isCached(cacheKey)) { - const ciphers = await this.getAllDecryptedCiphersOfType([CipherType.Identity]); + const ciphers = await this.getAllDecryptedCiphersOfType([CipherType.Identity], userId); if (!ciphers?.length) { return null; } @@ -659,9 +660,8 @@ export class CipherService implements CipherServiceAbstraction { this.sortedCiphersCache.updateLastUsedIndex(url); } - async updateLastUsedDate(id: string): Promise { - const userId = await firstValueFrom(this.stateProvider.activeUserId$); - let ciphersLocalData = await firstValueFrom(this.localData$); + async updateLastUsedDate(id: string, userId: UserId): Promise { + let ciphersLocalData = await firstValueFrom(this.localData$(userId)); if (!ciphersLocalData) { ciphersLocalData = {}; @@ -676,9 +676,9 @@ export class CipherService implements CipherServiceAbstraction { }; } - await this.localDataState.update(() => ciphersLocalData); + await this.localDataState(userId).update(() => ciphersLocalData); - const decryptedCipherCache = await this.getDecryptedCiphers(); + const decryptedCipherCache = await this.getDecryptedCiphers(userId); if (!decryptedCipherCache) { return; } @@ -693,9 +693,8 @@ export class CipherService implements CipherServiceAbstraction { await this.setDecryptedCiphers(decryptedCipherCache, userId); } - async updateLastLaunchedDate(id: string): Promise { - const userId = await firstValueFrom(this.stateProvider.activeUserId$); - let ciphersLocalData = await firstValueFrom(this.localData$); + async updateLastLaunchedDate(id: string, userId: UserId): Promise { + let ciphersLocalData = await firstValueFrom(this.localData$(userId)); if (!ciphersLocalData) { ciphersLocalData = {}; @@ -707,9 +706,9 @@ export class CipherService implements CipherServiceAbstraction { lastUsedDate: currentTime, }; - await this.localDataState.update(() => ciphersLocalData); + await this.localDataState(userId).update(() => ciphersLocalData); - const decryptedCipherCache = await this.getDecryptedCiphers(); + const decryptedCipherCache = await this.getDecryptedCiphers(userId); if (!decryptedCipherCache) { return; } @@ -785,7 +784,7 @@ export class CipherService implements CipherServiceAbstraction { organizationId: string, collectionIds: string[], userId: UserId, - ): Promise { + ): Promise { const attachmentPromises: Promise[] = []; if (cipher.attachments != null) { cipher.attachments.forEach((attachment) => { @@ -805,6 +804,7 @@ export class CipherService implements CipherServiceAbstraction { const response = await this.apiService.putShareCipher(cipher.id, request); const data = new CipherData(response, collectionIds); await this.upsert(data); + return new Cipher(data, cipher.localData); } async shareManyWithServer( @@ -913,13 +913,13 @@ export class CipherService implements CipherServiceAbstraction { return new Cipher(cData); } - async saveCollectionsWithServer(cipher: Cipher): Promise { + async saveCollectionsWithServer(cipher: Cipher, userId: UserId): Promise { const request = new CipherCollectionsRequest(cipher.collectionIds); const response = await this.apiService.putCipherCollections(cipher.id, request); // The response will now check for an unavailable value. This value determines whether // the user still has Can Manage access to the item after updating. if (response.unavailable) { - await this.delete(cipher.id); + await this.delete(cipher.id, userId); return; } const data = new CipherData(response.cipher); @@ -943,6 +943,7 @@ export class CipherService implements CipherServiceAbstraction { */ async bulkUpdateCollectionsWithServer( orgId: OrganizationId, + userId: UserId, cipherIds: CipherId[], collectionIds: CollectionId[], removeCollections: boolean = false, @@ -957,7 +958,7 @@ export class CipherService implements CipherServiceAbstraction { await this.apiService.send("POST", "/ciphers/bulk-collections", request, true, false); // Update the local state - const ciphers = await firstValueFrom(this.ciphers$); + const ciphers = await firstValueFrom(this.ciphers$(userId)); for (const id of cipherIds) { const cipher = ciphers[id]; @@ -974,15 +975,19 @@ export class CipherService implements CipherServiceAbstraction { } await this.clearCache(); - await this.encryptedCiphersState.update(() => ciphers); + await this.encryptedCiphersState(userId).update(() => ciphers); } async upsert(cipher: CipherData | CipherData[]): Promise> { const ciphers = cipher instanceof CipherData ? [cipher] : cipher; - return await this.updateEncryptedCipherState((current) => { + const res = await this.updateEncryptedCipherState((current) => { ciphers.forEach((c) => (current[c.id as CipherId] = c)); return current; }); + // Some state storage providers (e.g. Electron) don't update the state immediately, wait for next tick + // Otherwise, subscribers to cipherViews$ can get stale data + await new Promise((resolve) => setTimeout(resolve, 0)); + return res; } async replace(ciphers: { [id: string]: CipherData }, userId: UserId): Promise { @@ -999,13 +1004,16 @@ export class CipherService implements CipherServiceAbstraction { userId: UserId = null, ): Promise> { userId ||= await firstValueFrom(this.stateProvider.activeUserId$); - await this.clearDecryptedCiphersState(userId); + await this.clearCache(userId); const updatedCiphers = await this.stateProvider .getUser(userId, ENCRYPTED_CIPHERS) .update((current) => { const result = update(current ?? {}); return result; }); + // Some state storage providers (e.g. Electron) don't update the state immediately, wait for next tick + // Otherwise, subscribers to cipherViews$ can get stale data + await new Promise((resolve) => setTimeout(resolve, 0)); return updatedCiphers; } @@ -1015,10 +1023,10 @@ export class CipherService implements CipherServiceAbstraction { await this.clearCache(userId); } - async moveManyWithServer(ids: string[], folderId: string): Promise { + async moveManyWithServer(ids: string[], folderId: string, userId: UserId): Promise { await this.apiService.putMoveCiphers(new CipherBulkMoveRequest(ids, folderId)); - let ciphers = await firstValueFrom(this.ciphers$); + let ciphers = await firstValueFrom(this.ciphers$(userId)); if (ciphers == null) { ciphers = {}; } @@ -1031,11 +1039,11 @@ export class CipherService implements CipherServiceAbstraction { }); await this.clearCache(); - await this.encryptedCiphersState.update(() => ciphers); + await this.encryptedCiphersState(userId).update(() => ciphers); } - async delete(id: string | string[]): Promise { - const ciphers = await firstValueFrom(this.ciphers$); + async delete(id: string | string[], userId: UserId): Promise { + const ciphers = await firstValueFrom(this.ciphers$(userId)); if (ciphers == null) { return; } @@ -1053,31 +1061,36 @@ export class CipherService implements CipherServiceAbstraction { } await this.clearCache(); - await this.encryptedCiphersState.update(() => ciphers); + await this.encryptedCiphersState(userId).update(() => ciphers); } - async deleteWithServer(id: string, asAdmin = false): Promise { + async deleteWithServer(id: string, userId: UserId, asAdmin = false): Promise { if (asAdmin) { await this.apiService.deleteCipherAdmin(id); } else { await this.apiService.deleteCipher(id); } - await this.delete(id); + await this.delete(id, userId); } - async deleteManyWithServer(ids: string[], asAdmin = false): Promise { + async deleteManyWithServer(ids: string[], userId: UserId, asAdmin = false): Promise { const request = new CipherBulkDeleteRequest(ids); if (asAdmin) { await this.apiService.deleteManyCiphersAdmin(request); } else { await this.apiService.deleteManyCiphers(request); } - await this.delete(ids); + await this.delete(ids, userId); } - async deleteAttachment(id: string, attachmentId: string): Promise { - let ciphers = await firstValueFrom(this.ciphers$); + async deleteAttachment( + id: string, + revisionDate: string, + attachmentId: string, + userId: UserId, + ): Promise { + let ciphers = await firstValueFrom(this.ciphers$(userId)); const cipherId = id as CipherId; // eslint-disable-next-line if (ciphers == null || !ciphers.hasOwnProperty(id) || ciphers[cipherId].attachments == null) { @@ -1090,22 +1103,35 @@ export class CipherService implements CipherServiceAbstraction { } } + // Deleting the cipher updates the revision date on the server, + // Update the stored `revisionDate` to match + ciphers[cipherId].revisionDate = revisionDate; + await this.clearCache(); - await this.encryptedCiphersState.update(() => { + await this.encryptedCiphersState(userId).update(() => { if (ciphers == null) { ciphers = {}; } return ciphers; }); + + return ciphers[cipherId]; } - async deleteAttachmentWithServer(id: string, attachmentId: string): Promise { + async deleteAttachmentWithServer( + id: string, + attachmentId: string, + userId: UserId, + ): Promise { + let cipherResponse = null; try { - await this.apiService.deleteCipherAttachment(id, attachmentId); + cipherResponse = await this.apiService.deleteCipherAttachment(id, attachmentId); } catch (e) { return Promise.reject((e as ErrorResponse).getSingleMessage()); } - await this.deleteAttachment(id, attachmentId); + const cipherData = CipherData.fromJSON(cipherResponse?.cipher); + + return await this.deleteAttachment(id, cipherData.revisionDate, attachmentId, userId); } sortCiphersByLastUsed(a: CipherView, b: CipherView): number { @@ -1178,8 +1204,8 @@ export class CipherService implements CipherServiceAbstraction { }; } - async softDelete(id: string | string[]): Promise { - let ciphers = await firstValueFrom(this.ciphers$); + async softDelete(id: string | string[], userId: UserId): Promise { + let ciphers = await firstValueFrom(this.ciphers$(userId)); if (ciphers == null) { return; } @@ -1198,7 +1224,7 @@ export class CipherService implements CipherServiceAbstraction { } await this.clearCache(); - await this.encryptedCiphersState.update(() => { + await this.encryptedCiphersState(userId).update(() => { if (ciphers == null) { ciphers = {}; } @@ -1206,17 +1232,17 @@ export class CipherService implements CipherServiceAbstraction { }); } - async softDeleteWithServer(id: string, asAdmin = false): Promise { + async softDeleteWithServer(id: string, userId: UserId, asAdmin = false): Promise { if (asAdmin) { await this.apiService.putDeleteCipherAdmin(id); } else { await this.apiService.putDeleteCipher(id); } - await this.softDelete(id); + await this.softDelete(id, userId); } - async softDeleteManyWithServer(ids: string[], asAdmin = false): Promise { + async softDeleteManyWithServer(ids: string[], userId: UserId, asAdmin = false): Promise { const request = new CipherBulkDeleteRequest(ids); if (asAdmin) { await this.apiService.putDeleteManyCiphersAdmin(request); @@ -1224,13 +1250,14 @@ export class CipherService implements CipherServiceAbstraction { await this.apiService.putDeleteManyCiphers(request); } - await this.softDelete(ids); + await this.softDelete(ids, userId); } async restore( cipher: { id: string; revisionDate: string } | { id: string; revisionDate: string }[], + userId: UserId, ) { - let ciphers = await firstValueFrom(this.ciphers$); + let ciphers = await firstValueFrom(this.ciphers$(userId)); if (ciphers == null) { return; } @@ -1251,7 +1278,7 @@ export class CipherService implements CipherServiceAbstraction { } await this.clearCache(); - await this.encryptedCiphersState.update(() => { + await this.encryptedCiphersState(userId).update(() => { if (ciphers == null) { ciphers = {}; } @@ -1259,7 +1286,7 @@ export class CipherService implements CipherServiceAbstraction { }); } - async restoreWithServer(id: string, asAdmin = false): Promise { + async restoreWithServer(id: string, userId: UserId, asAdmin = false): Promise { let response; if (asAdmin) { response = await this.apiService.putRestoreCipherAdmin(id); @@ -1267,14 +1294,14 @@ export class CipherService implements CipherServiceAbstraction { response = await this.apiService.putRestoreCipher(id); } - await this.restore({ id: id, revisionDate: response.revisionDate }); + await this.restore({ id: id, revisionDate: response.revisionDate }, userId); } /** * No longer using an asAdmin Param. Org Vault bulkRestore will assess if an item is unassigned or editable * The Org Vault will pass those ids an array as well as the orgId when calling bulkRestore */ - async restoreManyWithServer(ids: string[], orgId: string = null): Promise { + async restoreManyWithServer(ids: string[], userId: UserId, orgId: string = null): Promise { let response; if (orgId) { @@ -1289,7 +1316,7 @@ export class CipherService implements CipherServiceAbstraction { for (const cipher of response.data) { restores.push({ id: cipher.id, revisionDate: cipher.revisionDate }); } - await this.restore(restores); + await this.restore(restores, userId); } async getKeyForCipherKeyDecryption(cipher: Cipher, userId: UserId): Promise { @@ -1299,8 +1326,8 @@ export class CipherService implements CipherServiceAbstraction { ); } - async setAddEditCipherInfo(value: AddEditCipherInfo) { - await this.addEditCipherInfoState.update(() => value, { + async setAddEditCipherInfo(value: AddEditCipherInfo, userId: UserId) { + await this.addEditCipherInfoState(userId).update(() => value, { shouldUpdate: (current) => !(current == null && value == null), }); } @@ -1319,8 +1346,8 @@ export class CipherService implements CipherServiceAbstraction { let encryptedCiphers: CipherWithIdRequest[] = []; - const ciphers = await firstValueFrom(this.cipherViews$); - const failedCiphers = await firstValueFrom(this.failedToDecryptCiphers$); + const ciphers = await firstValueFrom(this.cipherViews$(userId)); + const failedCiphers = await firstValueFrom(this.failedToDecryptCiphers$(userId)); if (!ciphers) { return encryptedCiphers; } @@ -1343,6 +1370,41 @@ export class CipherService implements CipherServiceAbstraction { return encryptedCiphers; } + /** + * @returns a SingleUserState + */ + private localDataState(userId: UserId) { + return this.stateProvider.getUser(userId, LOCAL_DATA_KEY); + } + + /** + * @returns a SingleUserState for the encrypted ciphers + */ + private encryptedCiphersState(userId: UserId) { + return this.stateProvider.getUser(userId, ENCRYPTED_CIPHERS); + } + + /** + * @returns a SingleUserState for the decrypted ciphers + */ + private decryptedCiphersState(userId: UserId) { + return this.stateProvider.getUser(userId, DECRYPTED_CIPHERS); + } + + /** + * @returns a SingleUserState for the add/edit cipher info + */ + private addEditCipherInfoState(userId: UserId) { + return this.stateProvider.getUser(userId, ADD_EDIT_CIPHER_INFO_KEY); + } + + /** + * @returns a SingleUserState for the failed to decrypt ciphers + */ + private failedToDecryptCiphersState(userId: UserId) { + return this.stateProvider.getUser(userId, FAILED_DECRYPTED_CIPHERS); + } + // Helpers // In the case of a cipher that is being shared with an organization, we want to decrypt the @@ -1646,6 +1708,7 @@ export class CipherService implements CipherServiceAbstraction { private async getCipherForUrl( url: string, + userId: UserId, lastUsed: boolean, lastLaunched: boolean, autofillOnPageLoad: boolean, @@ -1653,7 +1716,7 @@ export class CipherService implements CipherServiceAbstraction { const cacheKey = autofillOnPageLoad ? "autofillOnPageLoad-" + url : url; if (!this.sortedCiphersCache.isCached(cacheKey)) { - let ciphers = await this.getAllDecryptedForUrl(url); + let ciphers = await this.getAllDecryptedForUrl(url, userId); if (!ciphers) { return null; } diff --git a/libs/common/src/vault/services/folder/folder-api.service.ts b/libs/common/src/vault/services/folder/folder-api.service.ts index 24831393668..fe9c3218a84 100644 --- a/libs/common/src/vault/services/folder/folder-api.service.ts +++ b/libs/common/src/vault/services/folder/folder-api.service.ts @@ -1,6 +1,5 @@ -import { UserId } from "@bitwarden/common/types/guid"; - import { ApiService } from "../../../abstractions/api.service"; +import { UserId } from "../../../types/guid"; import { FolderApiServiceAbstraction } from "../../../vault/abstractions/folder/folder-api.service.abstraction"; import { InternalFolderService } from "../../../vault/abstractions/folder/folder.service.abstraction"; import { FolderData } from "../../../vault/models/data/folder.data"; @@ -14,7 +13,7 @@ export class FolderApiService implements FolderApiServiceAbstraction { private apiService: ApiService, ) {} - async save(folder: Folder, userId: UserId): Promise { + async save(folder: Folder, userId: UserId): Promise { const request = new FolderRequest(folder); let response: FolderResponse; @@ -27,6 +26,7 @@ export class FolderApiService implements FolderApiServiceAbstraction { const data = new FolderData(response); await this.folderService.upsert(data, userId); + return data; } async delete(id: string, userId: UserId): Promise { diff --git a/libs/common/src/vault/services/folder/folder.service.spec.ts b/libs/common/src/vault/services/folder/folder.service.spec.ts index 612cd83d99b..ced4e2dceb7 100644 --- a/libs/common/src/vault/services/folder/folder.service.spec.ts +++ b/libs/common/src/vault/services/folder/folder.service.spec.ts @@ -1,12 +1,13 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject, firstValueFrom } from "rxjs"; -import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; +import { KeyService } from "@bitwarden/key-management"; + import { makeEncString } from "../../../../spec"; import { FakeAccountService, mockAccountServiceWith } from "../../../../spec/fake-account-service"; import { FakeSingleUserState } from "../../../../spec/fake-state"; import { FakeStateProvider } from "../../../../spec/fake-state-provider"; -import { EncryptService } from "../../../platform/abstractions/encrypt.service"; +import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service"; import { I18nService } from "../../../platform/abstractions/i18n.service"; import { Utils } from "../../../platform/misc/utils"; import { EncString } from "../../../platform/models/domain/enc-string"; @@ -75,7 +76,7 @@ describe("Folder Service", () => { const result = await firstValueFrom(folderService.folders$(mockUserId)); expect(result.length).toBe(2); - expect(result).toIncludeAllPartialMembers([ + expect(result).toContainPartialObjects([ { id: "1", name: makeEncString("ENC_STRING_1") }, { id: "2", name: makeEncString("ENC_STRING_2") }, ]); @@ -96,7 +97,7 @@ describe("Folder Service", () => { const result = await firstValueFrom(folderService.folderViews$(mockUserId)); expect(result.length).toBe(3); - expect(result).toIncludeAllPartialMembers([ + expect(result).toContainPartialObjects([ { id: "1", name: "DEC" }, { id: "2", name: "DEC" }, { name: "No Folder" }, diff --git a/libs/common/src/vault/services/folder/folder.service.ts b/libs/common/src/vault/services/folder/folder.service.ts index 3aac5374fcb..89bc5353f88 100644 --- a/libs/common/src/vault/services/folder/folder.service.ts +++ b/libs/common/src/vault/services/folder/folder.service.ts @@ -2,11 +2,11 @@ // @ts-strict-ignore import { Observable, Subject, firstValueFrom, map, shareReplay, switchMap, merge } from "rxjs"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { KeyService } from "@bitwarden/key-management"; -import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; +import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service"; import { I18nService } from "../../../platform/abstractions/i18n.service"; +import { Utils } from "../../../platform/misc/utils"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { StateProvider } from "../../../platform/state"; import { UserId } from "../../../types/guid"; @@ -190,7 +190,7 @@ export class FolderService implements InternalFolderServiceAbstraction { }); // Items in a deleted folder are re-assigned to "No Folder" - const ciphers = await this.cipherService.getAll(); + const ciphers = await this.cipherService.getAll(userId); if (ciphers != null) { const updates: Cipher[] = []; for (const cId in ciphers) { diff --git a/libs/common/src/vault/services/key-state/folder.state.ts b/libs/common/src/vault/services/key-state/folder.state.ts index 99ad8e5ae35..b3e61f5bf31 100644 --- a/libs/common/src/vault/services/key-state/folder.state.ts +++ b/libs/common/src/vault/services/key-state/folder.state.ts @@ -6,7 +6,7 @@ import { FolderView } from "../../models/view/folder.view"; export const FOLDER_ENCRYPTED_FOLDERS = UserKeyDefinition.record( FOLDER_DISK, - "folder", + "folders", { deserializer: (obj: Jsonify) => FolderData.fromJSON(obj), clearOn: ["logout"], diff --git a/libs/common/src/vault/services/totp.service.spec.ts b/libs/common/src/vault/services/totp.service.spec.ts index 71e3ce80b18..c653b4ce1db 100644 --- a/libs/common/src/vault/services/totp.service.spec.ts +++ b/libs/common/src/vault/services/totp.service.spec.ts @@ -1,17 +1,39 @@ import { mock } from "jest-mock-extended"; +import { of, take } from "rxjs"; -import { LogService } from "../../platform/abstractions/log.service"; -import { WebCryptoFunctionService } from "../../platform/services/web-crypto-function.service"; +import { BitwardenClient, TotpResponse } from "@bitwarden/sdk-internal"; + +import { SdkService } from "../../platform/abstractions/sdk/sdk.service"; import { TotpService } from "./totp.service"; describe("TotpService", () => { let totpService: TotpService; + let generateTotpMock: jest.Mock; - const logService = mock(); + const sdkService = mock(); beforeEach(() => { - totpService = new TotpService(new WebCryptoFunctionService(global), logService); + generateTotpMock = jest + .fn() + .mockReturnValueOnce({ + code: "123456", + period: 30, + }) + .mockReturnValueOnce({ code: "654321", period: 30 }) + .mockReturnValueOnce({ code: "567892", period: 30 }); + + const mockBitwardenClient = { + vault: () => ({ + totp: () => ({ + generate_totp: generateTotpMock, + }), + }), + }; + + sdkService.client$ = of(mockBitwardenClient as unknown as BitwardenClient); + + totpService = new TotpService(sdkService); // TOTP is time-based, so we need to mock the current time jest.useFakeTimers({ @@ -24,40 +46,50 @@ describe("TotpService", () => { jest.useRealTimers(); }); - it("should return null if key is null", async () => { - const result = await totpService.getCode(null); - expect(result).toBeNull(); - }); + describe("getCode$", () => { + it("should emit TOTP response when key is provided", (done) => { + totpService + .getCode$("WQIQ25BRKZYCJVYP") + .pipe(take(1)) + .subscribe((result) => { + expect(result).toEqual({ code: "123456", period: 30 }); + done(); + }); - it("should return a code if key is not null", async () => { - const result = await totpService.getCode("WQIQ25BRKZYCJVYP"); - expect(result).toBe("194506"); - }); + jest.advanceTimersByTime(1000); + }); - it("should handle otpauth keys", async () => { - const key = "otpauth://totp/test-account?secret=WQIQ25BRKZYCJVYP"; - const result = await totpService.getCode(key); - expect(result).toBe("194506"); + it("should emit TOTP response every second", () => { + const responses: TotpResponse[] = []; - const period = totpService.getTimeInterval(key); - expect(period).toBe(30); - }); + totpService + .getCode$("WQIQ25BRKZYCJVYP") + .pipe(take(3)) + .subscribe((result) => { + responses.push(result); + }); - it("should handle otpauth different period", async () => { - const key = "otpauth://totp/test-account?secret=WQIQ25BRKZYCJVYP&period=60"; - const result = await totpService.getCode(key); - expect(result).toBe("730364"); + jest.advanceTimersByTime(2000); - const period = totpService.getTimeInterval(key); - expect(period).toBe(60); - }); + expect(responses).toEqual([ + { code: "123456", period: 30 }, + { code: "654321", period: 30 }, + { code: "567892", period: 30 }, + ]); + }); - it("should handle steam keys", async () => { - const key = "steam://HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ"; - const result = await totpService.getCode(key); - expect(result).toBe("7W6CJ"); + it("should stop emitting TOTP response after unsubscribing", () => { + const responses: TotpResponse[] = []; - const period = totpService.getTimeInterval(key); - expect(period).toBe(30); + const subscription = totpService.getCode$("WQIQ25BRKZYCJVYP").subscribe((result) => { + responses.push(result); + }); + + jest.advanceTimersByTime(1000); + subscription.unsubscribe(); + jest.advanceTimersByTime(1000); + + expect(responses).toHaveLength(2); + }); }); }); diff --git a/libs/common/src/vault/services/totp.service.ts b/libs/common/src/vault/services/totp.service.ts index b66e4a1bcf0..3f09462a2c5 100644 --- a/libs/common/src/vault/services/totp.service.ts +++ b/libs/common/src/vault/services/totp.service.ts @@ -1,170 +1,43 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { CryptoFunctionService } from "../../platform/abstractions/crypto-function.service"; -import { LogService } from "../../platform/abstractions/log.service"; -import { Utils } from "../../platform/misc/utils"; +import { Observable, map, shareReplay, switchMap, timer } from "rxjs"; + +import { TotpResponse } from "@bitwarden/sdk-internal"; + +import { SdkService } from "../../platform/abstractions/sdk/sdk.service"; import { TotpService as TotpServiceAbstraction } from "../abstractions/totp.service"; -const B32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; -const SteamChars = "23456789BCDFGHJKMNPQRTVWXY"; +/** + * Represents TOTP information including display formatting and timing + */ +export type TotpInfo = { + /** The TOTP code value */ + totpCode: string; + + /** The TOTP code value formatted for display, includes spaces */ + totpCodeFormatted: string; + + /** Progress bar percentage value */ + totpDash: number; + + /** Seconds remaining until the TOTP code changes */ + totpSec: number; + + /** Indicates when the code is close to expiring */ + totpLow: boolean; +}; export class TotpService implements TotpServiceAbstraction { - constructor( - private cryptoFunctionService: CryptoFunctionService, - private logService: LogService, - ) {} + constructor(private sdkService: SdkService) {} - async getCode(key: string): Promise { - if (key == null) { - return null; - } - let period = 30; - let alg: "sha1" | "sha256" | "sha512" = "sha1"; - let digits = 6; - let keyB32 = key; - const isOtpAuth = key.toLowerCase().indexOf("otpauth://") === 0; - const isSteamAuth = !isOtpAuth && key.toLowerCase().indexOf("steam://") === 0; - if (isOtpAuth) { - const params = Utils.getQueryParams(key); - if (params.has("digits") && params.get("digits") != null) { - try { - const digitParams = parseInt(params.get("digits").trim(), null); - if (digitParams > 10) { - digits = 10; - } else if (digitParams > 0) { - digits = digitParams; - } - } catch { - this.logService.error("Invalid digits param."); - } - } - if (params.has("period") && params.get("period") != null) { - try { - const periodParam = parseInt(params.get("period").trim(), null); - if (periodParam > 0) { - period = periodParam; - } - } catch { - this.logService.error("Invalid period param."); - } - } - if (params.has("secret") && params.get("secret") != null) { - keyB32 = params.get("secret"); - } - if (params.has("algorithm") && params.get("algorithm") != null) { - const algParam = params.get("algorithm").toLowerCase(); - if (algParam === "sha1" || algParam === "sha256" || algParam === "sha512") { - alg = algParam; - } - } - } else if (isSteamAuth) { - keyB32 = key.substr("steam://".length); - digits = 5; - } - - const epoch = Math.round(new Date().getTime() / 1000.0); - const timeHex = this.leftPad(this.decToHex(Math.floor(epoch / period)), 16, "0"); - const timeBytes = Utils.fromHexToArray(timeHex); - const keyBytes = this.b32ToBytes(keyB32); - - if (!keyBytes.length || !timeBytes.length) { - return null; - } - - const hash = await this.sign(keyBytes, timeBytes, alg); - if (hash.length === 0) { - return null; - } - - const offset = hash[hash.length - 1] & 0xf; - const binary = - ((hash[offset] & 0x7f) << 24) | - ((hash[offset + 1] & 0xff) << 16) | - ((hash[offset + 2] & 0xff) << 8) | - (hash[offset + 3] & 0xff); - - let otp = ""; - if (isSteamAuth) { - let fullCode = binary & 0x7fffffff; - for (let i = 0; i < digits; i++) { - otp += SteamChars[fullCode % SteamChars.length]; - fullCode = Math.trunc(fullCode / SteamChars.length); - } - } else { - otp = (binary % Math.pow(10, digits)).toString(); - otp = this.leftPad(otp, digits, "0"); - } - - return otp; - } - - getTimeInterval(key: string): number { - let period = 30; - if (key != null && key.toLowerCase().indexOf("otpauth://") === 0) { - const params = Utils.getQueryParams(key); - if (params.has("period") && params.get("period") != null) { - try { - period = parseInt(params.get("period").trim(), null); - } catch { - this.logService.error("Invalid period param."); - } - } - } - return period; - } - - // Helpers - - private leftPad(s: string, l: number, p: string): string { - if (l + 1 >= s.length) { - s = Array(l + 1 - s.length).join(p) + s; - } - return s; - } - - private decToHex(d: number): string { - return (d < 15.5 ? "0" : "") + Math.round(d).toString(16); - } - - private b32ToHex(s: string): string { - s = s.toUpperCase(); - let cleanedInput = ""; - - for (let i = 0; i < s.length; i++) { - if (B32Chars.indexOf(s[i]) < 0) { - continue; - } - - cleanedInput += s[i]; - } - s = cleanedInput; - - let bits = ""; - let hex = ""; - for (let i = 0; i < s.length; i++) { - const byteIndex = B32Chars.indexOf(s.charAt(i)); - if (byteIndex < 0) { - continue; - } - bits += this.leftPad(byteIndex.toString(2), 5, "0"); - } - for (let i = 0; i + 4 <= bits.length; i += 4) { - const chunk = bits.substr(i, 4); - hex = hex + parseInt(chunk, 2).toString(16); - } - return hex; - } - - private b32ToBytes(s: string): Uint8Array { - return Utils.fromHexToArray(this.b32ToHex(s)); - } - - private async sign( - keyBytes: Uint8Array, - timeBytes: Uint8Array, - alg: "sha1" | "sha256" | "sha512", - ) { - const signature = await this.cryptoFunctionService.hmac(timeBytes, keyBytes, alg); - return new Uint8Array(signature); + getCode$(key: string): Observable { + return timer(0, 1000).pipe( + switchMap(() => + this.sdkService.client$.pipe( + map((sdk) => { + return sdk.vault().totp().generate_totp(key); + }), + ), + ), + shareReplay({ refCount: true, bufferSize: 1 }), + ); } } diff --git a/libs/common/test.setup.ts b/libs/common/test.setup.ts index aa71b3e508a..9087c15c6b6 100644 --- a/libs/common/test.setup.ts +++ b/libs/common/test.setup.ts @@ -1,3 +1,5 @@ +import "core-js/proposals/explicit-resource-management"; + import { webcrypto } from "crypto"; import { addCustomMatchers } from "./spec"; diff --git a/libs/common/tsconfig.json b/libs/common/tsconfig.json index 128511ee410..03f66196a30 100644 --- a/libs/common/tsconfig.json +++ b/libs/common/tsconfig.json @@ -4,10 +4,10 @@ "paths": { "@bitwarden/admin-console/common": ["../admin-console/src/common"], "@bitwarden/auth/common": ["../auth/src/common"], + // TODO: Remove once circular dependencies in admin-console, auth and key-management are resolved "@bitwarden/common/*": ["../common/src/*"], - "@bitwarden/components": ["../components/src"], "@bitwarden/key-management": ["../key-management/src"], - "@bitwarden/platform": ["../platform/src"] + "@bitwarden/vault-export-core": ["../tools/export/vault-export/vault-export-core/src"] } }, "include": ["src", "spec", "./custom-matchers.d.ts", "../key-management/src/index.ts"], diff --git a/libs/components/src/a11y/a11y-grid.directive.ts b/libs/components/src/a11y/a11y-grid.directive.ts index c8e9d3d2fe3..ef7ba68b65c 100644 --- a/libs/components/src/a11y/a11y-grid.directive.ts +++ b/libs/components/src/a11y/a11y-grid.directive.ts @@ -86,6 +86,8 @@ export class A11yGridDirective implements AfterViewInit { }); this.getActiveCellContent().tabIndex = 0; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { // eslint-disable-next-line no-console console.error("Unable to initialize grid"); diff --git a/libs/angular/src/directives/a11y-title.directive.ts b/libs/components/src/a11y/a11y-title.directive.ts similarity index 98% rename from libs/angular/src/directives/a11y-title.directive.ts rename to libs/components/src/a11y/a11y-title.directive.ts index f5f016b93c0..c3833f42ed2 100644 --- a/libs/angular/src/directives/a11y-title.directive.ts +++ b/libs/components/src/a11y/a11y-title.directive.ts @@ -4,6 +4,7 @@ import { Directive, ElementRef, Input, OnInit, Renderer2 } from "@angular/core"; @Directive({ selector: "[appA11yTitle]", + standalone: true, }) export class A11yTitleDirective implements OnInit { @Input() set appA11yTitle(title: string) { diff --git a/libs/components/src/a11y/index.ts b/libs/components/src/a11y/index.ts new file mode 100644 index 00000000000..6090fb65d4e --- /dev/null +++ b/libs/components/src/a11y/index.ts @@ -0,0 +1 @@ +export * from "./a11y-title.directive"; diff --git a/libs/components/src/async-actions/bit-action.directive.ts b/libs/components/src/async-actions/bit-action.directive.ts index 3e793ae2ecd..ac50082852a 100644 --- a/libs/components/src/async-actions/bit-action.directive.ts +++ b/libs/components/src/async-actions/bit-action.directive.ts @@ -21,30 +21,35 @@ export class BitActionDirective implements OnDestroy { private destroy$ = new Subject(); private _loading$ = new BehaviorSubject(false); - disabled = false; - - @Input("bitAction") handler: FunctionReturningAwaitable; - + /** + * Observable of loading behavior subject + * + * Used in `form-button.directive.ts` + */ readonly loading$ = this._loading$.asObservable(); - constructor( - private buttonComponent: ButtonLikeAbstraction, - @Optional() private validationService?: ValidationService, - @Optional() private logService?: LogService, - ) {} - get loading() { return this._loading$.value; } set loading(value: boolean) { this._loading$.next(value); - this.buttonComponent.loading = value; + this.buttonComponent.loading.set(value); } + disabled = false; + + @Input("bitAction") handler: FunctionReturningAwaitable; + + constructor( + private buttonComponent: ButtonLikeAbstraction, + @Optional() private validationService?: ValidationService, + @Optional() private logService?: LogService, + ) {} + @HostListener("click") protected async onClick() { - if (!this.handler || this.loading || this.disabled || this.buttonComponent.disabled) { + if (!this.handler || this.loading || this.disabled || this.buttonComponent.disabled()) { return; } diff --git a/libs/components/src/async-actions/form-button.directive.ts b/libs/components/src/async-actions/form-button.directive.ts index 7c92865b984..1c2855f32e7 100644 --- a/libs/components/src/async-actions/form-button.directive.ts +++ b/libs/components/src/async-actions/form-button.directive.ts @@ -41,15 +41,15 @@ export class BitFormButtonDirective implements OnDestroy { if (submitDirective && buttonComponent) { submitDirective.loading$.pipe(takeUntil(this.destroy$)).subscribe((loading) => { if (this.type === "submit") { - buttonComponent.loading = loading; + buttonComponent.loading.set(loading); } else { - buttonComponent.disabled = this.disabled || loading; + buttonComponent.disabled.set(this.disabled || loading); } }); submitDirective.disabled$.pipe(takeUntil(this.destroy$)).subscribe((disabled) => { if (this.disabled !== false) { - buttonComponent.disabled = this.disabled || disabled; + buttonComponent.disabled.set(this.disabled || disabled); } }); } diff --git a/libs/components/src/async-actions/in-forms.stories.ts b/libs/components/src/async-actions/in-forms.stories.ts index fb94e43b196..b45f750084c 100644 --- a/libs/components/src/async-actions/in-forms.stories.ts +++ b/libs/components/src/async-actions/in-forms.stories.ts @@ -4,8 +4,8 @@ import { action } from "@storybook/addon-actions"; import { Meta, moduleMetadata, StoryObj } from "@storybook/angular"; import { delay, of } from "rxjs"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; -import { I18nService } from "@bitwarden/common/src/platform/abstractions/i18n.service"; import { ButtonModule } from "../button"; import { FormFieldModule } from "../form-field"; diff --git a/libs/components/src/async-actions/standalone.mdx b/libs/components/src/async-actions/standalone.mdx index efde494f2dd..f484ea01c58 100644 --- a/libs/components/src/async-actions/standalone.mdx +++ b/libs/components/src/async-actions/standalone.mdx @@ -1,6 +1,7 @@ -import { Meta } from "@storybook/addon-docs"; +import { Meta, Story } from "@storybook/addon-docs"; +import * as stories from "./standalone.stories.ts"; - + # Standalone Async Actions @@ -8,9 +9,13 @@ These directives should be used when building a standalone button that triggers in the background, eg. Refresh buttons. For non-submit buttons that are associated with forms see [Async Actions In Forms](?path=/story/component-library-async-actions-in-forms-documentation--page). +If the long running background task resolves quickly (e.g. less than 75 ms), the loading spinner +will not display on the button. This prevents an undesirable "flicker" of the loading spinner when +it is not necessary for the user to see it. + ## Usage -Adding async actions to standalone buttons requires the following 2 steps +Adding async actions to standalone buttons requires the following 2 steps: ### 1. Add a handler to your `Component` @@ -60,3 +65,21 @@ from how click handlers are usually defined with the output syntax `(click)="han `; ``` + +## Stories + +### Promise resolves -- loading spinner is displayed + + + +### Promise resolves -- quickly without loading spinner + + + +### Promise rejects + + + +### Observable + + diff --git a/libs/components/src/async-actions/standalone.stories.ts b/libs/components/src/async-actions/standalone.stories.ts index f658dfb0f01..52b85b88561 100644 --- a/libs/components/src/async-actions/standalone.stories.ts +++ b/libs/components/src/async-actions/standalone.stories.ts @@ -11,9 +11,9 @@ import { IconButtonModule } from "../icon-button"; import { BitActionDirective } from "./bit-action.directive"; -const template = ` +const template = /*html*/ ` `; @@ -22,9 +22,30 @@ const template = ` selector: "app-promise-example", }) class PromiseExampleComponent { + statusEmoji = "🟡"; action = async () => { await new Promise((resolve, reject) => { - setTimeout(resolve, 2000); + setTimeout(() => { + resolve(); + this.statusEmoji = "🟢"; + }, 5000); + }); + }; +} + +@Component({ + template, + selector: "app-action-resolves-quickly", +}) +class ActionResolvesQuicklyComponent { + statusEmoji = "🟡"; + + action = async () => { + await new Promise((resolve, reject) => { + setTimeout(() => { + resolve(); + this.statusEmoji = "🟢"; + }, 50); }); }; } @@ -59,6 +80,7 @@ export default { PromiseExampleComponent, ObservableExampleComponent, RejectedPromiseExampleComponent, + ActionResolvesQuicklyComponent, ], imports: [ButtonModule, IconButtonModule, BitActionDirective], providers: [ @@ -100,3 +122,10 @@ export const RejectedPromise: ObservableStory = { template: ``, }), }; + +export const ActionResolvesQuickly: PromiseStory = { + render: (args) => ({ + props: args, + template: ``, + }), +}; diff --git a/libs/components/src/avatar/avatar.component.ts b/libs/components/src/avatar/avatar.component.ts index 76ff702e88b..0e3dbd6f1b9 100644 --- a/libs/components/src/avatar/avatar.component.ts +++ b/libs/components/src/avatar/avatar.component.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { NgIf, NgClass } from "@angular/common"; +import { NgClass } from "@angular/common"; import { Component, Input, OnChanges } from "@angular/core"; import { DomSanitizer, SafeResourceUrl } from "@angular/platform-browser"; @@ -18,9 +18,11 @@ const SizeClasses: Record = { @Component({ selector: "bit-avatar", - template: ``, + template: `@if (src) { + + }`, standalone: true, - imports: [NgIf, NgClass], + imports: [NgClass], }) export class AvatarComponent implements OnChanges { @Input() border = false; diff --git a/libs/components/src/avatar/avatar.mdx b/libs/components/src/avatar/avatar.mdx index 0f3f6f06a9b..627ba526ed9 100644 --- a/libs/components/src/avatar/avatar.mdx +++ b/libs/components/src/avatar/avatar.mdx @@ -1,4 +1,4 @@ -import { Meta, Story, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs"; import * as stories from "./avatar.stories"; @@ -18,15 +18,15 @@ visual indicator to recognize which of a personal or work account a user is logg ### Large: 64px - + ### Default: 48px - + ### Small 28px - + ## Background color @@ -37,9 +37,9 @@ priority: - ID - Text, usually set to the user's Name field - + Use the user 'ID' field if `Name` is not defined. - + ## Outline @@ -47,7 +47,7 @@ If the avatar is displayed on one of the theme's `background` color variables or display the avatar with a 1 pixel `secondary-600` border to meet WCAG AA graphic contrast guidelines for interactive elements. - + ## Avatar as a button diff --git a/libs/components/src/avatar/avatar.stories.ts b/libs/components/src/avatar/avatar.stories.ts index d3a00fbe344..19a6f86d89c 100644 --- a/libs/components/src/avatar/avatar.stories.ts +++ b/libs/components/src/avatar/avatar.stories.ts @@ -13,7 +13,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=1881%3A16994", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-26525&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/badge-list/badge-list.component.html b/libs/components/src/badge-list/badge-list.component.html index ebd63117d03..c8aa7b84680 100644 --- a/libs/components/src/badge-list/badge-list.component.html +++ b/libs/components/src/badge-list/badge-list.component.html @@ -1,11 +1,15 @@

    - + @for (item of filteredItems; track item; let last = $last) { {{ item }} - , - - - {{ "plusNMore" | i18n: (items.length - filteredItems.length).toString() }} - + @if (!last || isFiltered) { + , + } + } + @if (isFiltered) { + + {{ "plusNMore" | i18n: (items.length - filteredItems.length).toString() }} + + }
    diff --git a/libs/components/src/badge-list/badge-list.component.ts b/libs/components/src/badge-list/badge-list.component.ts index ac8cb3281ab..86e9a84cb77 100644 --- a/libs/components/src/badge-list/badge-list.component.ts +++ b/libs/components/src/badge-list/badge-list.component.ts @@ -1,16 +1,17 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { CommonModule } from "@angular/common"; + import { Component, Input, OnChanges } from "@angular/core"; +import { I18nPipe } from "@bitwarden/ui-common"; + import { BadgeModule, BadgeVariant } from "../badge"; -import { I18nPipe } from "../shared/i18n.pipe"; @Component({ selector: "bit-badge-list", templateUrl: "badge-list.component.html", standalone: true, - imports: [CommonModule, BadgeModule, I18nPipe], + imports: [BadgeModule, I18nPipe], }) export class BadgeListComponent implements OnChanges { private _maxItems: number; diff --git a/libs/components/src/badge-list/badge-list.stories.ts b/libs/components/src/badge-list/badge-list.stories.ts index ede005f6fd6..f69ecde8377 100644 --- a/libs/components/src/badge-list/badge-list.stories.ts +++ b/libs/components/src/badge-list/badge-list.stories.ts @@ -33,7 +33,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/f32LSg3jaegICkMu7rPARm/Tailwind-Component-Library-Update?node-id=1881%3A16956", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-26440&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/badge/badge.mdx b/libs/components/src/badge/badge.mdx index fb2dceb0fa8..55f32183899 100644 --- a/libs/components/src/badge/badge.mdx +++ b/libs/components/src/badge/badge.mdx @@ -1,4 +1,4 @@ -import { Meta, Story, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs"; import * as stories from "./badge.stories"; @@ -31,39 +31,39 @@ The story below uses the ` + @if (showClose) { + + }
    diff --git a/libs/components/src/banner/banner.component.ts b/libs/components/src/banner/banner.component.ts index d3f64329978..a7b710d6a74 100644 --- a/libs/components/src/banner/banner.component.ts +++ b/libs/components/src/banner/banner.component.ts @@ -3,8 +3,9 @@ import { CommonModule } from "@angular/common"; import { Component, Input, OnInit, Output, EventEmitter } from "@angular/core"; +import { I18nPipe } from "@bitwarden/ui-common"; + import { IconButtonModule } from "../icon-button"; -import { I18nPipe } from "../shared/i18n.pipe"; type BannerTypes = "premium" | "info" | "warning" | "danger"; diff --git a/libs/components/src/banner/banner.mdx b/libs/components/src/banner/banner.mdx index d5b6a3b5279..67fb796a548 100644 --- a/libs/components/src/banner/banner.mdx +++ b/libs/components/src/banner/banner.mdx @@ -1,4 +1,4 @@ -import { Meta, Story, Controls, Canvas, Primary } from "@storybook/addon-docs"; +import { Meta, Controls, Canvas, Primary } from "@storybook/addon-docs"; import * as stories from "./banner.stories"; @@ -31,25 +31,25 @@ Use the following guidelines to help choose the correct type of banner. ### Premium - + Used primarily to encourage user to upgrade to premium. ### Info - + Used to communicate release notes, server maintenance or other informative event. ### Warning - + Used to alert the user of outdated info or versions. ### Danger - + Rarely used, but may be used to alert users over critical messages or very outdated versions. diff --git a/libs/components/src/banner/banner.stories.ts b/libs/components/src/banner/banner.stories.ts index c75d8b7034a..105d30bc04a 100644 --- a/libs/components/src/banner/banner.stories.ts +++ b/libs/components/src/banner/banner.stories.ts @@ -30,7 +30,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=2070%3A17207", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-26720&t=b5tDKylm5sWm2yKo-4", }, }, args: { diff --git a/libs/components/src/breadcrumbs/breadcrumb.component.html b/libs/components/src/breadcrumbs/breadcrumb.component.html index dd5bac9beb4..bb4dc7cdffe 100644 --- a/libs/components/src/breadcrumbs/breadcrumb.component.html +++ b/libs/components/src/breadcrumbs/breadcrumb.component.html @@ -1,3 +1,6 @@ - + @if (icon) { + + } + diff --git a/libs/components/src/breadcrumbs/breadcrumb.component.ts b/libs/components/src/breadcrumbs/breadcrumb.component.ts index ce18bde171f..53c46a9b24a 100644 --- a/libs/components/src/breadcrumbs/breadcrumb.component.ts +++ b/libs/components/src/breadcrumbs/breadcrumb.component.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { NgIf } from "@angular/common"; + import { Component, EventEmitter, Input, Output, TemplateRef, ViewChild } from "@angular/core"; import { QueryParamsHandling } from "@angular/router"; @@ -8,7 +8,6 @@ import { QueryParamsHandling } from "@angular/router"; selector: "bit-breadcrumb", templateUrl: "./breadcrumb.component.html", standalone: true, - imports: [NgIf], }) export class BreadcrumbComponent { @Input() diff --git a/libs/components/src/breadcrumbs/breadcrumbs.component.html b/libs/components/src/breadcrumbs/breadcrumbs.component.html index 502bb0bb8e7..5205e19cee5 100644 --- a/libs/components/src/breadcrumbs/breadcrumbs.component.html +++ b/libs/components/src/breadcrumbs/breadcrumbs.component.html @@ -1,5 +1,5 @@ - - +@for (breadcrumb of beforeOverflow; track breadcrumb; let last = $last) { + @if (breadcrumb.route) { - - + } + @if (!breadcrumb.route) { - - - - - - + } + @if (!last) { + + } +} +@if (hasOverflow) { + @if (beforeOverflow.length > 0) { + + } - - - + @for (breadcrumb of overflow; track breadcrumb) { + @if (breadcrumb.route) { - - + } + @if (!breadcrumb.route) { - - + } + } - - - + @for (breadcrumb of afterOverflow; track breadcrumb; let last = $last) { + @if (breadcrumb.route) { - - + } + @if (!breadcrumb.route) { - - - - + } + @if (!last) { + + } + } +} diff --git a/libs/components/src/breadcrumbs/breadcrumbs.mdx b/libs/components/src/breadcrumbs/breadcrumbs.mdx index 9dd23c530d1..1ea0aff8c36 100644 --- a/libs/components/src/breadcrumbs/breadcrumbs.mdx +++ b/libs/components/src/breadcrumbs/breadcrumbs.mdx @@ -1,4 +1,4 @@ -import { Meta, Story, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs"; import * as stories from "./breadcrumbs.stories"; @@ -23,7 +23,7 @@ See [Header with Breadcrumbs](?path=/story/web-header--with-breadcrumbs). When a user is 1 level deep into a tree, the top level is displayed as a single link above the page title. - + ### Second Level @@ -31,7 +31,7 @@ When a user is 2 or more levels deep into a tree, the top level is displayed fol icon, and the following pages. - + ### Overflow @@ -42,7 +42,7 @@ When a user is several levels deep into a tree, the top level or 2 are displayed When the user selects the icon button, a menu opens displaying the pages between the top level and the previous page. - + ### Small screens diff --git a/libs/components/src/breadcrumbs/breadcrumbs.stories.ts b/libs/components/src/breadcrumbs/breadcrumbs.stories.ts index 9c8ccbccd3f..eb75a70ad75 100644 --- a/libs/components/src/breadcrumbs/breadcrumbs.stories.ts +++ b/libs/components/src/breadcrumbs/breadcrumbs.stories.ts @@ -35,6 +35,12 @@ export default { ], }), ], + parameters: { + design: { + type: "figma", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-26962&t=b5tDKylm5sWm2yKo-4", + }, + }, args: { items: [], show: 3, diff --git a/libs/components/src/button/button.component.html b/libs/components/src/button/button.component.html index ee4d150dfcc..a07ab9fb99b 100644 --- a/libs/components/src/button/button.component.html +++ b/libs/components/src/button/button.component.html @@ -1,10 +1,10 @@ - + diff --git a/libs/components/src/button/button.component.ts b/libs/components/src/button/button.component.ts index 96311f91529..0b4ce3073c3 100644 --- a/libs/components/src/button/button.component.ts +++ b/libs/components/src/button/button.component.ts @@ -2,7 +2,9 @@ // @ts-strict-ignore import { coerceBooleanProperty } from "@angular/cdk/coercion"; import { NgClass } from "@angular/common"; -import { Input, HostBinding, Component } from "@angular/core"; +import { Input, HostBinding, Component, model, computed } from "@angular/core"; +import { toObservable, toSignal } from "@angular/core/rxjs-interop"; +import { debounce, interval } from "rxjs"; import { ButtonLikeAbstraction, ButtonType } from "../shared/button-like.abstraction"; @@ -49,6 +51,9 @@ const buttonStyles: Record = { providers: [{ provide: ButtonLikeAbstraction, useExisting: ButtonComponent }], standalone: true, imports: [NgClass], + host: { + "[attr.disabled]": "disabledAttr()", + }, }) export class ButtonComponent implements ButtonLikeAbstraction { @HostBinding("class") get classList() { @@ -64,24 +69,41 @@ export class ButtonComponent implements ButtonLikeAbstraction { "tw-no-underline", "hover:tw-no-underline", "focus:tw-outline-none", - "disabled:tw-bg-secondary-300", - "disabled:hover:tw-bg-secondary-300", - "disabled:tw-border-secondary-300", - "disabled:hover:tw-border-secondary-300", - "disabled:!tw-text-muted", - "disabled:hover:!tw-text-muted", - "disabled:tw-cursor-not-allowed", - "disabled:hover:tw-no-underline", ] .concat(this.block ? ["tw-w-full", "tw-block"] : ["tw-inline-block"]) - .concat(buttonStyles[this.buttonType ?? "secondary"]); + .concat(buttonStyles[this.buttonType ?? "secondary"]) + .concat( + this.showDisabledStyles() || this.disabled() + ? [ + "disabled:tw-bg-secondary-300", + "disabled:hover:tw-bg-secondary-300", + "disabled:tw-border-secondary-300", + "disabled:hover:tw-border-secondary-300", + "disabled:!tw-text-muted", + "disabled:hover:!tw-text-muted", + "disabled:tw-cursor-not-allowed", + "disabled:hover:tw-no-underline", + ] + : [], + ); } - @HostBinding("attr.disabled") - get disabledAttr() { - const disabled = this.disabled != null && this.disabled !== false; - return disabled || this.loading ? true : null; - } + protected disabledAttr = computed(() => { + const disabled = this.disabled() != null && this.disabled() !== false; + return disabled || this.loading() ? true : null; + }); + + /** + * Determine whether it is appropriate to display the disabled styles. We only want to show + * the disabled styles if the button is truly disabled, or if the loading styles are also + * visible. + * + * We can't use `disabledAttr` for this, because it returns `true` when `loading` is `true`. + * We only want to show disabled styles during loading if `showLoadingStyles` is `true`. + */ + protected showDisabledStyles = computed(() => { + return this.showLoadingStyle() || (this.disabledAttr() && this.loading() === false); + }); @Input() buttonType: ButtonType; @@ -96,7 +118,23 @@ export class ButtonComponent implements ButtonLikeAbstraction { this._block = coerceBooleanProperty(value); } - @Input() loading = false; + loading = model(false); - @Input() disabled = false; + /** + * Determine whether it is appropriate to display a loading spinner. We only want to show + * a spinner if it's been more than 75 ms since the `loading` state began. This prevents + * a spinner "flash" for actions that are synchronous/nearly synchronous. + * + * We can't use `loading` for this, because we still need to disable the button during + * the full `loading` state. I.e. we only want the spinner to be debounced, not the + * loading state. + * + * This pattern of converting a signal to an observable and back to a signal is not + * recommended. TODO -- find better way to use debounce with signals (CL-596) + */ + protected showLoadingStyle = toSignal( + toObservable(this.loading).pipe(debounce((isLoading) => interval(isLoading ? 75 : 0))), + ); + + disabled = model(false); } diff --git a/libs/components/src/button/button.mdx b/libs/components/src/button/button.mdx index 33e4aed19f7..21c992982d8 100644 --- a/libs/components/src/button/button.mdx +++ b/libs/components/src/button/button.mdx @@ -1,4 +1,4 @@ -import { Meta, Story, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs"; import * as stories from "./button.stories"; @@ -43,14 +43,14 @@ There are 3 main styles for the button: Primary, Secondary, and Danger. ### Primary - + Use the primary button styling for all Primary call to actions. An action is "primary" if it relates to the main purpose of a page. There should never be 2 primary styled buttons next to each other. ### Secondary - + The secondary styling should be used for secondary calls to action. An action is "secondary" if it relates indirectly to the purpose of a page. There may be multiple secondary buttons next to each @@ -58,20 +58,20 @@ other; however, generally there should only be 1 or 2 calls to action per page. ### Danger - + Use the danger styling only in settings when the user may preform a permanent action. ## Disabled UI - + ## Block Typically button widths expand with their text. In some causes though buttons may need to be block where the width is fixed and the text wraps to 2 lines if exceeding the button’s width. - + ## Accessibility @@ -96,7 +96,7 @@ Both submit and async action buttons use a loading button state while an action button is preforming a long running task in the background like a server API call, be sure to review the [Async Actions Directive](?path=/story/component-library-async-actions-overview--page). - + ### appA11yTitle diff --git a/libs/components/src/button/button.stories.ts b/libs/components/src/button/button.stories.ts index 3654442801c..6024b0559f2 100644 --- a/libs/components/src/button/button.stories.ts +++ b/libs/components/src/button/button.stories.ts @@ -13,7 +13,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=5115%3A26950", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-28224&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; @@ -86,16 +86,15 @@ export const DisabledWithAttribute: Story = { render: (args) => ({ props: args, template: ` - + @if (disabled) { - - + } @else { - + } `, }), args: { diff --git a/libs/components/src/callout/callout.component.html b/libs/components/src/callout/callout.component.html index f64e197b9ef..bb7f918df32 100644 --- a/libs/components/src/callout/callout.component.html +++ b/libs/components/src/callout/callout.component.html @@ -3,10 +3,14 @@ [ngClass]="calloutClass" [attr.aria-labelledby]="titleId" > -
    - - {{ title }} -
    + @if (title) { +
    + @if (icon) { + + } + {{ title }} +
    + }
    diff --git a/libs/components/src/callout/callout.mdx b/libs/components/src/callout/callout.mdx index 3fdb53943b4..160b1e1cc33 100644 --- a/libs/components/src/callout/callout.mdx +++ b/libs/components/src/callout/callout.mdx @@ -1,4 +1,4 @@ -import { Meta, Story, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs"; import * as stories from "./callout.stories"; @@ -28,7 +28,7 @@ Use the success callout to communicate a positive messaging to the user. The success callout may also be used for the information related to a premium membership. In this case, replace the icon with - + ### Info @@ -39,7 +39,7 @@ information. **Example:** in the Domain Claiming modal, an info callout is used to tell the user the domain will automatically be checked. - + ### Warning @@ -49,7 +49,7 @@ irreversible results. **Example:** the warning callout is used before the change master password and encryption key form to alert the user that they will be logged out. - + ### Danger @@ -59,7 +59,7 @@ not reversible. The danger callout can also be used to alert the user of an error or errors, such as a server side errors after form submit or failed communication request. - + ## Accessibility diff --git a/libs/components/src/callout/callout.stories.ts b/libs/components/src/callout/callout.stories.ts index cb51e96e8da..3101d4316f1 100644 --- a/libs/components/src/callout/callout.stories.ts +++ b/libs/components/src/callout/callout.stories.ts @@ -30,7 +30,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=1881%3A17484", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-28300&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/card/card.component.ts b/libs/components/src/card/card.component.ts index 37756088e0d..fdb02f280da 100644 --- a/libs/components/src/card/card.component.ts +++ b/libs/components/src/card/card.component.ts @@ -1,10 +1,8 @@ -import { CommonModule } from "@angular/common"; import { ChangeDetectionStrategy, Component } from "@angular/core"; @Component({ selector: "bit-card", standalone: true, - imports: [CommonModule], template: ``, changeDetection: ChangeDetectionStrategy.OnPush, host: { diff --git a/libs/components/src/card/card.stories.ts b/libs/components/src/card/card.stories.ts index b33f5f4a198..3482eedfd54 100644 --- a/libs/components/src/card/card.stories.ts +++ b/libs/components/src/card/card.stories.ts @@ -34,6 +34,12 @@ export default { (story) => `
    ${story}
    `, ), ], + parameters: { + design: { + type: "figma", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-28355&t=b5tDKylm5sWm2yKo-4", + }, + }, } as Meta; type Story = StoryObj; diff --git a/libs/components/src/checkbox/checkbox.stories.ts b/libs/components/src/checkbox/checkbox.stories.ts index c322f29b957..9a59897e009 100644 --- a/libs/components/src/checkbox/checkbox.stories.ts +++ b/libs/components/src/checkbox/checkbox.stories.ts @@ -9,7 +9,7 @@ import { } from "@angular/forms"; import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; -import { I18nService } from "@bitwarden/common/src/platform/abstractions/i18n.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { BadgeModule } from "../badge"; import { FormControlModule } from "../form-control"; @@ -81,7 +81,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=3930%3A16850&t=xXPx6GJYsJfuMQPE-4", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-35837&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/chip-select/chip-select.component.html b/libs/components/src/chip-select/chip-select.component.html index 81480f107f1..e88200b6e4f 100644 --- a/libs/components/src/chip-select/chip-select.component.html +++ b/libs/components/src/chip-select/chip-select.component.html @@ -30,78 +30,80 @@ {{ label }}
    - + @if (!selectedOption) { + + } - + @if (selectedOption) { + + }
    -
    - - - - - - - -
    + @if (getParent(renderedOptions); as parent) { + + + } + @for (option of renderedOptions.children; track option) { + + } +
    + } diff --git a/libs/components/src/chip-select/chip-select.component.ts b/libs/components/src/chip-select/chip-select.component.ts index 5b07d0bbb96..a4c73b699cf 100644 --- a/libs/components/src/chip-select/chip-select.component.ts +++ b/libs/components/src/chip-select/chip-select.component.ts @@ -18,7 +18,8 @@ import { import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms"; -import { compareValues } from "../../../common/src/platform/misc/compare-values"; +import { compareValues } from "@bitwarden/common/platform/misc/compare-values"; + import { ButtonModule } from "../button"; import { IconButtonModule } from "../icon-button"; import { MenuComponent, MenuItemDirective, MenuModule } from "../menu"; diff --git a/libs/components/src/chip-select/chip-select.mdx b/libs/components/src/chip-select/chip-select.mdx index 90bc7556209..d569158b75a 100644 --- a/libs/components/src/chip-select/chip-select.mdx +++ b/libs/components/src/chip-select/chip-select.mdx @@ -1,4 +1,4 @@ -import { Meta, Story, Primary, Controls, Canvas } from "@storybook/addon-docs"; +import { Meta, Primary, Controls, Canvas } from "@storybook/addon-docs"; import * as stories from "./chip-select.stories"; @@ -12,9 +12,7 @@ import { ChipSelectComponent } from "@bitwarden/components"; `` is a select element that is commonly used to filter items in lists or tables. - - - + ## Options @@ -91,9 +89,7 @@ const options = [ ]; ``` - - - + ## Placeholder Content diff --git a/libs/components/src/chip-select/chip-select.stories.ts b/libs/components/src/chip-select/chip-select.stories.ts index 598fa5b80be..e9b78235ccb 100644 --- a/libs/components/src/chip-select/chip-select.stories.ts +++ b/libs/components/src/chip-select/chip-select.stories.ts @@ -30,6 +30,12 @@ export default { ], }), ], + parameters: { + design: { + type: "figma", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-29548&t=b5tDKylm5sWm2yKo-4", + }, + }, } as Meta; type Story = StoryObj; diff --git a/libs/components/src/color-password/color-password.component.ts b/libs/components/src/color-password/color-password.component.ts index cbf746e9d73..4fc94e41854 100644 --- a/libs/components/src/color-password/color-password.component.ts +++ b/libs/components/src/color-password/color-password.component.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { NgFor, NgIf } from "@angular/common"; + import { Component, HostBinding, Input } from "@angular/core"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -14,18 +14,15 @@ enum CharacterType { @Component({ selector: "bit-color-password", - template: ` - {{ character }} - {{ - i + 1 - }} - `, - preserveWhitespaces: false, + template: `@for (character of passwordArray; track character; let i = $index) { + + {{ character }} + @if (showCount) { + {{ i + 1 }} + } + + }`, standalone: true, - imports: [NgFor, NgIf], }) export class ColorPasswordComponent { @Input() password: string = null; diff --git a/libs/components/src/color-password/color-password.mdx b/libs/components/src/color-password/color-password.mdx index d01c81b0073..8f3746715e1 100644 --- a/libs/components/src/color-password/color-password.mdx +++ b/libs/components/src/color-password/color-password.mdx @@ -1,4 +1,4 @@ -import { Meta, Story, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs"; import * as stories from "./color-password.stories"; @@ -18,15 +18,15 @@ the logic for displaying letters as `text-main`, numbers as `primary`, and speci The password count option is used in the Login type form. It is used to highlight each character's position in the password string. - + ## Wrapped Password When the password length is longer than the container's width, it should wrap as shown below. - + - + ## Accessibility diff --git a/libs/components/src/color-password/color-password.stories.ts b/libs/components/src/color-password/color-password.stories.ts index 07418cad721..bb835d97d4a 100644 --- a/libs/components/src/color-password/color-password.stories.ts +++ b/libs/components/src/color-password/color-password.stories.ts @@ -14,7 +14,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/6fvTDa3zfvgWdizLQ7nSTP/Numbered-Password", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=21540-46261&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/color-password/index.ts b/libs/components/src/color-password/index.ts index 86718f037f7..24870ca75d9 100644 --- a/libs/components/src/color-password/index.ts +++ b/libs/components/src/color-password/index.ts @@ -1 +1,2 @@ export * from "./color-password.module"; +export * from "./color-password.component"; diff --git a/libs/components/src/container/container.component.ts b/libs/components/src/container/container.component.ts index fbd9e40a7ca..1bcdb8f459b 100644 --- a/libs/components/src/container/container.component.ts +++ b/libs/components/src/container/container.component.ts @@ -1,4 +1,3 @@ -import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; /** @@ -7,7 +6,6 @@ import { Component } from "@angular/core"; @Component({ selector: "bit-container", templateUrl: "container.component.html", - imports: [CommonModule], standalone: true, }) export class ContainerComponent {} diff --git a/libs/angular/src/directives/copy-click.directive.spec.ts b/libs/components/src/copy-click/copy-click.directive.spec.ts similarity index 78% rename from libs/angular/src/directives/copy-click.directive.spec.ts rename to libs/components/src/copy-click/copy-click.directive.spec.ts index 29466f7fbe3..eab616b141e 100644 --- a/libs/angular/src/directives/copy-click.directive.spec.ts +++ b/libs/components/src/copy-click/copy-click.directive.spec.ts @@ -3,23 +3,32 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { ToastService } from "@bitwarden/components"; + +import { ToastService } from "../"; import { CopyClickDirective } from "./copy-click.directive"; @Component({ template: ` - - - - + + + + `, + standalone: true, + imports: [CopyClickDirective], }) class TestCopyClickComponent { - @ViewChild("noToast") noToastButton: ElementRef; - @ViewChild("infoToast") infoToastButton: ElementRef; - @ViewChild("successToast") successToastButton: ElementRef; - @ViewChild("toastWithLabel") toastWithLabelButton: ElementRef; + @ViewChild("noToast") noToastButton!: ElementRef; + @ViewChild("infoToast") infoToastButton!: ElementRef; + @ViewChild("successToast") successToastButton!: ElementRef; + @ViewChild("toastWithLabel") toastWithLabelButton!: ElementRef; } describe("CopyClickDirective", () => { @@ -32,7 +41,7 @@ describe("CopyClickDirective", () => { showToast.mockClear(); await TestBed.configureTestingModule({ - declarations: [CopyClickDirective, TestCopyClickComponent], + imports: [TestCopyClickComponent], providers: [ { provide: I18nService, diff --git a/libs/angular/src/directives/copy-click.directive.ts b/libs/components/src/copy-click/copy-click.directive.ts similarity index 93% rename from libs/angular/src/directives/copy-click.directive.ts rename to libs/components/src/copy-click/copy-click.directive.ts index cb346ebf8d3..f91366360c5 100644 --- a/libs/angular/src/directives/copy-click.directive.ts +++ b/libs/components/src/copy-click/copy-click.directive.ts @@ -4,11 +4,12 @@ import { Directive, HostListener, Input } from "@angular/core"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { ToastService } from "@bitwarden/components"; -import { ToastVariant } from "@bitwarden/components/src/toast/toast.component"; + +import { ToastService, ToastVariant } from "../"; @Directive({ selector: "[appCopyClick]", + standalone: true, }) export class CopyClickDirective { private _showToast = false; diff --git a/libs/components/src/copy-click/index.ts b/libs/components/src/copy-click/index.ts new file mode 100644 index 00000000000..82b971f1f5c --- /dev/null +++ b/libs/components/src/copy-click/index.ts @@ -0,0 +1 @@ +export * from "./copy-click.directive"; diff --git a/libs/components/src/dialog/dialog.service.stories.ts b/libs/components/src/dialog/dialog.service.stories.ts index 5e938412804..e7c5a17c308 100644 --- a/libs/components/src/dialog/dialog.service.stories.ts +++ b/libs/components/src/dialog/dialog.service.stories.ts @@ -19,7 +19,7 @@ interface Animal { } @Component({ - template: ``, + template: ``, }) class StoryDialogComponent { constructor(public dialogService: DialogService) {} @@ -42,8 +42,10 @@ class StoryDialogComponent { Animal: {{ animal }} - - + + `, @@ -91,7 +93,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-30495&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/dialog/dialog/dialog.component.html b/libs/components/src/dialog/dialog/dialog.component.html index 06a7772edbe..01f05985127 100644 --- a/libs/components/src/dialog/dialog/dialog.component.html +++ b/libs/components/src/dialog/dialog/dialog.component.html @@ -13,9 +13,11 @@ class="tw-text-main tw-mb-0 tw-truncate" > {{ title }} - - {{ subtitle }} - + @if (subtitle) { + + {{ subtitle }} + + }
  • - + @if (showCancelButton) { + + }
    diff --git a/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.component.ts b/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.component.ts index 60b2e1c3a3f..00026209183 100644 --- a/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.component.ts +++ b/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.component.ts @@ -1,7 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; -import { NgIf } from "@angular/common"; import { Component, Inject } from "@angular/core"; import { FormGroup, ReactiveFormsModule } from "@angular/forms"; @@ -39,7 +38,6 @@ const DEFAULT_COLOR: Record = { IconDirective, ButtonComponent, BitFormButtonDirective, - NgIf, ], }) export class SimpleConfigurableDialogComponent { diff --git a/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.service.stories.ts b/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.service.stories.ts index 4f21b8611b3..87d6eb9fbfc 100644 --- a/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.service.stories.ts +++ b/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.service.stories.ts @@ -12,22 +12,24 @@ import { DialogModule } from "../../dialog.module"; @Component({ template: ` -
    -

    {{ group.title }}

    -
    - + @for (group of dialogs; track group) { +
    +

    {{ group.title }}

    +
    + @for (dialog of group.dialogs; track dialog) { + + } +
    -
    + } - - {{ dialogCloseResult }} - + @if (showCallout) { + + {{ dialogCloseResult }} + + } `, }) class StoryDialogComponent { @@ -175,7 +177,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=21514-19247&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/dialog/simple-dialog/simple-dialog.component.html b/libs/components/src/dialog/simple-dialog/simple-dialog.component.html index 0b56c6287dc..d810838cabb 100644 --- a/libs/components/src/dialog/simple-dialog/simple-dialog.component.html +++ b/libs/components/src/dialog/simple-dialog/simple-dialog.component.html @@ -3,12 +3,13 @@ @fadeIn >
    - - - - - - + @if (!hideIcon()) { + @if (hasIcon) { + + } @else { + + } + }

    + diff --git a/libs/components/src/dialog/simple-dialog/simple-dialog.service.stories.ts b/libs/components/src/dialog/simple-dialog/simple-dialog.service.stories.ts index 665d658f8ee..680ebe9ed3b 100644 --- a/libs/components/src/dialog/simple-dialog/simple-dialog.service.stories.ts +++ b/libs/components/src/dialog/simple-dialog/simple-dialog.service.stories.ts @@ -17,7 +17,7 @@ interface Animal { } @Component({ - template: ``, + template: ``, }) class StoryDialogComponent { constructor(public dialogService: DialogService) {} @@ -41,8 +41,10 @@ class StoryDialogComponent { Animal: {{ animal }} - - + + `, @@ -87,7 +89,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=21514-19247&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/dialog/simple-dialog/simple-dialog.stories.ts b/libs/components/src/dialog/simple-dialog/simple-dialog.stories.ts index d86b56101b0..13618fdeb5f 100644 --- a/libs/components/src/dialog/simple-dialog/simple-dialog.stories.ts +++ b/libs/components/src/dialog/simple-dialog/simple-dialog.stories.ts @@ -1,5 +1,5 @@ import { NoopAnimationsModule } from "@angular/platform-browser/animations"; -import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; +import { Meta, moduleMetadata, StoryObj } from "@storybook/angular"; import { ButtonModule } from "../../button"; import { DialogModule } from "../dialog.module"; @@ -17,7 +17,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=21514-19247&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; @@ -57,8 +57,24 @@ export const CustomIcon: Story = { }), }; +export const HideIcon: Story = { + render: (args) => ({ + props: args, + template: ` + + Premium Subscription Available + Message Content + + + + + + `, + }), +}; + export const ScrollingContent: Story = { - render: (args: SimpleDialogComponent) => ({ + render: (args) => ({ props: args, template: ` diff --git a/libs/components/src/dialog/simple-dialog/types.ts b/libs/components/src/dialog/simple-dialog/types.ts index 4032b499cbe..9724111b4c2 100644 --- a/libs/components/src/dialog/simple-dialog/types.ts +++ b/libs/components/src/dialog/simple-dialog/types.ts @@ -46,7 +46,7 @@ export type SimpleDialogOptions = { * If null is provided, the cancel button will be removed. * * If not localized, pass in a `Translation` */ - cancelButtonText?: string | Translation; + cancelButtonText?: string | Translation | null; /** Whether or not the user can use escape or clicking the backdrop to close the dialog */ disableClose?: boolean; diff --git a/libs/components/src/disclosure/disclosure.mdx b/libs/components/src/disclosure/disclosure.mdx index 8df8e7025b8..2fcff6f5982 100644 --- a/libs/components/src/disclosure/disclosure.mdx +++ b/libs/components/src/disclosure/disclosure.mdx @@ -1,4 +1,4 @@ -import { Meta, Story, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs"; import * as stories from "./disclosure.stories"; @@ -34,7 +34,7 @@ To compose a disclosure and trigger: click button to hide this content ``` - +

    diff --git a/libs/components/src/disclosure/disclosure.stories.ts b/libs/components/src/disclosure/disclosure.stories.ts index 974589a667c..bb3680c1f3b 100644 --- a/libs/components/src/disclosure/disclosure.stories.ts +++ b/libs/components/src/disclosure/disclosure.stories.ts @@ -13,6 +13,12 @@ export default { imports: [DisclosureTriggerForDirective, DisclosureComponent, IconButtonModule], }), ], + parameters: { + design: { + type: "figma", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=21662-47329&t=k6OTDDPZOTtypRqo-11", + }, + }, } as Meta; type Story = StoryObj; diff --git a/libs/components/src/drawer/drawer-body.component.ts b/libs/components/src/drawer/drawer-body.component.ts new file mode 100644 index 00000000000..9bd2adcffbc --- /dev/null +++ b/libs/components/src/drawer/drawer-body.component.ts @@ -0,0 +1,36 @@ +import { CdkScrollable } from "@angular/cdk/scrolling"; +import { ChangeDetectionStrategy, Component, Signal, inject } from "@angular/core"; +import { toSignal } from "@angular/core/rxjs-interop"; +import { map } from "rxjs"; + +/** + * Body container for `bit-drawer` + */ +@Component({ + selector: "bit-drawer-body", + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [], + host: { + class: + "tw-p-4 tw-pt-0 tw-block tw-overflow-auto tw-border-solid tw-border tw-border-transparent tw-transition-colors tw-duration-200", + "[class.tw-border-t-secondary-300]": "isScrolled()", + }, + hostDirectives: [ + { + directive: CdkScrollable, + }, + ], + template: ` `, +}) +export class DrawerBodyComponent { + private scrollable = inject(CdkScrollable); + + /** TODO: share this utility with browser popup header? */ + protected isScrolled: Signal = toSignal( + this.scrollable + .elementScrolled() + .pipe(map(() => this.scrollable.measureScrollOffset("top") > 0)), + { initialValue: false }, + ); +} diff --git a/libs/components/src/drawer/drawer-close.directive.ts b/libs/components/src/drawer/drawer-close.directive.ts new file mode 100644 index 00000000000..bf56dd8b71f --- /dev/null +++ b/libs/components/src/drawer/drawer-close.directive.ts @@ -0,0 +1,29 @@ +import { Directive, inject } from "@angular/core"; + +import { DrawerComponent } from "./drawer.component"; + +/** + * Closes the ancestor drawer + * + * @example + * + * ```html + * + * + * + * ``` + **/ +@Directive({ + selector: "button[bitDrawerClose]", + standalone: true, + host: { + "(click)": "onClick()", + }, +}) +export class DrawerCloseDirective { + private drawer = inject(DrawerComponent, { optional: true }); + + protected onClick() { + this.drawer?.open.set(false); + } +} diff --git a/libs/components/src/drawer/drawer-header.component.html b/libs/components/src/drawer/drawer-header.component.html new file mode 100644 index 00000000000..4652e5537ee --- /dev/null +++ b/libs/components/src/drawer/drawer-header.component.html @@ -0,0 +1,15 @@ +
    +
    + +

    + {{ title() }} +

    +
    + +
    diff --git a/libs/components/src/drawer/drawer-header.component.ts b/libs/components/src/drawer/drawer-header.component.ts new file mode 100644 index 00000000000..de112a448cf --- /dev/null +++ b/libs/components/src/drawer/drawer-header.component.ts @@ -0,0 +1,35 @@ +import { CommonModule } from "@angular/common"; +import { ChangeDetectionStrategy, Component, HostBinding, input } from "@angular/core"; + +import { I18nPipe } from "@bitwarden/ui-common"; + +import { IconButtonModule } from "../icon-button"; +import { TypographyModule } from "../typography"; + +import { DrawerCloseDirective } from "./drawer-close.directive"; + +/** + * Header container for `bit-drawer` + **/ +@Component({ + selector: "bit-drawer-header", + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [CommonModule, DrawerCloseDirective, TypographyModule, IconButtonModule, I18nPipe], + templateUrl: "drawer-header.component.html", + host: { + class: "tw-block tw-pl-4 tw-pr-2 tw-py-2", + }, +}) +export class DrawerHeaderComponent { + /** + * The title to display + */ + title = input.required(); + + /** We don't want to set the HTML title attribute with `this.title` */ + @HostBinding("attr.title") + protected get getTitle(): null { + return null; + } +} diff --git a/libs/components/src/drawer/drawer-host.directive.ts b/libs/components/src/drawer/drawer-host.directive.ts new file mode 100644 index 00000000000..f5e3e56b099 --- /dev/null +++ b/libs/components/src/drawer/drawer-host.directive.ts @@ -0,0 +1,28 @@ +import { Portal } from "@angular/cdk/portal"; +import { Directive, signal } from "@angular/core"; + +/** + * Host that renders a drawer + * + * @internal + */ +@Directive({ + selector: "[bitDrawerHost]", + standalone: true, +}) +export class DrawerHostDirective { + private _portal = signal | undefined>(undefined); + + /** The portal to display */ + portal = this._portal.asReadonly(); + + open(portal: Portal) { + this._portal.set(portal); + } + + close(portal: Portal) { + if (portal === this.portal()) { + this._portal.set(undefined); + } + } +} diff --git a/libs/components/src/drawer/drawer.component.html b/libs/components/src/drawer/drawer.component.html new file mode 100644 index 00000000000..fce6b3c57eb --- /dev/null +++ b/libs/components/src/drawer/drawer.component.html @@ -0,0 +1,8 @@ + +
    + +
    +
    diff --git a/libs/components/src/drawer/drawer.component.ts b/libs/components/src/drawer/drawer.component.ts new file mode 100644 index 00000000000..ccabb6f0b6e --- /dev/null +++ b/libs/components/src/drawer/drawer.component.ts @@ -0,0 +1,76 @@ +import { CdkPortal, PortalModule } from "@angular/cdk/portal"; +import { CommonModule } from "@angular/common"; +import { + ChangeDetectionStrategy, + Component, + effect, + inject, + input, + model, + viewChild, +} from "@angular/core"; + +import { DrawerHostDirective } from "./drawer-host.directive"; + +/** + * A drawer is a panel of supplementary content that is adjacent to the page's main content. + * + * Drawers render in `bit-layout`. Drawers must be a descendant of `bit-layout`, but they do not need to be a direct descendant. + */ +@Component({ + selector: "bit-drawer", + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [CommonModule, PortalModule], + templateUrl: "drawer.component.html", +}) +export class DrawerComponent { + private drawerHost = inject(DrawerHostDirective); + private portal = viewChild.required(CdkPortal); + + /** + * Whether or not the drawer is open. + * + * Note: Does not support implicit boolean transform due to Angular limitation. Must be bound explicitly `[open]="true"` instead of just `open`. + * https://github.com/angular/angular/issues/55166#issuecomment-2032150999 + **/ + open = model(false); + + /** + * The ARIA role of the drawer. + * + * - [complementary](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/complementary_role) + * - For drawers that contain content that is complementary to the page's main content. (default) + * - [navigation](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/navigation_role) + * - For drawers that primary contain links to other content. + */ + role = input<"complementary" | "navigation">("complementary"); + + constructor() { + effect( + () => { + this.open() ? this.drawerHost.open(this.portal()) : this.drawerHost.close(this.portal()); + }, + { + allowSignalWrites: true, + }, + ); + + // Set `open` to `false` when another drawer is opened. + effect( + () => { + if (this.drawerHost.portal() !== this.portal()) { + this.open.set(false); + } + }, + { + allowSignalWrites: true, + }, + ); + } + + /** Toggle the drawer between open & closed */ + toggle() { + this.open.update((prev) => !prev); + } +} diff --git a/libs/components/src/drawer/drawer.mdx b/libs/components/src/drawer/drawer.mdx new file mode 100644 index 00000000000..57d618cfe95 --- /dev/null +++ b/libs/components/src/drawer/drawer.mdx @@ -0,0 +1,120 @@ +import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs"; + +import * as stories from "./drawer.stories"; + +import { DrawerOpen as KitchenSink } from "../stories/kitchen-sink/kitchen-sink.stories"; + + + +```ts +import { DrawerComponent } from "@bitwarden/components"; +``` + +# Drawer + +A drawer is a panel of supplementary content that is adjacent to the page's main content. + + + + + +## Usage + +A `bit-drawer` in a template will not render inline, but rather will render adjacent to the main +page content. + +```html + + + +

    Lorem ipsum dolor...

    +
    +
    +``` + +`bit-drawer` must be a descendant of `bit-layout`, but it does not need to be a direct descendant. + +## Header and body + +Header and body content can be provided with the `bit-drawer-header` and `bit-drawer-body` +components, respectively. + +A title can be passed to the header by input: +`` + +Custom content can be rendered before the title with the header's `start` slot: + +```html + + + +``` + +## Opening and closing + +`bit-drawer` opens when its `open` input is `true`: + +```html +... +``` + +Note: Model inputs do not support implicit boolean transformation (see Angular reasoning +[here](https://github.com/angular/angular/issues/55166#issuecomment-2032150999)). `open` must be +bound explicitly `` instead of just ``. + +Buttons can be made to open/toggle drawers by referencing a template variable, or by manipulating +state that is bound to `open`: + +```html + ... +``` + +For convenience, close buttons can be created _inside_ the drawer with the `bitDrawerClose` +directive: + +```html + + + +``` + +## Multiple Drawers + +Only one drawer can be open at a time, and they do not stack. If a drawer is already open, opening +another will close and replace the one already open. + + + +## Headless + +Omitting `bit-drawer-header` and `bit-drawer-body` allows for fully customizable content. + + + +## Accessibility + +- The drawer should contain an h2 element. If you are using `bit-drawer-header`, this is created for + you via the `title` input: + +```html + +

    Hello world!

    +
    + + + + + + +``` + +- The ARIA role of the drawer can be set with the `role` attribute: + - [complementary](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/complementary_role) + (default) + - For drawers that contain content that is complementary to the page's main content. + - [navigation](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/navigation_role) + - For drawers that primary contain links to other content. + +## Kitchen Sink + + diff --git a/libs/components/src/drawer/drawer.module.ts b/libs/components/src/drawer/drawer.module.ts new file mode 100644 index 00000000000..9f51ba06b4e --- /dev/null +++ b/libs/components/src/drawer/drawer.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from "@angular/core"; + +import { DrawerBodyComponent } from "./drawer-body.component"; +import { DrawerCloseDirective } from "./drawer-close.directive"; +import { DrawerHeaderComponent } from "./drawer-header.component"; +import { DrawerComponent } from "./drawer.component"; + +@NgModule({ + imports: [DrawerComponent, DrawerHeaderComponent, DrawerBodyComponent, DrawerCloseDirective], + exports: [DrawerComponent, DrawerHeaderComponent, DrawerBodyComponent, DrawerCloseDirective], +}) +export class DrawerModule {} diff --git a/libs/components/src/drawer/drawer.stories.ts b/libs/components/src/drawer/drawer.stories.ts new file mode 100644 index 00000000000..a524c9a7a1a --- /dev/null +++ b/libs/components/src/drawer/drawer.stories.ts @@ -0,0 +1,120 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { RouterTestingModule } from "@angular/router/testing"; +import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; + +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; + +import { ButtonModule } from "../button"; +import { CalloutModule } from "../callout"; +import { LayoutComponent } from "../layout"; +import { mockLayoutI18n } from "../layout/mocks"; +import { positionFixedWrapperDecorator } from "../stories/storybook-decorators"; +import { TypographyModule } from "../typography"; +import { I18nMockService } from "../utils"; + +import { DrawerBodyComponent } from "./drawer-body.component"; +import { DrawerHeaderComponent } from "./drawer-header.component"; +import { DrawerComponent } from "./drawer.component"; +import { DrawerModule } from "./drawer.module"; + +export default { + title: "Component Library/Drawer", + component: DrawerComponent, + subcomponents: { + DrawerHeaderComponent, + DrawerBodyComponent, + }, + decorators: [ + positionFixedWrapperDecorator(), + moduleMetadata({ + imports: [ + RouterTestingModule, + LayoutComponent, + DrawerModule, + ButtonModule, + CalloutModule, + TypographyModule, + ], + providers: [ + { + provide: I18nService, + useFactory: () => { + return new I18nMockService({ + ...mockLayoutI18n, + close: "Close", + }); + }, + }, + ], + }), + ], +} as Meta; + +type Story = StoryObj; + +export const Default: Story = { + render: (args) => ({ + props: args, + template: /*html*/ ` + +

    The drawer is {{ open ? "open" : "closed" }}.

    + + + + + + + + +

    + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +

    + +
    + + `, + }), + args: { + open: true, + }, +}; + +export const Headless: Story = { + render: (args) => ({ + props: args, + template: /*html*/ ` + +

    The drawer is {{ open ? "open" : "closed" }}.

    + + +

    + Hello world! +
    + + `, + }), + args: { + open: true, + }, +}; + +export const MultipleDrawers: Story = { + render: (args) => ({ + props: args, + template: /*html*/ ` + + + + + + Foo + + + + Bar + + + `, + }), +}; diff --git a/libs/components/src/drawer/index.ts b/libs/components/src/drawer/index.ts new file mode 100644 index 00000000000..abf5b8d34f1 --- /dev/null +++ b/libs/components/src/drawer/index.ts @@ -0,0 +1,5 @@ +export * from "./drawer.module"; +export * from "./drawer.component"; +export * from "./drawer-body.component"; +export * from "./drawer-close.directive"; +export * from "./drawer-header.component"; diff --git a/libs/components/src/form-control/form-control.component.html b/libs/components/src/form-control/form-control.component.html index aed8f7e3b24..b15202b0223 100644 --- a/libs/components/src/form-control/form-control.component.html +++ b/libs/components/src/form-control/form-control.component.html @@ -9,11 +9,17 @@ > - ({{ "required" | i18n }}) + @if (required) { + ({{ "required" | i18n }}) + } - + @if (!hasError) { + + } -
    - {{ displayError }} -
    +@if (hasError) { +
    + {{ displayError }} +
    +} diff --git a/libs/components/src/form-control/form-control.component.ts b/libs/components/src/form-control/form-control.component.ts index 9b87c44157a..690c00a9dc0 100644 --- a/libs/components/src/form-control/form-control.component.ts +++ b/libs/components/src/form-control/form-control.component.ts @@ -1,12 +1,12 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { coerceBooleanProperty } from "@angular/cdk/coercion"; -import { NgClass, NgIf } from "@angular/common"; +import { NgClass } from "@angular/common"; import { Component, ContentChild, HostBinding, Input } from "@angular/core"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { I18nPipe } from "@bitwarden/ui-common"; -import { I18nPipe } from "../shared/i18n.pipe"; import { TypographyDirective } from "../typography/typography.directive"; import { BitFormControlAbstraction } from "./form-control.abstraction"; @@ -15,7 +15,7 @@ import { BitFormControlAbstraction } from "./form-control.abstraction"; selector: "bit-form-control", templateUrl: "form-control.component.html", standalone: true, - imports: [NgClass, TypographyDirective, NgIf, I18nPipe], + imports: [NgClass, TypographyDirective, I18nPipe], }) export class FormControlComponent { @Input() label: string; diff --git a/libs/components/src/form-control/label.component.html b/libs/components/src/form-control/label.component.html index 64ba1ce9501..2a0a57e35d8 100644 --- a/libs/components/src/form-control/label.component.html +++ b/libs/components/src/form-control/label.component.html @@ -5,10 +5,10 @@ - + @if (isInsideFormControl) { - + } - +@if (!isInsideFormControl) { - +} diff --git a/libs/components/src/form-field/bit-validators.stories.ts b/libs/components/src/form-field/bit-validators.stories.ts index 642ff30bb5a..748f92f2523 100644 --- a/libs/components/src/form-field/bit-validators.stories.ts +++ b/libs/components/src/form-field/bit-validators.stories.ts @@ -35,7 +35,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/f32LSg3jaegICkMu7rPARm/Tailwind-Component-Library-Update?node-id=1881%3A17689", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=13213-55392&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/form-field/error-summary.component.ts b/libs/components/src/form-field/error-summary.component.ts index beed32a88ac..1709c3078fa 100644 --- a/libs/components/src/form-field/error-summary.component.ts +++ b/libs/components/src/form-field/error-summary.component.ts @@ -1,22 +1,22 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { NgIf } from "@angular/common"; + import { Component, Input } from "@angular/core"; import { AbstractControl, UntypedFormGroup } from "@angular/forms"; -import { I18nPipe } from "../shared/i18n.pipe"; +import { I18nPipe } from "@bitwarden/ui-common"; @Component({ selector: "bit-error-summary", - template: ` + template: ` @if (errorCount > 0) { {{ "fieldsNeedAttention" | i18n: errorString }} - `, + }`, host: { class: "tw-block tw-text-danger tw-mt-2", "aria-live": "assertive", }, standalone: true, - imports: [NgIf, I18nPipe], + imports: [I18nPipe], }) export class BitErrorSummary { @Input() diff --git a/libs/components/src/form-field/error-summary.stories.ts b/libs/components/src/form-field/error-summary.stories.ts index 4e1031abaf6..4b1a30c45b3 100644 --- a/libs/components/src/form-field/error-summary.stories.ts +++ b/libs/components/src/form-field/error-summary.stories.ts @@ -34,7 +34,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=1881%3A17689", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=13213-55392&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/form-field/form-field.component.html b/libs/components/src/form-field/form-field.component.html index d2771f42465..bd099859608 100644 --- a/libs/components/src/form-field/form-field.component.html +++ b/libs/components/src/form-field/form-field.component.html @@ -15,63 +15,65 @@ -
    -
    -
    -
    -
    -
    -
    -
    - -
    -
    - -
    -
    - -
    -
    -
    - - +} @else {
    - +} - - - - +@switch (input.hasError) { + @case (false) { + + } + @case (true) { + + } +} diff --git a/libs/components/src/form-field/form-field.component.ts b/libs/components/src/form-field/form-field.component.ts index 9f41c6cf6ac..4f7c2a67483 100644 --- a/libs/components/src/form-field/form-field.component.ts +++ b/libs/components/src/form-field/form-field.component.ts @@ -14,10 +14,11 @@ import { signal, } from "@angular/core"; +import { I18nPipe } from "@bitwarden/ui-common"; + import { BitHintComponent } from "../form-control/hint.component"; import { BitLabel } from "../form-control/label.component"; import { inputBorderClasses } from "../input/input.directive"; -import { I18nPipe } from "../shared/i18n.pipe"; import { BitErrorComponent } from "./error.component"; import { BitFormFieldControl } from "./form-field-control"; diff --git a/libs/components/src/form-field/form-field.stories.ts b/libs/components/src/form-field/form-field.stories.ts index 6d8323e088e..738ac96bf76 100644 --- a/libs/components/src/form-field/form-field.stories.ts +++ b/libs/components/src/form-field/form-field.stories.ts @@ -108,7 +108,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=1881%3A17689", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=13213-55392&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/form-field/index.ts b/libs/components/src/form-field/index.ts index 613ebaf9a9d..0c45f215ec9 100644 --- a/libs/components/src/form-field/index.ts +++ b/libs/components/src/form-field/index.ts @@ -1,4 +1,5 @@ export * from "./form-field.module"; export * from "./form-field.component"; export * from "./form-field-control"; +export * from "./password-input-toggle.directive"; export * as BitValidators from "./bit-validators"; diff --git a/libs/components/src/form-field/multi-select.stories.ts b/libs/components/src/form-field/multi-select.stories.ts index da4776ab025..8d84aa735bf 100644 --- a/libs/components/src/form-field/multi-select.stories.ts +++ b/libs/components/src/form-field/multi-select.stories.ts @@ -56,7 +56,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=5600%3A24278", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=13213-55392&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/form-field/password-input-toggle.stories.ts b/libs/components/src/form-field/password-input-toggle.stories.ts index 094f939e0ea..d46ec92ab37 100644 --- a/libs/components/src/form-field/password-input-toggle.stories.ts +++ b/libs/components/src/form-field/password-input-toggle.stories.ts @@ -27,7 +27,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/f32LSg3jaegICkMu7rPARm/Tailwind-Component-Library-Update?node-id=1881%3A17689", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=13213-55392&t=b5tDKylm5sWm2yKo-4", }, docs: { description: { diff --git a/libs/components/src/form/form.stories.ts b/libs/components/src/form/form.stories.ts index 23b2cc8cea2..6aef140fe5f 100644 --- a/libs/components/src/form/form.stories.ts +++ b/libs/components/src/form/form.stories.ts @@ -64,7 +64,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=1881%3A17689", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=13213-55392&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/form/forms.mdx b/libs/components/src/form/forms.mdx index fbb3a20e518..e3baf200f96 100644 --- a/libs/components/src/form/forms.mdx +++ b/libs/components/src/form/forms.mdx @@ -1,4 +1,4 @@ -import { Meta, Story, Source } from "@storybook/addon-docs"; +import { Meta, Canvas, Source } from "@storybook/addon-docs"; import * as formStories from "./form.stories"; import * as fieldStories from "../form-field/form-field.stories"; @@ -17,7 +17,7 @@ Component Library forms should always be built using [Angular Reactive Forms][re [ADR-0001][adr-0001] for a background to this decision. In practice this means that forms should always use the native `form` element and bind a `formGroup`. - +
    @@ -69,25 +69,25 @@ is too long, such as info link buttons or badges. #### Default with required attribute - + #### Password Toggle - + ### Search - + ### Selects #### Searchable single select (default) - + #### Multi-select - + ### Radio group @@ -113,11 +113,11 @@ using a radio group for more than 5 options even if the options require addition #### Block - + #### Inline - + ### Checkbox @@ -140,7 +140,7 @@ If a checkbox group has more than 4 options a #### Single checkbox - + ## Accessibility diff --git a/libs/components/src/icon-button/icon-button.component.html b/libs/components/src/icon-button/icon-button.component.html index 6eeaaaffaf0..0145f0b0ba5 100644 --- a/libs/components/src/icon-button/icon-button.component.html +++ b/libs/components/src/icon-button/icon-button.component.html @@ -1,10 +1,10 @@ - + = { "hover:tw-bg-transparent-hover", "hover:tw-border-text-contrast", "focus-visible:before:tw-ring-text-contrast", - "disabled:tw-opacity-60", - "disabled:hover:tw-border-transparent", - "disabled:hover:tw-bg-transparent", ...focusRing, ], main: [ @@ -46,9 +45,6 @@ const styles: Record = { "hover:tw-bg-transparent-hover", "hover:tw-border-primary-600", "focus-visible:before:tw-ring-primary-600", - "disabled:!tw-text-secondary-300", - "disabled:hover:tw-border-transparent", - "disabled:hover:tw-bg-transparent", ...focusRing, ], muted: [ @@ -60,11 +56,8 @@ const styles: Record = { "hover:tw-bg-transparent-hover", "hover:tw-border-primary-600", "focus-visible:before:tw-ring-primary-600", - "disabled:!tw-text-secondary-300", "aria-expanded:hover:tw-bg-secondary-700", "aria-expanded:hover:tw-border-secondary-700", - "disabled:hover:tw-border-transparent", - "disabled:hover:tw-bg-transparent", ...focusRing, ], primary: [ @@ -74,9 +67,6 @@ const styles: Record = { "hover:tw-bg-primary-600", "hover:tw-border-primary-600", "focus-visible:before:tw-ring-primary-600", - "disabled:tw-opacity-60", - "disabled:hover:tw-border-primary-600", - "disabled:hover:tw-bg-primary-600", ...focusRing, ], secondary: [ @@ -86,10 +76,6 @@ const styles: Record = { "hover:!tw-text-contrast", "hover:tw-bg-text-muted", "focus-visible:before:tw-ring-primary-600", - "disabled:tw-opacity-60", - "disabled:hover:tw-border-text-muted", - "disabled:hover:tw-bg-transparent", - "disabled:hover:!tw-text-muted", ...focusRing, ], danger: [ @@ -100,10 +86,6 @@ const styles: Record = { "hover:tw-bg-transparent", "hover:tw-border-primary-600", "focus-visible:before:tw-ring-primary-600", - "disabled:!tw-text-secondary-300", - "disabled:hover:tw-border-transparent", - "disabled:hover:tw-bg-transparent", - "disabled:hover:!tw-text-secondary-300", ...focusRing, ], light: [ @@ -113,10 +95,48 @@ const styles: Record = { "hover:tw-bg-transparent-hover", "hover:tw-border-text-alt2", "focus-visible:before:tw-ring-text-alt2", + ...focusRing, + ], + unstyled: [], +}; + +const disabledStyles: Record = { + contrast: [ + "disabled:tw-opacity-60", + "disabled:hover:tw-border-transparent", + "disabled:hover:tw-bg-transparent", + ], + main: [ + "disabled:!tw-text-secondary-300", + "disabled:hover:tw-border-transparent", + "disabled:hover:tw-bg-transparent", + ], + muted: [ + "disabled:!tw-text-secondary-300", + "disabled:hover:tw-border-transparent", + "disabled:hover:tw-bg-transparent", + ], + primary: [ + "disabled:tw-opacity-60", + "disabled:hover:tw-border-primary-600", + "disabled:hover:tw-bg-primary-600", + ], + secondary: [ + "disabled:tw-opacity-60", + "disabled:hover:tw-border-text-muted", + "disabled:hover:tw-bg-transparent", + "disabled:hover:!tw-text-muted", + ], + danger: [ + "disabled:!tw-text-secondary-300", + "disabled:hover:tw-border-transparent", + "disabled:hover:tw-bg-transparent", + "disabled:hover:!tw-text-secondary-300", + ], + light: [ "disabled:tw-opacity-60", "disabled:hover:tw-border-transparent", "disabled:hover:tw-bg-transparent", - ...focusRing, ], unstyled: [], }; @@ -137,11 +157,14 @@ const sizes: Record = { ], standalone: true, imports: [NgClass], + host: { + "[attr.disabled]": "disabledAttr()", + }, }) export class BitIconButtonComponent implements ButtonLikeAbstraction, FocusableElement { @Input("bitIconButton") icon: string; - @Input() buttonType: IconButtonType; + @Input() buttonType: IconButtonType = "main"; @Input() size: IconButtonSize = "default"; @@ -155,22 +178,51 @@ export class BitIconButtonComponent implements ButtonLikeAbstraction, FocusableE "hover:tw-no-underline", "focus:tw-outline-none", ] - .concat(styles[this.buttonType ?? "main"]) - .concat(sizes[this.size]); + .concat(styles[this.buttonType]) + .concat(sizes[this.size]) + .concat(this.showDisabledStyles() || this.disabled() ? disabledStyles[this.buttonType] : []); } get iconClass() { return [this.icon, "!tw-m-0"]; } - @HostBinding("attr.disabled") - get disabledAttr() { - const disabled = this.disabled != null && this.disabled !== false; - return disabled || this.loading ? true : null; - } + protected disabledAttr = computed(() => { + const disabled = this.disabled() != null && this.disabled() !== false; + return disabled || this.loading() ? true : null; + }); - @Input() loading = false; - @Input() disabled = false; + /** + * Determine whether it is appropriate to display the disabled styles. We only want to show + * the disabled styles if the button is truly disabled, or if the loading styles are also + * visible. + * + * We can't use `disabledAttr` for this, because it returns `true` when `loading` is `true`. + * We only want to show disabled styles during loading if `showLoadingStyles` is `true`. + */ + protected showDisabledStyles = computed(() => { + return this.showLoadingStyle() || (this.disabledAttr() && this.loading() === false); + }); + + loading = model(false); + + /** + * Determine whether it is appropriate to display a loading spinner. We only want to show + * a spinner if it's been more than 75 ms since the `loading` state began. This prevents + * a spinner "flash" for actions that are synchronous/nearly synchronous. + * + * We can't use `loading` for this, because we still need to disable the button during + * the full `loading` state. I.e. we only want the spinner to be debounced, not the + * loading state. + * + * This pattern of converting a signal to an observable and back to a signal is not + * recommended. TODO -- find better way to use debounce with signals (CL-596) + */ + protected showLoadingStyle = toSignal( + toObservable(this.loading).pipe(debounce((isLoading) => interval(isLoading ? 75 : 0))), + ); + + disabled = model(false); getFocusTarget() { return this.elementRef.nativeElement; diff --git a/libs/components/src/icon-button/icon-button.mdx b/libs/components/src/icon-button/icon-button.mdx index a45160d7884..85164717de7 100644 --- a/libs/components/src/icon-button/icon-button.mdx +++ b/libs/components/src/icon-button/icon-button.mdx @@ -1,4 +1,4 @@ -import { Meta, Story, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs"; import * as stories from "./icon-button.stories"; @@ -38,48 +38,48 @@ button component styles. Used for general icon buttons appearing on the theme’s main `background` - + ### Muted Used for low emphasis icon buttons appearing on the theme’s main `background` - + ### Contrast Used on a theme’s colored or contrasting backgrounds such as in the navigation or on toasts and banners. - + ### Danger Danger is used for “trash” actions throughout the experience, most commonly in the bottom right of the dialog component. - + ### Primary Used in place of the main button component if no text is used. This allows the button to display square. - + ### Secondary Used in place of the main button component if no text is used. This allows the button to display square. - + ### Light Used on a background that is dark in both light theme and dark theme. Example: end user navigation styles. - + **Note:** Main and contrast styles appear on backgrounds where using `primary-700` as a focus indicator does not meet WCAG graphic contrast guidelines. @@ -93,11 +93,11 @@ with less padding around the icon, such as in the navigation component. ### Small - + ### Default - + ## Accessibility diff --git a/libs/components/src/icon-button/icon-button.stories.ts b/libs/components/src/icon-button/icon-button.stories.ts index 6274bb2b8d1..08c95c5d641 100644 --- a/libs/components/src/icon-button/icon-button.stories.ts +++ b/libs/components/src/icon-button/icon-button.stories.ts @@ -13,7 +13,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=4369%3A16686", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-37011&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/icon/icon.stories.ts b/libs/components/src/icon/icon.stories.ts index 54cdd7928cd..53454567b7f 100644 --- a/libs/components/src/icon/icon.stories.ts +++ b/libs/components/src/icon/icon.stories.ts @@ -6,6 +6,12 @@ import * as GenericIcons from "./icons"; export default { title: "Component Library/Icon", component: BitIconComponent, + parameters: { + design: { + type: "figma", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=21662-50335&t=k6OTDDPZOTtypRqo-11", + }, + }, } as Meta; type Story = StoryObj; diff --git a/libs/components/src/index.ts b/libs/components/src/index.ts index a48750a99ff..319b60e6435 100644 --- a/libs/components/src/index.ts +++ b/libs/components/src/index.ts @@ -1,3 +1,5 @@ +export { ButtonType, ButtonLikeAbstraction } from "./shared/button-like.abstraction"; +export * from "./a11y"; export * from "./async-actions"; export * from "./avatar"; export * from "./badge-list"; @@ -5,15 +7,16 @@ export * from "./badge"; export * from "./banner"; export * from "./breadcrumbs"; export * from "./button"; -export { ButtonType } from "./shared/button-like.abstraction"; export * from "./callout"; export * from "./card"; export * from "./checkbox"; export * from "./chip-select"; export * from "./color-password"; export * from "./container"; +export * from "./copy-click"; export * from "./dialog"; export * from "./disclosure"; +export * from "./drawer"; export * from "./form-field"; export * from "./icon-button"; export * from "./icon"; diff --git a/libs/components/src/input/index.ts b/libs/components/src/input/index.ts index 9713d2b9192..6bd64495910 100644 --- a/libs/components/src/input/index.ts +++ b/libs/components/src/input/index.ts @@ -1,2 +1,3 @@ export * from "./input.module"; export * from "./autofocus.directive"; +export * from "./input.directive"; diff --git a/libs/components/src/item/item-content.component.html b/libs/components/src/item/item-content.component.html index 6f900c5c6e1..4010970dc9e 100644 --- a/libs/components/src/item/item-content.component.html +++ b/libs/components/src/item/item-content.component.html @@ -6,14 +6,26 @@ bitTypography="body2" class="tw-text-main tw-truncate tw-inline-flex tw-items-center tw-gap-1.5 tw-w-full" > -
    +
    -
    +
    diff --git a/libs/components/src/item/item-content.component.ts b/libs/components/src/item/item-content.component.ts index f6cc3f133ad..2a6e06291fd 100644 --- a/libs/components/src/item/item-content.component.ts +++ b/libs/components/src/item/item-content.component.ts @@ -1,11 +1,13 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { CommonModule } from "@angular/common"; + +import { NgClass } from "@angular/common"; import { AfterContentChecked, ChangeDetectionStrategy, Component, ElementRef, + Input, signal, ViewChild, } from "@angular/core"; @@ -15,7 +17,7 @@ import { TypographyModule } from "../typography"; @Component({ selector: "bit-item-content, [bit-item-content]", standalone: true, - imports: [CommonModule, TypographyModule], + imports: [TypographyModule, NgClass], templateUrl: `item-content.component.html`, host: { class: @@ -32,6 +34,13 @@ export class ItemContentComponent implements AfterContentChecked { protected endSlotHasChildren = signal(false); + /** + * Determines whether text will truncate or wrap. + * + * Default behavior is truncation. + */ + @Input() truncate = true; + ngAfterContentChecked(): void { this.endSlotHasChildren.set(this.endSlot?.nativeElement.childElementCount > 0); } diff --git a/libs/components/src/item/item.component.ts b/libs/components/src/item/item.component.ts index 97a80484373..1ef4a4af1fa 100644 --- a/libs/components/src/item/item.component.ts +++ b/libs/components/src/item/item.component.ts @@ -1,4 +1,3 @@ -import { CommonModule } from "@angular/common"; import { ChangeDetectionStrategy, Component, @@ -14,7 +13,7 @@ import { ItemActionComponent } from "./item-action.component"; @Component({ selector: "bit-item", standalone: true, - imports: [CommonModule, ItemActionComponent], + imports: [ItemActionComponent], changeDetection: ChangeDetectionStrategy.OnPush, templateUrl: "item.component.html", providers: [{ provide: A11yRowDirective, useExisting: ItemComponent }], diff --git a/libs/components/src/item/item.mdx b/libs/components/src/item/item.mdx index ca697ebb436..d3e6065487a 100644 --- a/libs/components/src/item/item.mdx +++ b/libs/components/src/item/item.mdx @@ -1,4 +1,4 @@ -import { Meta, Story, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs"; import * as stories from "./item.stories"; @@ -15,14 +15,14 @@ import { ItemModule } from "@bitwarden/components"; It is a generic container that can be used for either standalone content, an alternative to tables, or to list nav links. - +
    Items used within a parent `bit-layout` component will not have a border radius, since the `bit-layout` background is white. - +

    @@ -49,7 +49,7 @@ The content can be a button, anchor, or static container. ``` - + ### Content Slots @@ -82,7 +82,7 @@ The content can be a button, anchor, or static container. ``` - + ## Secondary Actions @@ -111,13 +111,37 @@ Actions are commonly icon buttons or badge buttons. ``` +## Text Overflow Behavior + +The default behavior for long text is to truncate it. However, you have the option of changing it to +wrap instead if that is what the design calls for. + +This can be changed by passing `[truncate]="false"` to the `bit-item-content`. + +```html + + + Long text goes here! + This could also be very long text + + +``` + +### Truncation (Default) + + + +### Wrap + + + ## Item Groups Groups of items can be associated by wrapping them in the ``. - + - + ### A11y @@ -138,4 +162,4 @@ Use `aria-label` or `aria-labelledby` to give groups an accessible name. ### Virtual Scrolling - + diff --git a/libs/components/src/item/item.stories.ts b/libs/components/src/item/item.stories.ts index 5adf9d3c49d..fd2d59c7ac2 100644 --- a/libs/components/src/item/item.stories.ts +++ b/libs/components/src/item/item.stories.ts @@ -53,6 +53,12 @@ export default { }), componentWrapperDecorator((story) => `
    ${story}
    `), ], + parameters: { + design: { + type: "figma", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-37011&t=b5tDKylm5sWm2yKo-11", + }, + }, } as Meta; type Story = StoryObj; @@ -129,7 +135,7 @@ export const ContentTypes: Story = { }), }; -export const TextOverflow: Story = { +export const TextOverflowTruncate: Story = { render: (args) => ({ props: args, template: /*html*/ ` @@ -152,6 +158,29 @@ export const TextOverflow: Story = { }), }; +export const TextOverflowWrap: Story = { + render: (args) => ({ + props: args, + template: /*html*/ ` + + + + Helloooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo! + Worlddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd! + + + + + + + + + + + `, + }), +}; + const multipleActionListTemplate = /*html*/ ` diff --git a/libs/components/src/layout/layout.component.html b/libs/components/src/layout/layout.component.html index ccbb40c2b57..33b8de81572 100644 --- a/libs/components/src/layout/layout.component.html +++ b/libs/components/src/layout/layout.component.html @@ -23,18 +23,21 @@ -
    + }; + as data + ) {
    -
    + class="tw-pointer-events-none tw-fixed tw-inset-0 tw-z-10 tw-bg-black tw-bg-opacity-0 motion-safe:tw-transition-colors md:tw-hidden" + [ngClass]="[data.open ? 'tw-bg-opacity-30 md:tw-bg-opacity-0' : 'tw-bg-opacity-0']" + > + @if (data.open) { +
    + } +

    + } +
    diff --git a/libs/components/src/layout/layout.component.ts b/libs/components/src/layout/layout.component.ts index d55ad8493eb..7bf8a6ad173 100644 --- a/libs/components/src/layout/layout.component.ts +++ b/libs/components/src/layout/layout.component.ts @@ -1,7 +1,9 @@ +import { PortalModule } from "@angular/cdk/portal"; import { CommonModule } from "@angular/common"; -import { Component } from "@angular/core"; +import { Component, inject } from "@angular/core"; import { RouterModule } from "@angular/router"; +import { DrawerHostDirective } from "../drawer/drawer-host.directive"; import { LinkModule } from "../link"; import { SideNavService } from "../navigation/side-nav.service"; import { SharedModule } from "../shared"; @@ -10,12 +12,14 @@ import { SharedModule } from "../shared"; selector: "bit-layout", templateUrl: "layout.component.html", standalone: true, - imports: [CommonModule, SharedModule, LinkModule, RouterModule], + imports: [CommonModule, SharedModule, LinkModule, RouterModule, PortalModule], + hostDirectives: [DrawerHostDirective], }) export class LayoutComponent { protected mainContentId = "main-content"; - constructor(protected sideNavService: SideNavService) {} + protected sideNavService = inject(SideNavService); + protected drawerPortal = inject(DrawerHostDirective).portal; focusMainContent() { document.getElementById(this.mainContentId)?.focus(); diff --git a/libs/components/src/layout/layout.stories.ts b/libs/components/src/layout/layout.stories.ts index a0eadebe7fa..e09055df596 100644 --- a/libs/components/src/layout/layout.stories.ts +++ b/libs/components/src/layout/layout.stories.ts @@ -6,10 +6,11 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { CalloutModule } from "../callout"; import { NavigationModule } from "../navigation"; +import { positionFixedWrapperDecorator } from "../stories/storybook-decorators"; import { I18nMockService } from "../utils/i18n-mock.service"; -import { positionFixedWrapperDecorator } from "../utils/position-fixed-wrapper-decorator"; import { LayoutComponent } from "./layout.component"; +import { mockLayoutI18n } from "./mocks"; export default { title: "Component Library/Layout", @@ -22,12 +23,7 @@ export default { { provide: I18nService, useFactory: () => { - return new I18nMockService({ - toggleSideNavigation: "Toggle side navigation", - skipToContent: "Skip to content", - submenu: "submenu", - toggleCollapse: "toggle collapse", - }); + return new I18nMockService(mockLayoutI18n); }, }, ], @@ -35,6 +31,10 @@ export default { ], parameters: { chromatic: { viewports: [640, 1280] }, + design: { + type: "figma", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=21662-51009&t=k6OTDDPZOTtypRqo-11", + }, }, } as Meta; diff --git a/libs/components/src/layout/mocks.ts b/libs/components/src/layout/mocks.ts new file mode 100644 index 00000000000..50c2bd9afb2 --- /dev/null +++ b/libs/components/src/layout/mocks.ts @@ -0,0 +1,7 @@ +/** TODO: create mock messages.json file for all of CL in favor of sharing per-Story mocks */ +export const mockLayoutI18n = { + toggleSideNavigation: "Toggle side navigation", + skipToContent: "Skip to content", + submenu: "submenu", + toggleCollapse: "toggle collapse", +}; diff --git a/libs/components/src/link/link.stories.ts b/libs/components/src/link/link.stories.ts index cc3d26dc9d2..d07d33ae589 100644 --- a/libs/components/src/link/link.stories.ts +++ b/libs/components/src/link/link.stories.ts @@ -19,7 +19,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=1881%3A17419", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-39582&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/menu/menu.mdx b/libs/components/src/menu/menu.mdx index 67c276cf372..d77dc0a7d7b 100644 --- a/libs/components/src/menu/menu.mdx +++ b/libs/components/src/menu/menu.mdx @@ -1,4 +1,4 @@ -import { Meta, Story, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs"; import * as stories from "./menu.stories"; @@ -9,7 +9,7 @@ import * as stories from "./menu.stories"; Menus are used to help organize related options. Menus are most often used for item options in tables. - +
    diff --git a/libs/components/src/menu/menu.stories.ts b/libs/components/src/menu/menu.stories.ts index 65fafd2d04d..f1f4d8df000 100644 --- a/libs/components/src/menu/menu.stories.ts +++ b/libs/components/src/menu/menu.stories.ts @@ -17,7 +17,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=1881%3A17952", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-40144&t=b5tDKylm5sWm2yKo-11", }, }, } as Meta; diff --git a/libs/components/src/multi-select/multi-select.component.html b/libs/components/src/multi-select/multi-select.component.html index 0c45e2d333f..e157871e17a 100644 --- a/libs/components/src/multi-select/multi-select.component.html +++ b/libs/components/src/multi-select/multi-select.component.html @@ -31,7 +31,9 @@ [disabled]="disabled" (click)="clear(item)" > - + @if (item.icon != null) { + + } {{ item.labelName }} @@ -41,10 +43,14 @@
    - + @if (isSelected(item)) { + + }
    - + @if (item.icon != null) { + + }
    {{ item.listName }} diff --git a/libs/components/src/multi-select/multi-select.component.ts b/libs/components/src/multi-select/multi-select.component.ts index 53e51bfe2f9..cd92eb1d7ae 100644 --- a/libs/components/src/multi-select/multi-select.component.ts +++ b/libs/components/src/multi-select/multi-select.component.ts @@ -2,7 +2,6 @@ // @ts-strict-ignore import { coerceBooleanProperty } from "@angular/cdk/coercion"; import { hasModifierKey } from "@angular/cdk/keycodes"; -import { NgIf } from "@angular/common"; import { Component, Input, @@ -24,10 +23,10 @@ import { import { NgSelectComponent, NgSelectModule } from "@ng-select/ng-select"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { I18nPipe } from "@bitwarden/ui-common"; import { BadgeModule } from "../badge"; import { BitFormFieldControl } from "../form-field/form-field-control"; -import { I18nPipe } from "../shared/i18n.pipe"; import { SelectItemView } from "./models/select-item-view"; @@ -39,7 +38,7 @@ let nextId = 0; templateUrl: "./multi-select.component.html", providers: [{ provide: BitFormFieldControl, useExisting: MultiSelectComponent }], standalone: true, - imports: [NgSelectModule, ReactiveFormsModule, FormsModule, BadgeModule, NgIf, I18nPipe], + imports: [NgSelectModule, ReactiveFormsModule, FormsModule, BadgeModule, I18nPipe], }) /** * This component has been implemented to only support Multi-select list events diff --git a/libs/components/src/navigation/nav-divider.component.html b/libs/components/src/navigation/nav-divider.component.html index 224f6ae0657..64e43aeab4e 100644 --- a/libs/components/src/navigation/nav-divider.component.html +++ b/libs/components/src/navigation/nav-divider.component.html @@ -1 +1,3 @@ -
    +@if (sideNavService.open$ | async) { +
    +} diff --git a/libs/components/src/navigation/nav-group.component.html b/libs/components/src/navigation/nav-group.component.html index 9f6d9ac034d..9752fe56eb1 100644 --- a/libs/components/src/navigation/nav-group.component.html +++ b/libs/components/src/navigation/nav-group.component.html @@ -1,5 +1,5 @@ - +@if (!hideIfEmpty || nestedNavComponents.length > 0) { - - - - - - - + @if (variant === "tree") { + + } + + + @if (variant !== "tree") { + + } - - -
    - -
    -
    -
    + @if (sideNavService.open$ | async) { + @if (open) { +
    + +
    + } + } +} diff --git a/libs/components/src/navigation/nav-group.component.ts b/libs/components/src/navigation/nav-group.component.ts index 58d93ddd3a4..37244f37c8d 100644 --- a/libs/components/src/navigation/nav-group.component.ts +++ b/libs/components/src/navigation/nav-group.component.ts @@ -12,8 +12,9 @@ import { SkipSelf, } from "@angular/core"; +import { I18nPipe } from "@bitwarden/ui-common"; + import { IconButtonModule } from "../icon-button"; -import { I18nPipe } from "../shared/i18n.pipe"; import { NavBaseComponent } from "./nav-base.component"; import { NavGroupAbstraction, NavItemComponent } from "./nav-item.component"; @@ -70,6 +71,8 @@ export class NavGroupComponent extends NavBaseComponent implements AfterContentI setOpen(isOpen: boolean) { this.open = isOpen; this.openChange.emit(this.open); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions this.open && this.parentNavGroup?.setOpen(this.open); } diff --git a/libs/components/src/navigation/nav-group.stories.ts b/libs/components/src/navigation/nav-group.stories.ts index a6fa53ff187..6fbb89d4d9e 100644 --- a/libs/components/src/navigation/nav-group.stories.ts +++ b/libs/components/src/navigation/nav-group.stories.ts @@ -6,8 +6,8 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { LayoutComponent } from "../layout"; import { SharedModule } from "../shared/shared.module"; +import { positionFixedWrapperDecorator } from "../stories/storybook-decorators"; import { I18nMockService } from "../utils/i18n-mock.service"; -import { positionFixedWrapperDecorator } from "../utils/position-fixed-wrapper-decorator"; import { NavGroupComponent } from "./nav-group.component"; import { NavigationModule } from "./navigation.module"; @@ -62,7 +62,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=4687%3A86642", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-40145&t=b5tDKylm5sWm2yKo-4", }, chromatic: { viewports: [640, 1280] }, }, diff --git a/libs/components/src/navigation/nav-item.stories.ts b/libs/components/src/navigation/nav-item.stories.ts index 20d6ebd1d7e..0f6f406b2eb 100644 --- a/libs/components/src/navigation/nav-item.stories.ts +++ b/libs/components/src/navigation/nav-item.stories.ts @@ -5,8 +5,8 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { IconButtonModule } from "../icon-button"; import { LayoutComponent } from "../layout"; +import { positionFixedWrapperDecorator } from "../stories/storybook-decorators"; import { I18nMockService } from "../utils/i18n-mock.service"; -import { positionFixedWrapperDecorator } from "../utils/position-fixed-wrapper-decorator"; import { NavItemComponent } from "./nav-item.component"; import { NavigationModule } from "./navigation.module"; @@ -39,7 +39,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=4687%3A86642", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-40145&t=b5tDKylm5sWm2yKo-4", }, chromatic: { viewports: [640, 1280] }, }, diff --git a/libs/components/src/navigation/nav-logo.component.html b/libs/components/src/navigation/nav-logo.component.html index 427e926f2d7..a6169315333 100644 --- a/libs/components/src/navigation/nav-logo.component.html +++ b/libs/components/src/navigation/nav-logo.component.html @@ -1,20 +1,23 @@ - - +@if (sideNavService.open) { +
    + + + +
    +} +@if (!sideNavService.open) { + +} diff --git a/libs/components/src/navigation/nav-logo.component.ts b/libs/components/src/navigation/nav-logo.component.ts index 8a84970500c..de9d801e553 100644 --- a/libs/components/src/navigation/nav-logo.component.ts +++ b/libs/components/src/navigation/nav-logo.component.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { NgIf } from "@angular/common"; + import { Component, Input } from "@angular/core"; import { RouterLinkActive, RouterLink } from "@angular/router"; @@ -14,7 +14,7 @@ import { SideNavService } from "./side-nav.service"; selector: "bit-nav-logo", templateUrl: "./nav-logo.component.html", standalone: true, - imports: [NgIf, RouterLinkActive, RouterLink, BitIconComponent, NavItemComponent], + imports: [RouterLinkActive, RouterLink, BitIconComponent, NavItemComponent], }) export class NavLogoComponent { /** Icon that is displayed when the side nav is closed */ diff --git a/libs/components/src/navigation/side-nav.component.html b/libs/components/src/navigation/side-nav.component.html index 05c99c7d64e..3b77c981be4 100644 --- a/libs/components/src/navigation/side-nav.component.html +++ b/libs/components/src/navigation/side-nav.component.html @@ -1,42 +1,46 @@ - + +} diff --git a/libs/components/src/navigation/side-nav.component.ts b/libs/components/src/navigation/side-nav.component.ts index c86a517100f..e8e4f131d6d 100644 --- a/libs/components/src/navigation/side-nav.component.ts +++ b/libs/components/src/navigation/side-nav.component.ts @@ -4,8 +4,9 @@ import { CdkTrapFocus } from "@angular/cdk/a11y"; import { CommonModule } from "@angular/common"; import { Component, ElementRef, Input, ViewChild } from "@angular/core"; +import { I18nPipe } from "@bitwarden/ui-common"; + import { BitIconButtonComponent } from "../icon-button/icon-button.component"; -import { I18nPipe } from "../shared/i18n.pipe"; import { NavDividerComponent } from "./nav-divider.component"; import { SideNavService } from "./side-nav.service"; diff --git a/libs/components/src/no-items/no-items.stories.ts b/libs/components/src/no-items/no-items.stories.ts index d8e5b59bdbf..48d52476b17 100644 --- a/libs/components/src/no-items/no-items.stories.ts +++ b/libs/components/src/no-items/no-items.stories.ts @@ -13,6 +13,12 @@ export default { imports: [ButtonModule, NoItemsModule], }), ], + parameters: { + design: { + type: "figma", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=21665-25102&t=k6OTDDPZOTtypRqo-11", + }, + }, } as Meta; type Story = StoryObj; diff --git a/libs/components/src/popover/popover.component.html b/libs/components/src/popover/popover.component.html index 8da3b002031..cb0681822d0 100644 --- a/libs/components/src/popover/popover.component.html +++ b/libs/components/src/popover/popover.component.html @@ -1,11 +1,11 @@